From 03b43b6637fef70e2f815fb8b1320473580df44e Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 3 May 2025 15:23:53 +0200 Subject: [PATCH 01/95] feat: rename tap screen actions inside key maps --- CHANGELOG.md | 4 ++++ .../java/io/github/sds100/keymapper/actions/ActionUiHelper.kt | 2 +- app/src/main/res/values/strings.xml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b43105a25..7e501ffdd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - #699 Time constraints ⏰ +## Changed + +- Rename tap screen actions inside key maps. + ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) #### 28 April 2025 diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt index 755353b04d..9aa24cbbed 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt @@ -356,7 +356,7 @@ class ActionUiHelper( } else { getString( R.string.description_tap_coordinate_with_description, - arrayOf(action.x, action.y, action.description), + action.description, ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 46985421d3..f4747ad28e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -98,8 +98,8 @@ Input %s through shell Input %s%s from %s Open %s - Tap coordinates %d, %d - Tap coordinates %d, %d (%s) + Tap screen (%d, %d) + Tap screen (%s) Swipe with %d finger(s) from coordinates %d/%d to %d/%d in %dms Swipe with %d finger(s) from coordinates %d/%d to %d/%d in %dms (%s) %s with %d finger(s) on coordinates %d/%d with a pinch distance of %dpx in %dms From 684de16c9d7421a2b97f143e8fbd9f9a2d5847d6 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 5 May 2025 00:18:27 +0200 Subject: [PATCH 02/95] chore: update version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index c6beedf368..977745561c 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.0 -VERSION_CODE=104 +VERSION_CODE=105 VERSION_NUM=0 \ No newline at end of file From 7b11e4c894588d2c0e03b91792573f483b0afc72 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 5 May 2025 00:21:36 +0200 Subject: [PATCH 03/95] fix: editing actions works again --- .../io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt index 96d6da8bb0..f501900c70 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt @@ -177,7 +177,6 @@ class ConfigActionsViewModel( override fun onEditClick() { val actionUid = actionOptionsUid.value ?: return coroutineScope.launch { - actionOptionsUid.update { null } val keyMap = config.keyMap.first().dataOrNull() ?: return@launch val oldAction = keyMap.actionList.find { it.uid == actionUid } ?: return@launch From 3d4b5c9a04fc13661a079f34ef7162de7b3093bd Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 5 May 2025 00:23:25 +0200 Subject: [PATCH 04/95] chore: update whats new --- app/src/main/assets/whats-new.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/assets/whats-new.txt b/app/src/main/assets/whats-new.txt index 7c29bd65d8..2d54ecfa7c 100644 --- a/app/src/main/assets/whats-new.txt +++ b/app/src/main/assets/whats-new.txt @@ -1,4 +1,10 @@ -Key Mapper 3.0 is here! 🎉 +** 3.1 features ** + +⏰ Time constraints. + +🔎 Action to interact with app elements. + +** 3.0 features ** 🫧 Floating Buttons: you can create custom on-screen buttons to trigger key maps. From 0cd1c3455abd8043095c218141b62fbc272f4040 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 5 May 2025 19:11:13 +0200 Subject: [PATCH 05/95] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 977745561c..9fa7b22caa 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.0 -VERSION_CODE=105 +VERSION_CODE=107 VERSION_NUM=0 \ No newline at end of file From ee0e2251f9346ecf5598c431c33aa7bb1ed02b43 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 15:14:23 +0200 Subject: [PATCH 06/95] update strings for interact with ui element screen --- .../actions/uielement/InteractUiElementScreen.kt | 2 +- app/src/main/res/values/strings.xml | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt index c4fd683a5d..e2d1a13d70 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt @@ -389,7 +389,7 @@ private fun InteractionCountBox( Column(modifier = Modifier.weight(1f)) { Text( pluralStringResource( - R.plurals.action_interact_ui_element_interactions_detected, + R.plurals.action_interact_ui_element_elements_detected, interactionCount, interactionCount, ), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 933bc823e1..295eef2f03 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1104,13 +1104,12 @@ You must prepend \'Bearer\' if necessary Interact with app element - Key Mapper can detect and interact with app elements like menus, tabs, buttons and checkboxes. You need to record yourself interacting with the app element so that Key Mapper knows what you want to do. + Key Mapper can detect and interact with app elements like menus, tabs, buttons and checkboxes. You need to record yourself interacting with the app so that Key Mapper knows what you want to do. Start recording Stop recording (%s min left) - Go to another app and interact with it. Key Mapper will record what you do and you can choose which interactions you want to use in your key map. Open Key Mapper again when you’re done. - - %d interaction detected - %d interactions detected + + %d element detected + %d elements detected Choose the app to interact with Record again @@ -1118,7 +1117,7 @@ Choose the element you want your key map to interact with. Can\'t find what you’re looking for? Not all apps are compatible. For incompatible apps you can try the Tap Screen action instead. - Interaction type + Possible interactions Select how you want to interact with the UI element. Filter interaction type From 7438c6d9c8be83b84fb001131ac174e788b73f08 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 15:14:28 +0200 Subject: [PATCH 07/95] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 9fa7b22caa..a9644d9a6e 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.0 -VERSION_CODE=107 +VERSION_CODE=109 VERSION_NUM=0 \ No newline at end of file From d1064ce37b3205af45f1bc1b0474edc25792f6f4 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 15:16:23 +0200 Subject: [PATCH 08/95] fastlane: update app title to be title case --- fastlane/metadata/android/en-US/title.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt index 4efb85f341..9810cafe1e 100644 --- a/fastlane/metadata/android/en-US/title.txt +++ b/fastlane/metadata/android/en-US/title.txt @@ -1 +1 @@ -Key Mapper & Floating buttons \ No newline at end of file +Key Mapper & Floating Buttons \ No newline at end of file From f3f47a31c9d261dbf4ace3f0b054b981189ea08b Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 15:18:14 +0200 Subject: [PATCH 09/95] chore: update whats new --- app/src/main/assets/whats-new.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/assets/whats-new.txt b/app/src/main/assets/whats-new.txt index 2d54ecfa7c..791c6929e6 100644 --- a/app/src/main/assets/whats-new.txt +++ b/app/src/main/assets/whats-new.txt @@ -1,10 +1,8 @@ -** 3.1 features ** - ⏰ Time constraints. 🔎 Action to interact with app elements. -** 3.0 features ** +== 3.0 features == 🫧 Floating Buttons: you can create custom on-screen buttons to trigger key maps. From 8b4c59b7a77223194d2965bd5a60fccbe677a6fc Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 20:17:07 +0200 Subject: [PATCH 10/95] fix: use correct app title for choosing app element app and single line description --- .../keymapper/actions/uielement/InteractUiElementScreen.kt | 6 ++++-- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt index e2d1a13d70..482b679396 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt @@ -135,7 +135,7 @@ fun InteractUiElementScreen( composable(DEST_SELECT_APP) { ChooseAppScreen( modifier = Modifier.fillMaxSize(), - title = stringResource(R.string.action_interact_ui_element_choose_element_title), + title = stringResource(R.string.action_interact_ui_element_choose_app_title), state = appListState, query = appSearchQuery, onQueryChange = { query -> viewModel.appSearchQuery.update { query } }, @@ -397,7 +397,7 @@ private fun InteractionCountBox( ) Text( - stringResource(R.string.action_interact_ui_element_choose_interaction), + stringResource(R.string.action_interact_ui_element_choose_app_title), style = MaterialTheme.typography.bodyMedium, ) } @@ -474,6 +474,8 @@ private fun SelectedElementSection( value = state.description, onValueChange = onDescriptionChanged, isError = isError, + maxLines = 1, + singleLine = true, supportingText = if (isError) { { Text(stringResource(R.string.error_cant_be_empty)) } } else { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 295eef2f03..2b44780b4e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1111,7 +1111,7 @@ %d element detected %d elements detected - Choose the app to interact with + Choose the app to interact with Record again Choose app element Choose the element you want your key map to interact with. From 9a926b7f8161741e58dd0fe41f5e2fc9782e114e Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 20:18:55 +0200 Subject: [PATCH 11/95] fix: max 1 line for action/constraint shortcut buttons --- .../keymapper/mappings/keymaps/ShortcutRow.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt index d8a147549a..6c157e7ace 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.google.accompanist.drawablepainter.rememberDrawablePainter @@ -97,6 +98,8 @@ private fun ShortcutButton( text = text, style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, ) } } @@ -140,3 +143,24 @@ private fun PreviewDrawable() { } } } + +@Preview +@Composable +private fun PreviewMultipleLines() { + val ctx = LocalContext.current + val icon = ctx.drawable(R.mipmap.ic_launcher_round) + + KeyMapperTheme { + Surface { + ShortcutRow( + shortcuts = setOf( + ShortcutModel( + icon = ComposeIconInfo.Drawable(icon), + text = "Line 1\nLine 2\nLine 3", + data = TriggerKeyShortcut.FINGERPRINT_GESTURE, + ), + ), + ) + } + } +} From 1ea2b26074c70ced263e93bf459015015c8a3af9 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 21:15:43 +0200 Subject: [PATCH 12/95] refactor: move draggable compose code to utils/ui/compose --- .../java/io/github/sds100/keymapper/actions/ActionListItem.kt | 2 +- .../java/io/github/sds100/keymapper/actions/ActionsScreen.kt | 4 ++-- .../keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt | 2 +- .../keymapper/mappings/keymaps/trigger/TriggerScreen.kt | 4 ++-- .../github/sds100/keymapper/sorting/SortBottomSheetContent.kt | 4 ++-- .../{compose/draggable => util/ui/compose}/DragDropState.kt | 2 +- .../{compose/draggable => util/ui/compose}/DraggableItem.kt | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) rename app/src/main/java/io/github/sds100/keymapper/{compose/draggable => util/ui/compose}/DragDropState.kt (99%) rename app/src/main/java/io/github/sds100/keymapper/{compose/draggable => util/ui/compose}/DraggableItem.kt (95%) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt index 0ce11b41af..22152922a2 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt @@ -44,7 +44,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.google.accompanist.drawablepainter.rememberDrawablePainter import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.compose.draggable.DragDropState +import io.github.sds100.keymapper.util.ui.compose.DragDropState import io.github.sds100.keymapper.util.drawable import io.github.sds100.keymapper.util.ui.LinkType import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt index 003235c51e..50acaf1326 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt @@ -39,8 +39,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.compose.draggable.DraggableItem -import io.github.sds100.keymapper.compose.draggable.rememberDragDropState +import io.github.sds100.keymapper.util.ui.compose.DraggableItem +import io.github.sds100.keymapper.util.ui.compose.rememberDragDropState import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel import io.github.sds100.keymapper.mappings.keymaps.ShortcutRow import io.github.sds100.keymapper.system.camera.CameraLens diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt index 46f27f7661..f93f20f4ef 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt @@ -42,7 +42,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.compose.draggable.DragDropState +import io.github.sds100.keymapper.util.ui.compose.DragDropState import io.github.sds100.keymapper.mappings.ClickType import io.github.sds100.keymapper.mappings.FingerprintGestureType import io.github.sds100.keymapper.util.ui.LinkType diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt index c56c550109..aa778801b6 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt @@ -39,8 +39,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.core.layout.WindowHeightSizeClass import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.compose.draggable.DraggableItem -import io.github.sds100.keymapper.compose.draggable.rememberDragDropState +import io.github.sds100.keymapper.util.ui.compose.DraggableItem +import io.github.sds100.keymapper.util.ui.compose.rememberDragDropState import io.github.sds100.keymapper.mappings.ClickType import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel import io.github.sds100.keymapper.mappings.keymaps.ShortcutRow diff --git a/app/src/main/java/io/github/sds100/keymapper/sorting/SortBottomSheetContent.kt b/app/src/main/java/io/github/sds100/keymapper/sorting/SortBottomSheetContent.kt index fb80600a7d..fbfedd964e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/sorting/SortBottomSheetContent.kt +++ b/app/src/main/java/io/github/sds100/keymapper/sorting/SortBottomSheetContent.kt @@ -70,8 +70,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.compose.draggable.DraggableItem -import io.github.sds100.keymapper.compose.draggable.rememberDragDropState +import io.github.sds100.keymapper.util.ui.compose.DraggableItem +import io.github.sds100.keymapper.util.ui.compose.rememberDragDropState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/app/src/main/java/io/github/sds100/keymapper/compose/draggable/DragDropState.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/DragDropState.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/compose/draggable/DragDropState.kt rename to app/src/main/java/io/github/sds100/keymapper/util/ui/compose/DragDropState.kt index 8d6d6a345a..2dfb597db2 100644 --- a/app/src/main/java/io/github/sds100/keymapper/compose/draggable/DragDropState.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/DragDropState.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.compose.draggable +package io.github.sds100.keymapper.util.ui.compose import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Spring diff --git a/app/src/main/java/io/github/sds100/keymapper/compose/draggable/DraggableItem.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/DraggableItem.kt similarity index 95% rename from app/src/main/java/io/github/sds100/keymapper/compose/draggable/DraggableItem.kt rename to app/src/main/java/io/github/sds100/keymapper/util/ui/compose/DraggableItem.kt index 3bdf4f3fd4..b0653c9fb4 100644 --- a/app/src/main/java/io/github/sds100/keymapper/compose/draggable/DraggableItem.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/DraggableItem.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.compose.draggable +package io.github.sds100.keymapper.util.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope From 4d9aa3157e264ba694aee751c7fbc7858572ae67 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 22:31:36 +0200 Subject: [PATCH 13/95] #1663 feat: add action to stop media playback --- .../sds100/keymapper/actions/ActionData.kt | 10 ++++++++ .../actions/ActionDataEntityMapper.kt | 12 ++++++++++ .../sds100/keymapper/actions/ActionId.kt | 2 ++ .../keymapper/actions/ActionUiHelper.kt | 3 +++ .../sds100/keymapper/actions/ActionUtils.kt | 9 +++++++ .../keymapper/actions/CreateActionDelegate.kt | 5 ++++ .../actions/PerformActionsUseCase.kt | 10 +++++++- .../keymapper/mappings/PauseKeyMapsUseCase.kt | 2 +- .../system/media/AndroidMediaAdapter.kt | 24 ++++++++++--------- .../keymapper/system/media/MediaAdapter.kt | 5 ++-- app/src/main/res/values/strings.xml | 10 +++++--- 11 files changed, 74 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt index 3823a2e5f7..cb9ea44868 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt @@ -365,6 +365,11 @@ sealed class ActionData : Comparable { data class Rewind(override val packageName: String) : ControlMediaForApp() { override val id = ActionId.REWIND_PACKAGE } + + @Serializable + data class Stop(override val packageName: String) : ControlMediaForApp() { + override val id = ActionId.STOP_MEDIA_PACKAGE + } } @Serializable @@ -403,6 +408,11 @@ sealed class ActionData : Comparable { data object Rewind : ControlMedia() { override val id = ActionId.REWIND } + + @Serializable + data object Stop : ControlMedia() { + override val id = ActionId.STOP_MEDIA + } } @Serializable diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt index 4133b18d4d..fc60df2b62 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt @@ -356,6 +356,7 @@ object ActionDataEntityMapper { ActionId.PREVIOUS_TRACK_PACKAGE, ActionId.FAST_FORWARD_PACKAGE, ActionId.REWIND_PACKAGE, + ActionId.STOP_MEDIA_PACKAGE, -> { val packageName = entity.extras.getData(ActionEntity.EXTRA_PACKAGE_NAME).valueOrNull() @@ -383,6 +384,9 @@ object ActionDataEntityMapper { ActionId.REWIND_PACKAGE -> ActionData.ControlMediaForApp.Rewind(packageName) + ActionId.STOP_MEDIA_PACKAGE -> + ActionData.ControlMediaForApp.Stop(packageName) + else -> throw Exception("don't know how to create system action for $actionId") } } @@ -461,6 +465,7 @@ object ActionDataEntityMapper { ActionId.PREVIOUS_TRACK -> ActionData.ControlMedia.PreviousTrack ActionId.FAST_FORWARD -> ActionData.ControlMedia.FastForward ActionId.REWIND -> ActionData.ControlMedia.Rewind + ActionId.STOP_MEDIA -> ActionData.ControlMedia.Stop ActionId.GO_BACK -> ActionData.GoBack ActionId.GO_HOME -> ActionData.GoHome @@ -638,6 +643,11 @@ object ActionDataEntityMapper { is ActionData.Url -> data.url is ActionData.Sound -> data.soundUid is ActionData.InteractUiElement -> data.description + is ActionData.ControlMediaForApp.Rewind -> SYSTEM_ACTION_ID_MAP[data.id]!! + is ActionData.ControlMediaForApp.Stop -> SYSTEM_ACTION_ID_MAP[data.id]!! + is ActionData.ControlMedia.Rewind -> SYSTEM_ACTION_ID_MAP[data.id]!! + is ActionData.ControlMedia.Stop -> SYSTEM_ACTION_ID_MAP[data.id]!! + is ActionData.GoBack -> SYSTEM_ACTION_ID_MAP[data.id]!! else -> SYSTEM_ACTION_ID_MAP[data.id]!! } @@ -982,6 +992,8 @@ object ActionDataEntityMapper { ActionId.FAST_FORWARD_PACKAGE to "fast_forward_package", ActionId.REWIND to "rewind", ActionId.REWIND_PACKAGE to "rewind_package", + ActionId.STOP_MEDIA to "stop_media", + ActionId.STOP_MEDIA_PACKAGE to "stop_media_package", ActionId.GO_BACK to "go_back", ActionId.GO_HOME to "go_home", diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt index 011c7f825a..e1fa780d7f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt @@ -79,6 +79,8 @@ enum class ActionId { FAST_FORWARD_PACKAGE, REWIND, REWIND_PACKAGE, + STOP_MEDIA, + STOP_MEDIA_PACKAGE, GO_BACK, GO_HOME, diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt index ccd1fac039..ea942177b8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt @@ -222,6 +222,7 @@ class ActionUiHelper( is ActionData.ControlMediaForApp.PlayPause -> R.string.action_play_pause_media_package_formatted is ActionData.ControlMediaForApp.PreviousTrack -> R.string.action_previous_track_package_formatted is ActionData.ControlMediaForApp.Rewind -> R.string.action_rewind_package_formatted + is ActionData.ControlMediaForApp.Stop -> R.string.action_stop_media_package_formatted } getString(resId, appName) @@ -235,6 +236,7 @@ class ActionUiHelper( is ActionData.ControlMediaForApp.PlayPause -> R.string.action_play_pause_media_package is ActionData.ControlMediaForApp.PreviousTrack -> R.string.action_previous_track_package is ActionData.ControlMediaForApp.Rewind -> R.string.action_rewind_package + is ActionData.ControlMediaForApp.Stop -> R.string.action_stop_media_package } getString(resId) @@ -453,6 +455,7 @@ class ActionUiHelper( ActionData.ControlMedia.PlayPause -> getString(R.string.action_play_pause_media) ActionData.ControlMedia.PreviousTrack -> getString(R.string.action_previous_track) ActionData.ControlMedia.Rewind -> getString(R.string.action_rewind) + ActionData.ControlMedia.Stop -> getString(R.string.action_stop_media) ActionData.CopyText -> getString(R.string.action_text_copy) ActionData.CutText -> getString(R.string.action_text_cut) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt index a2a0d82c73..55073a03fc 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt @@ -55,6 +55,7 @@ import androidx.compose.material.icons.outlined.SkipPrevious import androidx.compose.material.icons.outlined.Splitscreen import androidx.compose.material.icons.outlined.StayCurrentLandscape import androidx.compose.material.icons.outlined.StayCurrentPortrait +import androidx.compose.material.icons.outlined.StopCircle import androidx.compose.material.icons.outlined.Swipe import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material.icons.outlined.ViewArray @@ -182,6 +183,8 @@ object ActionUtils { ActionId.FAST_FORWARD_PACKAGE -> ActionCategory.MEDIA ActionId.REWIND -> ActionCategory.MEDIA ActionId.REWIND_PACKAGE -> ActionCategory.MEDIA + ActionId.STOP_MEDIA -> ActionCategory.MEDIA + ActionId.STOP_MEDIA_PACKAGE -> ActionCategory.MEDIA ActionId.GO_BACK -> ActionCategory.NAVIGATION ActionId.GO_HOME -> ActionCategory.NAVIGATION @@ -291,6 +294,8 @@ object ActionUtils { ActionId.FAST_FORWARD_PACKAGE -> R.string.action_fast_forward_package ActionId.REWIND -> R.string.action_rewind ActionId.REWIND_PACKAGE -> R.string.action_rewind_package + ActionId.STOP_MEDIA -> R.string.action_stop_media + ActionId.STOP_MEDIA_PACKAGE -> R.string.action_stop_media_package ActionId.GO_BACK -> R.string.action_go_back ActionId.GO_HOME -> R.string.action_go_home ActionId.OPEN_RECENTS -> R.string.action_open_recents @@ -404,6 +409,8 @@ object ActionUtils { ActionId.FAST_FORWARD_PACKAGE -> R.drawable.ic_outline_fast_forward_24 ActionId.REWIND -> R.drawable.ic_outline_fast_rewind_24 ActionId.REWIND_PACKAGE -> R.drawable.ic_outline_fast_rewind_24 + ActionId.STOP_MEDIA -> R.drawable.ic_outline_pause_24 + ActionId.STOP_MEDIA_PACKAGE -> R.drawable.ic_outline_pause_24 ActionId.GO_BACK -> R.drawable.ic_baseline_arrow_back_24 ActionId.GO_HOME -> R.drawable.ic_outline_home_24 ActionId.OPEN_RECENTS -> null @@ -721,6 +728,8 @@ object ActionUtils { ActionId.FAST_FORWARD_PACKAGE -> Icons.Outlined.FastForward ActionId.REWIND -> Icons.Outlined.FastRewind ActionId.REWIND_PACKAGE -> Icons.Outlined.FastRewind + ActionId.STOP_MEDIA -> Icons.Outlined.StopCircle + ActionId.STOP_MEDIA_PACKAGE -> Icons.Outlined.StopCircle ActionId.GO_BACK -> Icons.AutoMirrored.Outlined.ArrowBack ActionId.GO_HOME -> Icons.Outlined.Home ActionId.OPEN_RECENTS -> Icons.Outlined.ViewArray diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt b/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt index 649ef42bd7..bad924c888 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt @@ -181,6 +181,7 @@ class CreateActionDelegate( ActionId.PREVIOUS_TRACK_PACKAGE, ActionId.FAST_FORWARD_PACKAGE, ActionId.REWIND_PACKAGE, + ActionId.STOP_MEDIA_PACKAGE, -> { val packageName = navigate( @@ -211,6 +212,9 @@ class CreateActionDelegate( ActionId.REWIND_PACKAGE -> ActionData.ControlMediaForApp.Rewind(packageName) + ActionId.STOP_MEDIA_PACKAGE -> + ActionData.ControlMediaForApp.Stop(packageName) + else -> throw Exception("don't know how to create action for $actionId") } @@ -729,6 +733,7 @@ class CreateActionDelegate( ActionId.PREVIOUS_TRACK -> return ActionData.ControlMedia.PreviousTrack ActionId.FAST_FORWARD -> return ActionData.ControlMedia.FastForward ActionId.REWIND -> return ActionData.ControlMedia.Rewind + ActionId.STOP_MEDIA -> return ActionData.ControlMedia.Stop ActionId.GO_BACK -> return ActionData.GoBack ActionId.GO_HOME -> return ActionData.GoHome diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt index 80c6ea900d..551a3b5154 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt @@ -223,6 +223,10 @@ class PerformActionsUseCaseImpl( result = mediaAdapter.rewind(action.packageName) } + is ActionData.ControlMediaForApp.Stop -> { + result = mediaAdapter.stop(action.packageName) + } + is ActionData.Rotation.CycleRotations -> { result = displayAdapter.disableAutoRotate().then { val currentOrientation = displayAdapter.cachedOrientation @@ -340,7 +344,7 @@ class PerformActionsUseCaseImpl( is ActionData.Sound -> { result = soundsManager.getSound(action.soundUid).then { file -> - mediaAdapter.playSoundFile(file.uri, VolumeStream.ACCESSIBILITY) + mediaAdapter.playFile(file.uri, VolumeStream.ACCESSIBILITY) } } @@ -547,6 +551,10 @@ class PerformActionsUseCaseImpl( result = mediaAdapter.rewind() } + is ActionData.ControlMedia.Stop -> { + result = mediaAdapter.stop() + } + is ActionData.GoBack -> { result = accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt index 3214f13d19..82c4dc88fe 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt @@ -21,7 +21,7 @@ class PauseKeyMapsUseCaseImpl( override fun pause() { preferenceRepository.set(Keys.mappingsPaused, true) - mediaAdapter.stopMedia() + mediaAdapter.stopFileMedia() Timber.d("Pause mappings") } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt index 7fc9bebaf7..7ca1d7cba6 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt @@ -82,6 +82,18 @@ class AndroidMediaAdapter(context: Context, coroutineScope: CoroutineScope) : Me override fun nextTrack(packageName: String?): Result<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_NEXT, packageName) + override fun stop(packageName: String?): Result<*> = sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STOP, packageName) + + override fun stopFileMedia(): Result<*> { + synchronized(mediaPlayerLock) { + mediaPlayer?.stop() + mediaPlayer?.release() + mediaPlayer = null + } + + return Success(Unit) + } + override fun getActiveMediaSessionPackages(): List { return activeMediaSessions.value .filter { it.playbackState?.state == PlaybackState.STATE_PLAYING } @@ -105,7 +117,7 @@ class AndroidMediaAdapter(context: Context, coroutineScope: CoroutineScope) : Me return audioVolumeControlStreams } - override fun playSoundFile(uri: String, stream: VolumeStream): Result<*> { + override fun playFile(uri: String, stream: VolumeStream): Result<*> { try { synchronized(mediaPlayerLock) { mediaPlayer?.stop() @@ -147,16 +159,6 @@ class AndroidMediaAdapter(context: Context, coroutineScope: CoroutineScope) : Me } } - override fun stopMedia(): Result<*> { - synchronized(mediaPlayerLock) { - mediaPlayer?.stop() - mediaPlayer?.release() - mediaPlayer = null - } - - return Success(Unit) - } - fun onActiveMediaSessionChange(mediaSessions: List) { activeMediaSessions.update { mediaSessions } } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt index d990c9afe5..4c778078af 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt @@ -35,7 +35,8 @@ interface MediaAdapter { fun playPause(packageName: String? = null): Result<*> fun previousTrack(packageName: String? = null): Result<*> fun nextTrack(packageName: String? = null): Result<*> + fun stop(packageName: String? = null): Result<*> - fun playSoundFile(uri: String, stream: VolumeStream): Result<*> - fun stopMedia(): Result<*> + fun playFile(uri: String, stream: VolumeStream): Result<*> + fun stopFileMedia(): Result<*> } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2b44780b4e..f10c306f79 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -981,11 +981,11 @@ Next track Next track for an app - Next track for %s> + Next track for %s Previous track Previous track for an app - Previous track for %s> + Previous track for %s Fast forward Fast forward for an app @@ -994,9 +994,13 @@ Rewind Rewind for an app - Rewind for %s> + Rewind for %s Not all media apps support rewinding. E.g Google Play Music. + Stop media + Stop media for an app + Stop media for %s + Go back Go home Open recents From 0279bbd04eb56ab8599cb813010450a2ef87fe3d Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 22:33:22 +0200 Subject: [PATCH 14/95] chore: create Cursor IDE rule for creating an action --- .cursor/rules/create-action.mdc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .cursor/rules/create-action.mdc diff --git a/.cursor/rules/create-action.mdc b/.cursor/rules/create-action.mdc new file mode 100644 index 0000000000..e8b352f9db --- /dev/null +++ b/.cursor/rules/create-action.mdc @@ -0,0 +1,27 @@ +--- +description: +globs: +alwaysApply: false +--- +[ActionEntity.kt](mdc:app/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt) [ActionDataEntityMapper.kt](mdc:app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt) [ActionData.kt](mdc:app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt) [ActionId.kt](mdc:app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt) [PerformActionsUseCase.kt](mdc:app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt) [ActionUtils.kt](mdc:app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt) [strings.xml](mdc:app/src/main/res/values/strings.xml) [ActionUiHelper.kt](mdc:app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt) [CreateActionDelegate.kt](mdc:app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt) + + +When you create an action you must follow these steps: + +0. Ask me whether the action is editable. +1. Create a new id in ActionId +2. Create a new ActionData +3. Map the data to and from an entity in ActionDataEntityMapper +4. Give the action a category in ActionUtils +5. If the action is editable then add it to the isEditable function in ActionUtils +6. Create a title for the action in strings.xml +7. Give the action a title and icon in ActionUtils. Only create a compose Icon. Ignore the drawable one. +8. Give the action a title in ActionUiHelper +9. Stub out the action in PerformActionsUseCase +10. Handle creating the action in CreateActionDelegate + +Important things to remember: + +- Do not delete any existing code for other actions. +- Follow the naming of existing code and strings and do not change them. +- Add code near existing code for similar actions. \ No newline at end of file From 334e624cffede2f70568f5553a4d4c83c51cc8c6 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 22:33:29 +0200 Subject: [PATCH 15/95] chore: create Cursor IDE rule for jetpack compose code --- .cursor/rules/jetpack-compose.mdc | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .cursor/rules/jetpack-compose.mdc diff --git a/.cursor/rules/jetpack-compose.mdc b/.cursor/rules/jetpack-compose.mdc new file mode 100644 index 0000000000..ef3b89eb3a --- /dev/null +++ b/.cursor/rules/jetpack-compose.mdc @@ -0,0 +1,71 @@ +--- +description: +globs: +alwaysApply: false +--- + +# Android Native (Jetpack Compose) + +# Android Jetpack Compose .cursorrules + +## Flexibility Notice +**Note:** This is a recommended project structure, but be flexible and adapt to existing project structures. Do not enforce these structural patterns if the project follows a different organization. Focus on maintaining consistency with the existing project architecture while applying Jetpack Compose best practices. + +## Project Architecture and Best Practices +```kotlin +val androidJetpackComposeBestPractices = listOf( + "Adapt to existing project architecture while maintaining clean code principles", + "Follow Material Design 3 guidelines and components", + "Implement clean architecture with domain, data, and presentation layers", + "Use Kotlin coroutines and Flow for asynchronous operations", + "Implement dependency injection using Hilt", + "Follow unidirectional data flow with ViewModel and UI State", + "Use Compose navigation for screen management", + "Implement proper state hoisting and composition" +) +``` + +## Folder Structure +**Note:** This is a reference structure. Adapt to the project's existing organization. +```plaintext +app/ + src/ + main/ + java/io/github/sds100/keymapper + actions/ + constraints/ + groups/ + home/ + mappings/ + keymaps/ + utils/ + res/ + values/ + drawable/ + mipmap/ + test/ + androidTest/ +``` + +## Compose UI Guidelines +1. Use `remember` and `derivedStateOf` appropriately +2. Implement proper recomposition optimization +3. Use proper Compose modifiers ordering +4. Follow composable function naming conventions +5. Implement proper preview annotations +6. Use proper state management with `MutableState` +7. Implement proper error handling and loading states +8. Use proper theming with `MaterialTheme` +9. Follow accessibility guidelines +10. Implement proper animation patterns + +## Performance Guidelines +1. Minimize recomposition using proper keys +2. Use proper lazy loading with `LazyColumn` and `LazyRow` +3. Implement efficient image loading +4. Use proper state management to prevent unnecessary updates +5. Follow proper lifecycle awareness +6. Implement proper memory management +7. Use proper background processing + + From 6375136e6ceffe261ab2a448845cafdb9e73a555 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 23:43:40 +0200 Subject: [PATCH 16/95] #1663 feat: add action to step forward/backward media --- CHANGELOG.md | 1 + .../sds100/keymapper/actions/ActionData.kt | 20 +++++++++++++++++++ .../actions/ActionDataEntityMapper.kt | 14 +++++++++++++ .../sds100/keymapper/actions/ActionId.kt | 4 ++++ .../keymapper/actions/ActionUiHelper.kt | 6 ++++++ .../sds100/keymapper/actions/ActionUtils.kt | 18 +++++++++++++++++ .../keymapper/actions/CreateActionDelegate.kt | 10 ++++++++++ .../actions/PerformActionsUseCase.kt | 16 +++++++++++++++ .../system/media/AndroidMediaAdapter.kt | 16 +++++++++++++++ .../keymapper/system/media/MediaAdapter.kt | 2 ++ app/src/main/res/values/strings.xml | 8 ++++++++ 11 files changed, 115 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 187276ce10..237cac90f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - #699 Time constraints ⏰ - #257 Action to interact with user interface elements inside other apps. +- #1663 Actions to stop, step forward, and step backward playing media. ## Changed diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt index cb9ea44868..56dfd47265 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt @@ -370,6 +370,16 @@ sealed class ActionData : Comparable { data class Stop(override val packageName: String) : ControlMediaForApp() { override val id = ActionId.STOP_MEDIA_PACKAGE } + + @Serializable + data class StepForward(override val packageName: String) : ControlMediaForApp() { + override val id = ActionId.STEP_FORWARD_PACKAGE + } + + @Serializable + data class StepBackward(override val packageName: String) : ControlMediaForApp() { + override val id = ActionId.STEP_BACKWARD_PACKAGE + } } @Serializable @@ -413,6 +423,16 @@ sealed class ActionData : Comparable { data object Stop : ControlMedia() { override val id = ActionId.STOP_MEDIA } + + @Serializable + data object StepForward : ControlMedia() { + override val id = ActionId.STEP_FORWARD + } + + @Serializable + data object StepBackward : ControlMedia() { + override val id = ActionId.STEP_BACKWARD + } } @Serializable diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt index fc60df2b62..8eedce9b51 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt @@ -357,6 +357,8 @@ object ActionDataEntityMapper { ActionId.FAST_FORWARD_PACKAGE, ActionId.REWIND_PACKAGE, ActionId.STOP_MEDIA_PACKAGE, + ActionId.STEP_FORWARD_PACKAGE, + ActionId.STEP_BACKWARD_PACKAGE, -> { val packageName = entity.extras.getData(ActionEntity.EXTRA_PACKAGE_NAME).valueOrNull() @@ -387,6 +389,12 @@ object ActionDataEntityMapper { ActionId.STOP_MEDIA_PACKAGE -> ActionData.ControlMediaForApp.Stop(packageName) + ActionId.STEP_FORWARD_PACKAGE -> + ActionData.ControlMediaForApp.StepForward(packageName) + + ActionId.STEP_BACKWARD_PACKAGE -> + ActionData.ControlMediaForApp.StepBackward(packageName) + else -> throw Exception("don't know how to create system action for $actionId") } } @@ -466,6 +474,8 @@ object ActionDataEntityMapper { ActionId.FAST_FORWARD -> ActionData.ControlMedia.FastForward ActionId.REWIND -> ActionData.ControlMedia.Rewind ActionId.STOP_MEDIA -> ActionData.ControlMedia.Stop + ActionId.STEP_FORWARD -> ActionData.ControlMedia.StepForward + ActionId.STEP_BACKWARD -> ActionData.ControlMedia.StepBackward ActionId.GO_BACK -> ActionData.GoBack ActionId.GO_HOME -> ActionData.GoHome @@ -994,6 +1004,10 @@ object ActionDataEntityMapper { ActionId.REWIND_PACKAGE to "rewind_package", ActionId.STOP_MEDIA to "stop_media", ActionId.STOP_MEDIA_PACKAGE to "stop_media_package", + ActionId.STEP_FORWARD to "step_forward", + ActionId.STEP_FORWARD_PACKAGE to "step_forward_package", + ActionId.STEP_BACKWARD to "step_backward", + ActionId.STEP_BACKWARD_PACKAGE to "step_backward_package", ActionId.GO_BACK to "go_back", ActionId.GO_HOME to "go_home", diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt index e1fa780d7f..5b93ee03a7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt @@ -81,6 +81,10 @@ enum class ActionId { REWIND_PACKAGE, STOP_MEDIA, STOP_MEDIA_PACKAGE, + STEP_FORWARD, + STEP_FORWARD_PACKAGE, + STEP_BACKWARD, + STEP_BACKWARD_PACKAGE, GO_BACK, GO_HOME, diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt index ea942177b8..08c7b3c5c9 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt @@ -223,6 +223,8 @@ class ActionUiHelper( is ActionData.ControlMediaForApp.PreviousTrack -> R.string.action_previous_track_package_formatted is ActionData.ControlMediaForApp.Rewind -> R.string.action_rewind_package_formatted is ActionData.ControlMediaForApp.Stop -> R.string.action_stop_media_package_formatted + is ActionData.ControlMediaForApp.StepForward -> R.string.action_step_forward_media_package_formatted + is ActionData.ControlMediaForApp.StepBackward -> R.string.action_step_backward_media_package_formatted } getString(resId, appName) @@ -237,6 +239,8 @@ class ActionUiHelper( is ActionData.ControlMediaForApp.PreviousTrack -> R.string.action_previous_track_package is ActionData.ControlMediaForApp.Rewind -> R.string.action_rewind_package is ActionData.ControlMediaForApp.Stop -> R.string.action_stop_media_package + is ActionData.ControlMediaForApp.StepForward -> R.string.action_step_forward_media_package + is ActionData.ControlMediaForApp.StepBackward -> R.string.action_step_backward_media_package } getString(resId) @@ -456,6 +460,8 @@ class ActionUiHelper( ActionData.ControlMedia.PreviousTrack -> getString(R.string.action_previous_track) ActionData.ControlMedia.Rewind -> getString(R.string.action_rewind) ActionData.ControlMedia.Stop -> getString(R.string.action_stop_media) + ActionData.ControlMedia.StepForward -> getString(R.string.action_step_forward_media) + ActionData.ControlMedia.StepBackward -> getString(R.string.action_step_backward_media) ActionData.CopyText -> getString(R.string.action_text_copy) ActionData.CutText -> getString(R.string.action_text_cut) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt index 55073a03fc..7cff7aeb8d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt @@ -30,6 +30,7 @@ import androidx.compose.material.icons.outlined.FastForward import androidx.compose.material.icons.outlined.FastRewind import androidx.compose.material.icons.outlined.FlashlightOff import androidx.compose.material.icons.outlined.FlashlightOn +import androidx.compose.material.icons.outlined.Forward30 import androidx.compose.material.icons.outlined.Fullscreen import androidx.compose.material.icons.outlined.Home import androidx.compose.material.icons.outlined.Http @@ -45,6 +46,7 @@ import androidx.compose.material.icons.outlined.PhonelinkRing import androidx.compose.material.icons.outlined.Pinch import androidx.compose.material.icons.outlined.PlayArrow import androidx.compose.material.icons.outlined.PowerSettingsNew +import androidx.compose.material.icons.outlined.Replay30 import androidx.compose.material.icons.outlined.ScreenLockRotation import androidx.compose.material.icons.outlined.ScreenRotation import androidx.compose.material.icons.outlined.Settings @@ -185,6 +187,10 @@ object ActionUtils { ActionId.REWIND_PACKAGE -> ActionCategory.MEDIA ActionId.STOP_MEDIA -> ActionCategory.MEDIA ActionId.STOP_MEDIA_PACKAGE -> ActionCategory.MEDIA + ActionId.STEP_FORWARD -> ActionCategory.MEDIA + ActionId.STEP_FORWARD_PACKAGE -> ActionCategory.MEDIA + ActionId.STEP_BACKWARD -> ActionCategory.MEDIA + ActionId.STEP_BACKWARD_PACKAGE -> ActionCategory.MEDIA ActionId.GO_BACK -> ActionCategory.NAVIGATION ActionId.GO_HOME -> ActionCategory.NAVIGATION @@ -296,6 +302,10 @@ object ActionUtils { ActionId.REWIND_PACKAGE -> R.string.action_rewind_package ActionId.STOP_MEDIA -> R.string.action_stop_media ActionId.STOP_MEDIA_PACKAGE -> R.string.action_stop_media_package + ActionId.STEP_FORWARD -> R.string.action_step_forward_media + ActionId.STEP_FORWARD_PACKAGE -> R.string.action_step_forward_media_package + ActionId.STEP_BACKWARD -> R.string.action_step_backward_media + ActionId.STEP_BACKWARD_PACKAGE -> R.string.action_step_backward_media_package ActionId.GO_BACK -> R.string.action_go_back ActionId.GO_HOME -> R.string.action_go_home ActionId.OPEN_RECENTS -> R.string.action_open_recents @@ -411,6 +421,10 @@ object ActionUtils { ActionId.REWIND_PACKAGE -> R.drawable.ic_outline_fast_rewind_24 ActionId.STOP_MEDIA -> R.drawable.ic_outline_pause_24 ActionId.STOP_MEDIA_PACKAGE -> R.drawable.ic_outline_pause_24 + ActionId.STEP_FORWARD -> null + ActionId.STEP_FORWARD_PACKAGE -> null + ActionId.STEP_BACKWARD -> null + ActionId.STEP_BACKWARD_PACKAGE -> null ActionId.GO_BACK -> R.drawable.ic_baseline_arrow_back_24 ActionId.GO_HOME -> R.drawable.ic_outline_home_24 ActionId.OPEN_RECENTS -> null @@ -730,6 +744,10 @@ object ActionUtils { ActionId.REWIND_PACKAGE -> Icons.Outlined.FastRewind ActionId.STOP_MEDIA -> Icons.Outlined.StopCircle ActionId.STOP_MEDIA_PACKAGE -> Icons.Outlined.StopCircle + ActionId.STEP_FORWARD -> Icons.Outlined.Forward30 + ActionId.STEP_FORWARD_PACKAGE -> Icons.Outlined.Forward30 + ActionId.STEP_BACKWARD -> Icons.Outlined.Replay30 + ActionId.STEP_BACKWARD_PACKAGE -> Icons.Outlined.Replay30 ActionId.GO_BACK -> Icons.AutoMirrored.Outlined.ArrowBack ActionId.GO_HOME -> Icons.Outlined.Home ActionId.OPEN_RECENTS -> Icons.Outlined.ViewArray diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt b/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt index bad924c888..0e1a52efe1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt @@ -182,6 +182,8 @@ class CreateActionDelegate( ActionId.FAST_FORWARD_PACKAGE, ActionId.REWIND_PACKAGE, ActionId.STOP_MEDIA_PACKAGE, + ActionId.STEP_FORWARD_PACKAGE, + ActionId.STEP_BACKWARD_PACKAGE, -> { val packageName = navigate( @@ -215,6 +217,12 @@ class CreateActionDelegate( ActionId.STOP_MEDIA_PACKAGE -> ActionData.ControlMediaForApp.Stop(packageName) + ActionId.STEP_FORWARD_PACKAGE -> + ActionData.ControlMediaForApp.StepForward(packageName) + + ActionId.STEP_BACKWARD_PACKAGE -> + ActionData.ControlMediaForApp.StepBackward(packageName) + else -> throw Exception("don't know how to create action for $actionId") } @@ -734,6 +742,8 @@ class CreateActionDelegate( ActionId.FAST_FORWARD -> return ActionData.ControlMedia.FastForward ActionId.REWIND -> return ActionData.ControlMedia.Rewind ActionId.STOP_MEDIA -> return ActionData.ControlMedia.Stop + ActionId.STEP_FORWARD -> return ActionData.ControlMedia.StepForward + ActionId.STEP_BACKWARD -> return ActionData.ControlMedia.StepBackward ActionId.GO_BACK -> return ActionData.GoBack ActionId.GO_HOME -> return ActionData.GoHome diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt index 551a3b5154..80be40ea79 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt @@ -227,6 +227,14 @@ class PerformActionsUseCaseImpl( result = mediaAdapter.stop(action.packageName) } + is ActionData.ControlMediaForApp.StepForward -> { + result = mediaAdapter.stepForward(action.packageName) + } + + is ActionData.ControlMediaForApp.StepBackward -> { + result = mediaAdapter.stepBackward(action.packageName) + } + is ActionData.Rotation.CycleRotations -> { result = displayAdapter.disableAutoRotate().then { val currentOrientation = displayAdapter.cachedOrientation @@ -555,6 +563,14 @@ class PerformActionsUseCaseImpl( result = mediaAdapter.stop() } + is ActionData.ControlMedia.StepForward -> { + result = mediaAdapter.stepForward() + } + + is ActionData.ControlMedia.StepBackward -> { + result = mediaAdapter.stepBackward() + } + is ActionData.GoBack -> { result = accessibilityService.doGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt index 7ca1d7cba6..3886863a38 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/media/AndroidMediaAdapter.kt @@ -94,6 +94,22 @@ class AndroidMediaAdapter(context: Context, coroutineScope: CoroutineScope) : Me return Success(Unit) } + override fun stepForward(packageName: String?): Result<*> { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STEP_FORWARD, packageName) + } else { + return Error.SdkVersionTooLow(Build.VERSION_CODES.M) + } + } + + override fun stepBackward(packageName: String?): Result<*> { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + sendMediaKeyEvent(KeyEvent.KEYCODE_MEDIA_STEP_BACKWARD, packageName) + } else { + return Error.SdkVersionTooLow(Build.VERSION_CODES.M) + } + } + override fun getActiveMediaSessionPackages(): List { return activeMediaSessions.value .filter { it.playbackState?.state == PlaybackState.STATE_PLAYING } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt index 4c778078af..c0a4858e81 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/media/MediaAdapter.kt @@ -36,6 +36,8 @@ interface MediaAdapter { fun previousTrack(packageName: String? = null): Result<*> fun nextTrack(packageName: String? = null): Result<*> fun stop(packageName: String? = null): Result<*> + fun stepForward(packageName: String? = null): Result<*> + fun stepBackward(packageName: String? = null): Result<*> fun playFile(uri: String, stream: VolumeStream): Result<*> fun stopFileMedia(): Result<*> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f10c306f79..2dc876b22f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1001,6 +1001,14 @@ Stop media for an app Stop media for %s + Step media forward + Step media forward for an app + Step media forward for %s + + Step media backward + Step media backward for an app + Step media backward for %s + Go back Go home Open recents From f29c29ec8095271870be3ce79c3ed36d1a1b2a4b Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 7 May 2025 23:49:31 +0200 Subject: [PATCH 17/95] fix changelog --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 237cac90f7..cebf36aee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,6 @@ - Rename tap screen actions inside key maps. -## Changed - -- Rename tap screen actions inside key maps. - ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) #### 28 April 2025 From e1b46ce33c645c29f0f67925f772cdf063519c32 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 8 May 2025 12:15:55 +0200 Subject: [PATCH 18/95] #1683 fix: Key event actions do not work in some apps due to missing source field --- CHANGELOG.md | 4 +++ .../io/github/sds100/keymapper/UseCases.kt | 4 +-- .../shizuku/ShizukuInputEventInjector.kt | 14 ++-------- .../system/inputevents/InputEventInjector.kt | 28 +++++++++++++++++++ .../inputmethod/ImeInputEventInjector.kt | 25 ++++------------- app/version.properties | 2 +- 6 files changed, 42 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cebf36aee3..32babf419d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ - Rename tap screen actions inside key maps. +## Bug fixes + +- #1683 key event actions work in Minecraft and other apps again. + ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) #### 28 April 2025 diff --git a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt index caee6e48b0..260a3eca4a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt +++ b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt @@ -142,7 +142,7 @@ object UseCases { ServiceLocator.intentAdapter(ctx), getActionError(ctx), keyMapperImeMessenger(ctx, keyEventRelayService), - ShizukuInputEventInjector(coroutineScope = ServiceLocator.appCoroutineScope(ctx)), + ShizukuInputEventInjector(), ServiceLocator.packageManagerAdapter(ctx), ServiceLocator.appShortcutAdapter(ctx), ServiceLocator.popupMessageAdapter(ctx), @@ -179,7 +179,7 @@ object UseCases { ServiceLocator.audioAdapter(ctx), keyMapperImeMessenger(ctx, keyEventRelayService), service, - ShizukuInputEventInjector(ServiceLocator.appCoroutineScope(ctx)), + ShizukuInputEventInjector(), ServiceLocator.popupMessageAdapter(ctx), ServiceLocator.permissionAdapter(ctx), ServiceLocator.resourceProvider(ctx), diff --git a/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt b/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt index 9608b58f1c..2b812d21c0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt +++ b/app/src/main/java/io/github/sds100/keymapper/shizuku/ShizukuInputEventInjector.kt @@ -8,7 +8,6 @@ import android.view.KeyEvent import io.github.sds100.keymapper.system.inputevents.InputEventInjector import io.github.sds100.keymapper.system.inputmethod.InputKeyModel import io.github.sds100.keymapper.util.InputEventType -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import rikka.shizuku.ShizukuBinderWrapper @@ -16,7 +15,7 @@ import rikka.shizuku.SystemServiceHelper import timber.log.Timber @SuppressLint("PrivateApi") -class ShizukuInputEventInjector(private val coroutineScope: CoroutineScope) : InputEventInjector { +class ShizukuInputEventInjector : InputEventInjector { companion object { // private const val INJECT_INPUT_EVENT_MODE_ASYNC = 0 @@ -40,16 +39,7 @@ class ShizukuInputEventInjector(private val coroutineScope: CoroutineScope) : In val eventTime = SystemClock.uptimeMillis() - val keyEvent = KeyEvent( - eventTime, - eventTime, - action, - model.keyCode, - model.repeat, - model.metaState, - model.deviceId, - model.scanCode, - ) + val keyEvent = createInjectedKeyEvent(eventTime, action, model) withContext(Dispatchers.IO) { // MUST wait for the application to finish processing the event before sending the next one. diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt index a126bac316..f2780cc903 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt @@ -1,7 +1,35 @@ package io.github.sds100.keymapper.system.inputevents +import android.view.InputDevice +import android.view.KeyEvent import io.github.sds100.keymapper.system.inputmethod.InputKeyModel interface InputEventInjector { suspend fun inputKeyEvent(model: InputKeyModel) + + fun createInjectedKeyEvent( + eventTime: Long, + action: Int, + model: InputKeyModel, + ): KeyEvent { + val source = when { + InputEventUtils.isDpadKeyCode(model.keyCode) -> InputDevice.SOURCE_DPAD + KeyEvent.isGamepadButton(model.keyCode) -> InputDevice.SOURCE_GAMEPAD + else -> InputDevice.SOURCE_KEYBOARD + } + + return KeyEvent( + eventTime, + eventTime, + action, + model.keyCode, + model.repeat, + model.metaState, + model.deviceId, + model.scanCode, + 0, + // See issue #1683. Some apps ignore key events which do not have a source. + source, + ) + } } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ImeInputEventInjector.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ImeInputEventInjector.kt index 756acde8df..e5ce73abdd 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ImeInputEventInjector.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/ImeInputEventInjector.kt @@ -82,7 +82,7 @@ class ImeInputEventInjectorImpl( val eventTime = SystemClock.uptimeMillis() - val keyEvent = createKeyEvent(eventTime, action, model) + val keyEvent = createInjectedKeyEvent(eventTime, action, model) putExtra(KEY_MAPPER_INPUT_METHOD_EXTRA_KEY_EVENT, keyEvent) @@ -90,34 +90,19 @@ class ImeInputEventInjectorImpl( } } - private fun createKeyEvent( - eventTime: Long, - action: Int, - model: InputKeyModel, - ): KeyEvent = KeyEvent( - eventTime, - eventTime, - action, - model.keyCode, - model.repeat, - model.metaState, - model.deviceId, - model.scanCode, - ) - private fun inputKeyEventRelayService(model: InputKeyModel, imePackageName: String) { val eventTime = SystemClock.uptimeMillis() when (model.inputType) { InputEventType.DOWN_UP -> { - val downKeyEvent = createKeyEvent(eventTime, KeyEvent.ACTION_DOWN, model) + val downKeyEvent = createInjectedKeyEvent(eventTime, KeyEvent.ACTION_DOWN, model) keyEventRelayService.sendKeyEvent( downKeyEvent, imePackageName, KeyEventRelayService.CALLBACK_ID_INPUT_METHOD, ) - val upKeyEvent = createKeyEvent(eventTime, KeyEvent.ACTION_UP, model) + val upKeyEvent = createInjectedKeyEvent(eventTime, KeyEvent.ACTION_UP, model) keyEventRelayService.sendKeyEvent( upKeyEvent, imePackageName, @@ -126,7 +111,7 @@ class ImeInputEventInjectorImpl( } InputEventType.DOWN -> { - val downKeyEvent = createKeyEvent(eventTime, KeyEvent.ACTION_DOWN, model) + val downKeyEvent = createInjectedKeyEvent(eventTime, KeyEvent.ACTION_DOWN, model) keyEventRelayService.sendKeyEvent( downKeyEvent, imePackageName, @@ -135,7 +120,7 @@ class ImeInputEventInjectorImpl( } InputEventType.UP -> { - val upKeyEvent = createKeyEvent(eventTime, KeyEvent.ACTION_UP, model) + val upKeyEvent = createInjectedKeyEvent(eventTime, KeyEvent.ACTION_UP, model) keyEventRelayService.sendKeyEvent( upKeyEvent, imePackageName, diff --git a/app/version.properties b/app/version.properties index a9644d9a6e..dad6c30b8f 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.0 -VERSION_CODE=109 +VERSION_CODE=110 VERSION_NUM=0 \ No newline at end of file From ac9dc914ebe1cc4957f9dc8168c9fea1554139f2 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 8 May 2025 12:21:03 +0200 Subject: [PATCH 19/95] chore: update whats new --- app/src/main/assets/whats-new.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/assets/whats-new.txt b/app/src/main/assets/whats-new.txt index 791c6929e6..d1de1ca498 100644 --- a/app/src/main/assets/whats-new.txt +++ b/app/src/main/assets/whats-new.txt @@ -2,6 +2,8 @@ 🔎 Action to interact with app elements. +Fix for Minecraft 1.21.80. + == 3.0 features == 🫧 Floating Buttons: you can create custom on-screen buttons to trigger key maps. From 7b36280fa9ccd3a99c909c0d8c5bac7544c248f2 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 8 May 2025 12:21:50 +0200 Subject: [PATCH 20/95] fix style --- .../java/io/github/sds100/keymapper/actions/ActionListItem.kt | 2 +- .../java/io/github/sds100/keymapper/actions/ActionsScreen.kt | 4 ++-- .../keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt | 2 +- .../keymapper/mappings/keymaps/trigger/TriggerScreen.kt | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt index 22152922a2..92276cfeb8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionListItem.kt @@ -44,10 +44,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.google.accompanist.drawablepainter.rememberDrawablePainter import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.util.ui.compose.DragDropState import io.github.sds100.keymapper.util.drawable import io.github.sds100.keymapper.util.ui.LinkType import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo +import io.github.sds100.keymapper.util.ui.compose.DragDropState @Composable fun ActionListItem( diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt index 50acaf1326..6e4126b876 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt @@ -39,14 +39,14 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.util.ui.compose.DraggableItem -import io.github.sds100.keymapper.util.ui.compose.rememberDragDropState import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel import io.github.sds100.keymapper.mappings.keymaps.ShortcutRow import io.github.sds100.keymapper.system.camera.CameraLens import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.ui.LinkType import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo +import io.github.sds100.keymapper.util.ui.compose.DraggableItem +import io.github.sds100.keymapper.util.ui.compose.rememberDragDropState import kotlinx.coroutines.flow.update @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt index f93f20f4ef..4b44d2ea23 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt @@ -42,10 +42,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.util.ui.compose.DragDropState import io.github.sds100.keymapper.mappings.ClickType import io.github.sds100.keymapper.mappings.FingerprintGestureType import io.github.sds100.keymapper.util.ui.LinkType +import io.github.sds100.keymapper.util.ui.compose.DragDropState @Composable fun TriggerKeyListItem( diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt index aa778801b6..f20a2ad284 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt @@ -39,15 +39,15 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.core.layout.WindowHeightSizeClass import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.util.ui.compose.DraggableItem -import io.github.sds100.keymapper.util.ui.compose.rememberDragDropState import io.github.sds100.keymapper.mappings.ClickType import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel import io.github.sds100.keymapper.mappings.keymaps.ShortcutRow import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.ui.LinkType import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo +import io.github.sds100.keymapper.util.ui.compose.DraggableItem import io.github.sds100.keymapper.util.ui.compose.RadioButtonText +import io.github.sds100.keymapper.util.ui.compose.rememberDragDropState @OptIn(ExperimentalMaterial3Api::class) @Composable From 72f0cd884849ac0ba2555bf08544057ae9ac723d Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 8 May 2025 13:10:20 +0200 Subject: [PATCH 21/95] #1683 when reinjecting/imitating a key event set the same source as the real key event --- .../actions/PerformActionsUseCase.kt | 9 +++++ .../keymaps/detection/DetectKeyMapsUseCase.kt | 4 ++ .../DetectScreenOffKeyEventsController.kt | 3 ++ .../detection/DpadMotionEventTracker.kt | 2 + .../accessibility/MyAccessibilityService.kt | 2 + .../system/inputevents/InputEventInjector.kt | 10 +---- .../system/inputevents/InputEventUtils.kt | 39 +++++++++++++++++++ .../system/inputevents/MyKeyEvent.kt | 1 + .../system/inputmethod/InputKeyModel.kt | 2 + .../actions/PerformActionsUseCaseTest.kt | 6 +++ .../keymaps/DpadMotionEventTrackerTest.kt | 5 +++ .../mappings/keymaps/KeyMapControllerTest.kt | 9 +++++ 12 files changed, 83 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt index 80be40ea79..fdfb55b8de 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt @@ -2,6 +2,7 @@ package io.github.sds100.keymapper.actions import android.accessibilityservice.AccessibilityService import android.os.Build +import android.view.InputDevice import android.view.KeyEvent import android.view.accessibility.AccessibilityNodeInfo import io.github.sds100.keymapper.R @@ -152,11 +153,19 @@ class PerformActionsUseCaseImpl( is ActionData.InputKeyEvent -> { val deviceId: Int = getDeviceIdForKeyEventAction(action) + // See issue #1683. Some apps ignore key events which do not have a source. + val source = when { + InputEventUtils.isDpadKeyCode(action.keyCode) -> InputDevice.SOURCE_DPAD + InputEventUtils.isGamepadButton(action.keyCode) -> InputDevice.SOURCE_GAMEPAD + else -> InputDevice.SOURCE_KEYBOARD + } + val model = InputKeyModel( keyCode = action.keyCode, inputType = inputEventType, metaState = keyMetaState.withFlag(action.metaState), deviceId = deviceId, + source = source, ) result = when { diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt index fafed46f55..b7db65f9c4 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt @@ -2,6 +2,7 @@ package io.github.sds100.keymapper.mappings.keymaps.detection import android.accessibilityservice.AccessibilityService import android.os.SystemClock +import android.view.InputDevice import android.view.KeyEvent import io.github.sds100.keymapper.R import io.github.sds100.keymapper.constraints.ConstraintState @@ -193,6 +194,7 @@ class DetectKeyMapsUseCaseImpl( deviceId: Int, inputEventType: InputEventType, scanCode: Int, + source: Int, ) { val model = InputKeyModel( keyCode, @@ -200,6 +202,7 @@ class DetectKeyMapsUseCaseImpl( metaState, deviceId, scanCode, + source = source, ) if (permissionAdapter.isGranted(Permission.SHIZUKU)) { @@ -258,6 +261,7 @@ interface DetectKeyMapsUseCase { deviceId: Int = 0, inputEventType: InputEventType = InputEventType.DOWN_UP, scanCode: Int = 0, + source: Int = InputDevice.SOURCE_UNKNOWN, ) val isScreenOn: Flow diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectScreenOffKeyEventsController.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectScreenOffKeyEventsController.kt index beb9f826eb..600e9fab2f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectScreenOffKeyEventsController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectScreenOffKeyEventsController.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.mappings.keymaps.detection +import android.view.InputDevice import android.view.KeyEvent import io.github.sds100.keymapper.system.devices.DevicesAdapter import io.github.sds100.keymapper.system.devices.InputDeviceInfo @@ -90,6 +91,7 @@ class DetectScreenOffKeyEventsController( scanCode = 0, metaState = 0, repeatCount = 0, + source = InputDevice.SOURCE_UNKNOWN, ), ) } @@ -103,6 +105,7 @@ class DetectScreenOffKeyEventsController( scanCode = 0, metaState = 0, repeatCount = 0, + source = InputDevice.SOURCE_UNKNOWN, ), ) } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DpadMotionEventTracker.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DpadMotionEventTracker.kt index 202ba121ad..0266b50cfa 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DpadMotionEventTracker.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DpadMotionEventTracker.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.mappings.keymaps.detection +import android.view.InputDevice import android.view.KeyEvent import io.github.sds100.keymapper.system.devices.InputDeviceInfo import io.github.sds100.keymapper.system.inputevents.InputEventUtils @@ -107,6 +108,7 @@ class DpadMotionEventTracker { scanCode = 0, device = event.device, repeatCount = 0, + source = InputDevice.SOURCE_DPAD, ) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt index 99205867b6..c25ce900ec 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt @@ -140,6 +140,7 @@ class MyAccessibilityService : scanCode = event.scanCode, device = device, repeatCount = event.repeatCount, + source = event.source, ), ) } @@ -315,6 +316,7 @@ class MyAccessibilityService : scanCode = event.scanCode, device = device, repeatCount = event.repeatCount, + source = event.source, ), KeyEventDetectionSource.ACCESSIBILITY_SERVICE, ) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt index f2780cc903..5a680f9893 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventInjector.kt @@ -1,6 +1,5 @@ package io.github.sds100.keymapper.system.inputevents -import android.view.InputDevice import android.view.KeyEvent import io.github.sds100.keymapper.system.inputmethod.InputKeyModel @@ -12,12 +11,6 @@ interface InputEventInjector { action: Int, model: InputKeyModel, ): KeyEvent { - val source = when { - InputEventUtils.isDpadKeyCode(model.keyCode) -> InputDevice.SOURCE_DPAD - KeyEvent.isGamepadButton(model.keyCode) -> InputDevice.SOURCE_GAMEPAD - else -> InputDevice.SOURCE_KEYBOARD - } - return KeyEvent( eventTime, eventTime, @@ -28,8 +21,7 @@ interface InputEventInjector { model.deviceId, model.scanCode, 0, - // See issue #1683. Some apps ignore key events which do not have a source. - source, + model.source, ) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventUtils.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventUtils.kt index d5b336eabc..a42c1949fa 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/InputEventUtils.kt @@ -830,4 +830,43 @@ object InputEventUtils { fun isDpadDevice(event: InputEvent): Boolean = // Check that input comes from a device with directional pads. event.source and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD + + fun isGamepadButton(keyCode: Int): Boolean { + return when (keyCode) { + KeyEvent.KEYCODE_BUTTON_A, + KeyEvent.KEYCODE_BUTTON_B, + KeyEvent.KEYCODE_BUTTON_C, + KeyEvent.KEYCODE_BUTTON_X, + KeyEvent.KEYCODE_BUTTON_Y, + KeyEvent.KEYCODE_BUTTON_Z, + KeyEvent.KEYCODE_BUTTON_L1, + KeyEvent.KEYCODE_BUTTON_R1, + KeyEvent.KEYCODE_BUTTON_L2, + KeyEvent.KEYCODE_BUTTON_R2, + KeyEvent.KEYCODE_BUTTON_THUMBL, + KeyEvent.KEYCODE_BUTTON_THUMBR, + KeyEvent.KEYCODE_BUTTON_START, + KeyEvent.KEYCODE_BUTTON_SELECT, + KeyEvent.KEYCODE_BUTTON_MODE, + KeyEvent.KEYCODE_BUTTON_1, + KeyEvent.KEYCODE_BUTTON_2, + KeyEvent.KEYCODE_BUTTON_3, + KeyEvent.KEYCODE_BUTTON_4, + KeyEvent.KEYCODE_BUTTON_5, + KeyEvent.KEYCODE_BUTTON_6, + KeyEvent.KEYCODE_BUTTON_7, + KeyEvent.KEYCODE_BUTTON_8, + KeyEvent.KEYCODE_BUTTON_9, + KeyEvent.KEYCODE_BUTTON_10, + KeyEvent.KEYCODE_BUTTON_11, + KeyEvent.KEYCODE_BUTTON_12, + KeyEvent.KEYCODE_BUTTON_13, + KeyEvent.KEYCODE_BUTTON_14, + KeyEvent.KEYCODE_BUTTON_15, + KeyEvent.KEYCODE_BUTTON_16, + -> true + + else -> false + } + } } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/MyKeyEvent.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/MyKeyEvent.kt index 64384acaac..6517eefa26 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputevents/MyKeyEvent.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputevents/MyKeyEvent.kt @@ -9,4 +9,5 @@ data class MyKeyEvent( val scanCode: Int, val device: InputDeviceInfo?, val repeatCount: Int, + val source: Int, ) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/InputKeyModel.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/InputKeyModel.kt index ec2942ec0e..17e4a53871 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/InputKeyModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/InputKeyModel.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.system.inputmethod +import android.view.InputDevice import io.github.sds100.keymapper.util.InputEventType /** @@ -12,4 +13,5 @@ data class InputKeyModel( val deviceId: Int = 0, val scanCode: Int = 0, val repeat: Int = 0, + val source: Int = InputDevice.SOURCE_UNKNOWN, ) diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt index b338b65d2c..69b71487f1 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.actions +import android.view.InputDevice import android.view.KeyEvent import io.github.sds100.keymapper.system.accessibility.IAccessibilityService import io.github.sds100.keymapper.system.devices.FakeDevicesAdapter @@ -142,6 +143,7 @@ class PerformActionsUseCaseTest { deviceId = fakeGamePad.id, scanCode = 0, repeat = 0, + source = InputDevice.SOURCE_GAMEPAD, ) verify(mockImeInputEventInjector, times(1)).inputKeyEvent(expectedInputKeyModel) @@ -171,6 +173,7 @@ class PerformActionsUseCaseTest { deviceId = 0, scanCode = 0, repeat = 0, + source = InputDevice.SOURCE_GAMEPAD, ) verify(mockImeInputEventInjector, times(1)).inputKeyEvent(expectedInputKeyModel) @@ -220,6 +223,7 @@ class PerformActionsUseCaseTest { deviceId = fakeKeyboard.id, scanCode = 0, repeat = 0, + source = InputDevice.SOURCE_GAMEPAD, ) verify(mockImeInputEventInjector, times(1)).inputKeyEvent(expectedInputKeyModel) @@ -278,6 +282,7 @@ class PerformActionsUseCaseTest { deviceId = 11, scanCode = 0, repeat = 0, + source = InputDevice.SOURCE_KEYBOARD, ), ) } @@ -318,6 +323,7 @@ class PerformActionsUseCaseTest { deviceId = 10, scanCode = 0, repeat = 0, + source = InputDevice.SOURCE_KEYBOARD, ), ) } diff --git a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/DpadMotionEventTrackerTest.kt b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/DpadMotionEventTrackerTest.kt index b323630108..492a3d788a 100644 --- a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/DpadMotionEventTrackerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/DpadMotionEventTrackerTest.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.mappings.keymaps +import android.view.InputDevice import android.view.KeyEvent import io.github.sds100.keymapper.mappings.keymaps.detection.DpadMotionEventTracker import io.github.sds100.keymapper.system.devices.InputDeviceInfo @@ -70,6 +71,7 @@ class DpadMotionEventTrackerTest { scanCode = 0, device = CONTROLLER_1_DEVICE, repeatCount = 0, + source = InputDevice.SOURCE_DPAD, ), ), ) @@ -83,6 +85,7 @@ class DpadMotionEventTrackerTest { scanCode = 0, device = CONTROLLER_1_DEVICE, repeatCount = 0, + source = InputDevice.SOURCE_DPAD, ), ), ) @@ -277,6 +280,7 @@ class DpadMotionEventTrackerTest { scanCode = 0, device = device, repeatCount = 0, + source = 0, ) } @@ -288,6 +292,7 @@ class DpadMotionEventTrackerTest { scanCode = 0, device = device, repeatCount = 0, + source = 0, ) } } diff --git a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapControllerTest.kt b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapControllerTest.kt index 149d0d7522..7a0fee13b6 100644 --- a/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapControllerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapControllerTest.kt @@ -1166,6 +1166,7 @@ class KeyMapControllerTest { any(), any(), any(), + any(), ) // If both triggers are detected @@ -1180,6 +1181,7 @@ class KeyMapControllerTest { any(), any(), any(), + any(), ) // If no triggers are detected @@ -1194,6 +1196,7 @@ class KeyMapControllerTest { any(), any(), any(), + any(), ) } } @@ -2665,6 +2668,7 @@ class KeyMapControllerTest { any(), any(), any(), + any(), ) verify(detectKeyMapsUseCase, times(1)).imitateButtonPress( @@ -2673,6 +2677,7 @@ class KeyMapControllerTest { any(), any(), any(), + any(), ) } } @@ -3068,6 +3073,7 @@ class KeyMapControllerTest { any(), any(), any(), + any(), ) } @@ -3099,6 +3105,7 @@ class KeyMapControllerTest { any(), any(), any(), + any(), ) } @@ -3126,6 +3133,7 @@ class KeyMapControllerTest { any(), any(), any(), + any(), ) verify(performActionsUseCase, times(1)).perform(TEST_ACTION.data) } @@ -4137,6 +4145,7 @@ class KeyMapControllerTest { scanCode = scanCode, device = device, repeatCount = repeatCount, + source = 0, ), ) From 9918cf0ff8e105f2d8e168bbe8d829349f0c4c14 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 8 May 2025 13:10:27 +0200 Subject: [PATCH 22/95] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index dad6c30b8f..773194701f 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.0 -VERSION_CODE=110 +VERSION_CODE=112 VERSION_NUM=0 \ No newline at end of file From d4a9202d4b82e3e23315ff78f20d718c3ad0c320 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Thu, 8 May 2025 11:10:54 +0000 Subject: [PATCH 23/95] New Crowdin translations by GitHub Action --- app/src/main/res/values-id/strings.xml | 74 ++ app/src/main/res/values-tr/strings.xml | 1178 +++++++++++++++++ .../metadata/android/ar/full_description.txt | 55 + .../metadata/android/ar/short_description.txt | 1 + fastlane/metadata/android/ar/title.txt | 1 + .../android/cs_CZ/full_description.txt | 55 + .../android/cs_CZ/short_description.txt | 1 + fastlane/metadata/android/cs_CZ/title.txt | 1 + .../android/de_DE/full_description.txt | 55 + .../android/de_DE/short_description.txt | 1 + fastlane/metadata/android/de_DE/title.txt | 1 + .../android/es_ES/full_description.txt | 55 + .../android/es_ES/short_description.txt | 1 + fastlane/metadata/android/es_ES/title.txt | 1 + .../android/fr_FR/full_description.txt | 55 + .../android/fr_FR/short_description.txt | 1 + fastlane/metadata/android/fr_FR/title.txt | 1 + .../android/hu_HU/full_description.txt | 55 + .../android/hu_HU/short_description.txt | 1 + fastlane/metadata/android/hu_HU/title.txt | 1 + .../android/id_ID/full_description.txt | 55 + .../android/id_ID/short_description.txt | 1 + fastlane/metadata/android/id_ID/title.txt | 1 + .../android/ka_GE/full_description.txt | 55 + .../android/ka_GE/short_description.txt | 1 + fastlane/metadata/android/ka_GE/title.txt | 1 + .../android/ko_KR/full_description.txt | 55 + .../android/ko_KR/short_description.txt | 1 + fastlane/metadata/android/ko_KR/title.txt | 1 + .../android/pl_PL/full_description.txt | 55 + .../android/pl_PL/short_description.txt | 1 + fastlane/metadata/android/pl_PL/title.txt | 1 + .../android/pt_BR/full_description.txt | 55 + .../android/pt_BR/short_description.txt | 1 + fastlane/metadata/android/pt_BR/title.txt | 1 + .../android/ru_RU/full_description.txt | 55 + .../android/ru_RU/short_description.txt | 1 + fastlane/metadata/android/ru_RU/title.txt | 1 + .../metadata/android/sk/full_description.txt | 55 + .../metadata/android/sk/short_description.txt | 1 + fastlane/metadata/android/sk/title.txt | 1 + .../android/tr_TR/full_description.txt | 55 + .../android/tr_TR/short_description.txt | 1 + fastlane/metadata/android/tr_TR/title.txt | 1 + .../metadata/android/uk/full_description.txt | 55 + .../metadata/android/uk/short_description.txt | 1 + fastlane/metadata/android/uk/title.txt | 1 + .../metadata/android/vi/full_description.txt | 55 + .../metadata/android/vi/short_description.txt | 1 + fastlane/metadata/android/vi/title.txt | 1 + .../android/zh_CN/full_description.txt | 55 + .../android/zh_CN/short_description.txt | 1 + fastlane/metadata/android/zh_CN/title.txt | 1 + .../android/zh_TW/full_description.txt | 55 + .../android/zh_TW/short_description.txt | 1 + fastlane/metadata/android/zh_TW/title.txt | 1 + 56 files changed, 2278 insertions(+) create mode 100644 app/src/main/res/values-id/strings.xml create mode 100644 fastlane/metadata/android/ar/full_description.txt create mode 100644 fastlane/metadata/android/ar/short_description.txt create mode 100644 fastlane/metadata/android/ar/title.txt create mode 100644 fastlane/metadata/android/cs_CZ/full_description.txt create mode 100644 fastlane/metadata/android/cs_CZ/short_description.txt create mode 100644 fastlane/metadata/android/cs_CZ/title.txt create mode 100644 fastlane/metadata/android/de_DE/full_description.txt create mode 100644 fastlane/metadata/android/de_DE/short_description.txt create mode 100644 fastlane/metadata/android/de_DE/title.txt create mode 100644 fastlane/metadata/android/es_ES/full_description.txt create mode 100644 fastlane/metadata/android/es_ES/short_description.txt create mode 100644 fastlane/metadata/android/es_ES/title.txt create mode 100644 fastlane/metadata/android/fr_FR/full_description.txt create mode 100644 fastlane/metadata/android/fr_FR/short_description.txt create mode 100644 fastlane/metadata/android/fr_FR/title.txt create mode 100644 fastlane/metadata/android/hu_HU/full_description.txt create mode 100644 fastlane/metadata/android/hu_HU/short_description.txt create mode 100644 fastlane/metadata/android/hu_HU/title.txt create mode 100644 fastlane/metadata/android/id_ID/full_description.txt create mode 100644 fastlane/metadata/android/id_ID/short_description.txt create mode 100644 fastlane/metadata/android/id_ID/title.txt create mode 100644 fastlane/metadata/android/ka_GE/full_description.txt create mode 100644 fastlane/metadata/android/ka_GE/short_description.txt create mode 100644 fastlane/metadata/android/ka_GE/title.txt create mode 100644 fastlane/metadata/android/ko_KR/full_description.txt create mode 100644 fastlane/metadata/android/ko_KR/short_description.txt create mode 100644 fastlane/metadata/android/ko_KR/title.txt create mode 100644 fastlane/metadata/android/pl_PL/full_description.txt create mode 100644 fastlane/metadata/android/pl_PL/short_description.txt create mode 100644 fastlane/metadata/android/pl_PL/title.txt create mode 100644 fastlane/metadata/android/pt_BR/full_description.txt create mode 100644 fastlane/metadata/android/pt_BR/short_description.txt create mode 100644 fastlane/metadata/android/pt_BR/title.txt create mode 100644 fastlane/metadata/android/ru_RU/full_description.txt create mode 100644 fastlane/metadata/android/ru_RU/short_description.txt create mode 100644 fastlane/metadata/android/ru_RU/title.txt create mode 100644 fastlane/metadata/android/sk/full_description.txt create mode 100644 fastlane/metadata/android/sk/short_description.txt create mode 100644 fastlane/metadata/android/sk/title.txt create mode 100644 fastlane/metadata/android/tr_TR/full_description.txt create mode 100644 fastlane/metadata/android/tr_TR/short_description.txt create mode 100644 fastlane/metadata/android/tr_TR/title.txt create mode 100644 fastlane/metadata/android/uk/full_description.txt create mode 100644 fastlane/metadata/android/uk/short_description.txt create mode 100644 fastlane/metadata/android/uk/title.txt create mode 100644 fastlane/metadata/android/vi/full_description.txt create mode 100644 fastlane/metadata/android/vi/short_description.txt create mode 100644 fastlane/metadata/android/vi/title.txt create mode 100644 fastlane/metadata/android/zh_CN/full_description.txt create mode 100644 fastlane/metadata/android/zh_CN/short_description.txt create mode 100644 fastlane/metadata/android/zh_CN/title.txt create mode 100644 fastlane/metadata/android/zh_TW/full_description.txt create mode 100644 fastlane/metadata/android/zh_TW/short_description.txt create mode 100644 fastlane/metadata/android/zh_TW/title.txt diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml new file mode 100644 index 0000000000..1d669e715e --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1d669e715e..e32a75e124 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,74 +1,1252 @@ + Tuşlarınızı özgür bırakın! + Key Mapper, uygulama dışında olduğunuzda düğme basışlarınızı algılayıp değiştirebilmesi için bir erişilebilirlik servisine ihtiyaç duyar. Tuş eşlemeleriniz, erişilebilirlik servisini etkinleştirdiğinizde çalışır. Tetikleyici oluşturmak ve eylemleri test etmek için de bu servisin açık olması gerekir. + seçildi + Etkinleştir + ¯\\_(ツ)_/¯\n\nBurada hiçbir şey yok! + İlk adım, tuş eşlemesini tetikleyecek bazı düğmeler eklemektir.\n\nÖnce ‘Tetikleyici Kaydet’ seçeneğine dokunun ve ardından yeniden eşlemek istediğiniz düğmelere basın. Bunlar burada görünecek.\n\nAlternatif olarak, bir tuş eşlemesini ‘gelişmiş tetikleyici’ kullanarak tetikleyebilirsiniz.\n\nİstediğiniz tuşları karıştırıp eşleştirebilirsiniz! + Root gerektirir + Bas… + Eylem yok + Tetikleyici yok + Bilinmeyen cihaz adı + Açık + Kapalı + Sistemi takip et + Bu cihaz + Herhangi bir cihaz + Varsayılan + Erişilebilirlik servisini etkinleştir + Erişilebilirlik servisini yeniden başlat + Paylaş + Burada hiçbir şey yok! + Burada hiçbir şey yok! + Tekrarlamayı durdur… + Tetikleyici bırakıldığında + Tetikleyici tekrar basıldığında + Sınır ulaşıldığında + Tetikleyici bırakıldığında + Tetikleyici tekrar basıldığında + Gizli uygulamaları göster + Değiştiriciler + ÖNEMLİ!!! Bu koordinatlar yalnızca ekranınız ekran görüntüsüyle aynı yönde olduğunda doğrudur! Bu eylem, ekranda yaptığınız tüm dokunma veya hareketleri iptal eder.\n\nEkranınızdaki bir noktanın koordinatlarını bulmakta yardıma ihtiyacınız varsa, bir ekran görüntüsü alın ve ardından bu eylemin basmasını istediğiniz yere ekran görüntüsüne dokunun. + Not: \"İçeri sıkıştırma\" kullanırken X ve Y BİTİŞ koordinatlarıdır, \"Dışarı sıkıştırma\" kullanırken X ve Y BAŞLANGIÇ koordinatlarıdır. + Eylemleri düzeltmek için dokunun! + Kısıtlamaları düzeltmek için dokunun! + Eylemleri gerçekleştir + Tetikleyici şu olana kadar basılı tut… + Cihaz yok + ¯\\_(ツ)_/¯\n\nEkstra yok! + Tuş olayı eylemini yapılandırma tamamlandı + Koordinat seçimi tamamlandı + Key Mapper günlüğü + Neler Yeni + Ses dosyası, Key Mapper’ın özel veri klasörüne kopyalanacak; bu, dosya taşınsa veya silinse bile eylemlerinizin çalışmaya devam edeceği anlamına gelir. Ayrıca, tuş eşlemelerinizle birlikte zip klasöründe yedeklenecektir. + Kaydedilen ses dosyalarını ayarlardan silebilirsiniz. + Eşleştirilmiş cihaz bulunamadı. Bluetooth açık mı? + Kısayol olarak kullanmak için bir tuş eşlemesine dokunun. + Tuş eşleme kısayolu oluştur + Etkin + Devre dışı + Sıfırla + Cihaz yöneticisini etkinleştirdikten sonra, Key Mapper’ı kaldırmak istiyorsanız bunu DEVRE DIŞI BIRAKMANIZ gerekir. + %sms bekle + Etkinlik başlat: %s + Servis başlat: %s + Yayın gönder: %s + Tuş eşleme kimliği + Kabuğu kullan (Yalnızca ROOT) + Rahatsız Etmeyin modunda düzgün çalışması için izin gerekli! + Ekran kapalıyken tetikleme seçeneği, çalışması için root izni gerektirir! + Bu tetikleyici, telefon çalarken veya görüşme sırasında çalışmaz! + Android, telefonunuz çalarken veya bir görüşme sırasında erişilebilirlik servislerinin ses düğmesi basışlarını algılamasına izin vermez, ancak giriş yöntemi servisleri bunu algılayabilir. Bu nedenle, bu tetikleyicinin çalışmasını istiyorsanız Key Mapper klavyelerinden birini kullanmalısınız. + Android sınırlamaları nedeniyle harekette çok fazla parmak var. + Android sınırlamaları nedeniyle hareket süresi çok yüksek. + DPAD tetikleyicilerinin çalışması için bir Key Mapper klavyesi kullanmalısınız! + Tuş eşlemeleriniz rastgele duracak! + Tuş eşlemeleriniz duraklatıldı! + Devam ettir + Tuş eşlemelerinizin çalışması için erişilebilirlik servisinin açık olması gerekir! + Telefonunuz, Key Mapper’ı arka planda çalışırken kapattı veya çöktü! + Erişilebilirlik servisi etkin! Tuş eşlemeleriniz çalışmalı. + Ekstra günlük kaydı açık! Bir sorunu düzeltmeye çalışmıyorsanız bunu kapatın. + Kapat + Hakkında + %s uygulamasını aç + ‘%s’ yaz + %s%s gir + Kabuk üzerinden %s gir + %s cihazından %s%s gir + %s aç + Ekrana dokun (%d, %d) + Ekrana dokun (%s) + %d parmakla %d/%d koordinatlarından %d/%d koordinatlarına %dms içinde kaydır + %d parmakla %d/%d koordinatlarından %d/%d koordinatlarına %dms içinde kaydır (%s) + %s, %d parmakla %d/%d koordinatlarında %dpx sıkıştırma mesafesiyle %dms içinde + %s, %d parmakla %d/%d koordinatlarında %dpx sıkıştırma mesafesiyle %dms içinde (%s) + %s numarayı ara + Ses çal: %s + Seçenekler: + Eylemler: + Tetikleyici: + Kısıtlamalar: + Yukarı kaydır + Aşağı kaydır + Sola kaydır + Sağa kaydır + Ekstralar + Başlangıç X + Başlangıç Y + Bitiş X + Bitiş Y + Sıkıştırma mesafesi (px) + Sıkıştırma türü + İçeri sıkıştırma + Dışarı sıkıştırma + Tuş kodu + Cihazdan + Kısayol adı + Koordinat açıklaması (isteğe bağlı) + Girilecek metin + Açılacak URL + Aranacak telefon numarası + Eylem + Kategoriler + Veri + Paket + Sınıf + Ad + Değer (%s) + Key Mapper için açıklama (gerekli) + Bayraklar + Ses dosyası açıklaması + WiFi ağ SSID’si + Aynı anda + Sırayla + VE + VEYA + Kısa basış + Uzun basış + Çift basış + Doğru + Yanlış + Etkinlik + Servis + Yayın alıcısı + Tetikleyici ve eylemler + Kısıtlamalar ve daha fazlası + Tetikleyici + Eylemler + Kısıtlamalar + Seçenekler + %s seçildi + Yedekleme başarılı! + Yedekleme başarısız! + Geri yükleme başarılı! + Geri yükleme başarısız! + Otomatik yedekleme başarılı! + Otomatik yedekleme başarısız! + Ekran görüntüsü alındı + Ekran görüntüsü çözünürlüğü bu cihazın çözünürlüğüyle eşleşmiyor! + Tuş eşleme UUID’si panoya kopyalandı + Bir tuş eşlemesini tetiklediniz + Günlük kopyalandı + Kaydedilmiş ses dosyanız yok! + Key Mapper, Shizuku kullanarak kendine WRITE_SECURE_SETTINGS izni verdi + Key Mapper, Root kullanarak kendine WRITE_SECURE_SETTINGS izni verdi + Sıra tetikleyici zaman aşımı + Uzun basış gecikmesi + Çift basış zaman aşımı + Tekrarlama gecikmesi + Tekrarlama sınırı + Her… tekrar et + Titreşim süresi + Kaç kez + Her tekrar için kaç kez + Sonraki eylemden önceki gecikme + Basılı tutma süresi + Kaydırma süresi (ms) + Parmak sayısı + Ekran görüntüsüyle ayarlanacak koordinatlar + Başlangıç + Bitiş + Sıkıştırma süresi (ms) + Parmak sayısı + %s ön planda + %s ön planda değil + %s medya oynatıyor + %s medya oynatmıyor + %s bağlı + %s bağlantısı kesildi + Ekran açık + Ekran kapalı + Flaş kapalı + Flaş açık + Ön flaş kapalı + Ön flaş açık + VE + VEYA + Ön plandaki uygulama + Ön planda olmayan uygulama + Bluetooth cihazı bağlı + Bluetooth cihazı bağlantısı kesildi + Ekran açık + Ekran kapalı + Dikey (0°) + Yatay (90°) + Dikey (180°) + Yatay (270°) + Dikey (herhangi) + Yatay (herhangi) + Medya oynatan uygulama + Medya oynatmayan uygulama + Medya oynuyor + Medya oynatmıyor + Flaş açık + Flaş kapalı + WiFi açık + WiFi kapalı + Bir WiFi ağına bağlı + Bir WiFi ağından bağlantı kesildi + Android 10 ve daha yeni sürümlerde uygulamaların bilinen WiFi ağlarının listesini sorgulamasına izin verilmez, bu nedenle SSID’yi manuel olarak yazmanız gerekecek. + + Herhangi bir WiFi ağının eşleşmesi gerekiyorsa boş bırakın. + Herhangi biri + %s WiFi’ye bağlı + %s WiFi’den bağlantı kesildi + Herhangi bir WiFi’ye bağlı + Hiçbir WiFi’ye bağlı değil + Giriş yöntemi seçildi + %s seçildi + Giriş yöntemi seçilmedi + %s seçilmedi + Cihaz kilitli + Cihaz kilidi açık + Kilit ekranı gösteriliyor + Kilit ekranı gösterilmiyor + Telefon görüşmesinde + Telefon görüşmesinde değil + Telefon çalıyor + Şarj oluyor + Şarj olmuyor + Dikey (0°) + Yatay (90°) + Dikey (180°) + Yatay (270°) + Zaman + %s ve %s arasındaki süre + Zaman sınırlaması + Başlangıç zamanı + Başlangıç zamanını düzenle + Bitiş zamanı + Bitiş zamanını düzenle + Uzun basış + Çift basış + Uygulama kapatma özelliğini kapat + Telefonunuzdaki tüm uygulama kapatma \"özelliklerini\" nasıl kapatacağınızı gösteren dontkillmyapp.com adresindeki harika kılavuzu takip edin. + + \n\nKılavuzu okuduktan sonra bir sonraki slayta geçmeniz ve erişilebilirlik servisini yeniden başlatmanız gerekecek. + Kılavuzu aç + Erişilebilirlik servisini yeniden başlat + Erişilebilirlik servisi yeniden başlatılmalıdır. Kapatıp açın. + Hata raporu oluştur + \"Rapor oluştur\" seçeneğine dokunarak hata raporunu kaydedeceğiniz bir konum seçin. Bir sonraki slayt, bunu geliştiriciye nasıl göndereceğinizi açıklayacak. + Rapor oluştur + Raporu paylaş + Hata raporunu geliştiriciyle paylaşmanın 2 yolu var. Discord sunucusuna katılabilir veya GitHub’da bir sorun oluşturabilirsiniz. Mesajınıza hata raporunu eklemeyi unutmayın! + Discord + GitHub + Ayarlar + Hakkında + Ara + Yardım + Hata bildir + Giriş yöntemi seçiciyi göster + Kaydet + Geri yükle + Her şeyi yedekle + Dokunarak duraklat + Dokunarak devam ettir + Kaydet + Kısa mesajları aç/kapat + Kopyala + Temizle + Eylem ekle + Tetikleyici kaydet + Gelişmiş tetikleyiciler + YENİ! + Tamam + Düzelt + Kaydediyor (%d…) + Kısıtlama ekle + Tuş kodu seç + Ekstra ekle + Başlatıcıda kısayol oluştur + Kısayolu manuel olarak oluştur + Intent kılavuzu + Yardım + Ekran görüntüsü seç (isteğe bağlı) + Etkinlik seç + Bayrakları ayarla + Sınır yok + Ses dosyası seç + Eylemi düzenle + Eylemi değiştir + Root izni gerekli! + Erişilebilirlik ayarları sayfası bulunamadı + Kaydedilmemiş değişiklikler + Kaydedilmemiş değişiklikleriniz var. Bunları iptal ederseniz, düzenlemeleriniz kaybolacak. + Telefonunuzun rootlu olmadığını biliyorsanız veya rootun ne olduğunu bilmiyorsanız, yalnızca rootlu cihazlarda çalışan özellikleri kullanamazsınız. ‘Tamam’a dokunduğunuzda ayarlara yönlendirileceksiniz. + Ayarlarda, en alta kaydırın ve root özelliklerini/eylemlerini kullanabilmek için ‘Key Mapper root iznine sahip’ seçeneğine dokunun. + WRITE_SECURE_SETTINGS izni ver + Bu izni vermek için bir PC/Mac gereklidir. Çevrimiçi kılavuzu okuyun. + Cihazınızda erişilebilirlik servisleri ayarları sayfası yok gibi görünüyor. Bunu nasıl düzelteceğinizi açıklayan çevrimiçi kılavuzu okumak için “kılavuz” seçeneğine dokunun. + Tuşlar, basılı tutulacakları sırayla yukarıdan aşağıya listelenmelidir. + Bir “sıra” tetikleyicisi, paralel tetikleyicilerden farklı olarak bir zaman aşımına sahiptir. Bu, ilk tuşa bastıktan sonra tetikleyicideki diğer tuşları girmeniz için belirli bir süreniz olacağı anlamına gelir. Tetikleyiciye eklediğiniz tüm tuşlar, zaman aşımı süresine ulaşılana kadar normal işlevlerini yerine getirmez. Bu zaman aşımını “Seçenekler” sekmesinden değiştirebilirsiniz. + Android, uygulamaların bağlı (eşleştirilmemiş) Bluetooth cihazlarının listesini almasına izin vermez. Uygulamalar yalnızca bu cihazların bağlandığını ve bağlantısının kesildiğini algılayabilir. Bu nedenle, Bluetooth cihazınız erişilebilirlik servisi başladığında zaten bağlıysa, uygulamanın bunu bilmesi için cihazı yeniden bağlamanız gerekecek. + Konumu değiştir veya otomatik yedeklemeyi kapat? + Ekran açma/kapama kısıtlamaları, yalnızca “ekran kapalıyken tetikleyiciyi algıla” tuş eşleme seçeneğini açtıysanız çalışır. Bu seçenek, bazı tuşlar (örneğin ses düğmeleri) için ve yalnızca rootluysanız görünür. Desteklenen tuşların listesini Yardım sayfasında görebilirsiniz. + PIN veya Desen gibi başka bir ekran kilidiniz varsa endişelenmenize gerek yok. Ancak yalnızca Parola ekran kilidi kullanıyorsanız, Key Mapper Temel Giriş Yöntemi’ni kullanırsanız telefonunuzun kilidini açamazsınız çünkü bu yöntemin bir arayüzü yoktur. Key Mapper’a WRITE_SECURE_SETTINGS izni vererek klavyeyi değiştirmek için bir bildirim gösterebilirsiniz. Bunu nasıl yapacağınızı öğrenmek için ekranın altındaki soru işaretine dokunun. + Eylemler için bir giriş yöntemi gerektiren bir yöntem seçin. Bunu daha sonra ana ekranın alt menüsündeki “Eylemler için klavye seç” seçeneğiyle değiştirebilirsiniz. + Caps Lock tuşunun hala büyük harf kilitlemesini engellemek için klavyenizde “Caps Lock’u kameraya” klavye düzenini seçmeniz gerekir. Bu ayarı cihaz ayarlarınızda -> Diller ve Giriş -> Fiziksel Klavye -> Klavyenize dokunun -> Klavye Düzenlerini Ayarla bölümünden bulabilirsiniz. Bu, Caps Lock tuşunu KEYCODE_CAMERA’ya yeniden eşleyerek Key Mapper’ın bunu doğru şekilde eşlemesini sağlar.\n\nBunu yaptıktan sonra Caps Lock tetikleyici tuşunu kaldırıp Caps Lock tuşunu tekrar kaydetmelisiniz. Adımları doğru yaptıysanız “Caps Lock” yerine “Kamera” yazmalıdır. + Bağlı harici cihaz yok. + Key Mapper GUI Klavyesini Yükle + Bu şiddetle tavsiye edilir! Bu, Key Mapper ile kullanabileceğiniz uygun bir klavyedir. Key Mapper’a dahili olan (Temel Giriş Yöntemi) klavyede ekran klavyesi yoktur. Nereden yüklemek istediğinizi seçin. + Key Mapper Leanback Klavyesini Yükle + Bu şiddetle tavsiye edilir! Bu, Key Mapper ile kullanabileceğiniz Android TV için uygun bir klavyedir. Key Mapper’a dahili olan (Temel Giriş Yöntemi) klavyede ekran klavyesi yoktur. Nereden yüklemek istediğinizi seçin. + Key Mapper GUI Klavyesini Yükle + Nereden indirmek istediğinizi seçin. + Key Mapper Leanback Klavyesini Yükle + Nereden indirmek istediğinizi seçin. + Bu eylem için ek kurulum gerekiyor + Bu eylemi kullanmak için cihazınızı ayarlamanın 3 yolu var. Her birinin avantajları ve dezavantajları şunlardır: + + \n\n1. Shizuku’yu indirin (önerilen). Şu anda kullandığınız ekran klavyesini değiştirmeniz gerekmez, ancak cihazınızı her yeniden başlattığınızda bir dakikalık kurulum gerektirir. + + \n\n2. Key Mapper GUI Klavyesini indirin. Bu, Key Mapper ile kullanabileceğiniz bir ekran klavyesidir, ancak şu anda kullandığınız klavyeyi (örneğin Gboard) kullanamazsınız. + + \n\n3. Hiçbir şey yapmayın ve dahili Key Mapper klavyesini kullanın. Bu önerilmez çünkü Key Mapper’ı kullandığınızda hiçbir ekran klavyeniz olmaz! Hiçbir avantajı yoktur. + Bu eylem için ek kurulum gerekiyor + Bu eylemi kullanmak için cihazınızı ayarlamanın 3 yolu var. Her birinin avantajları ve dezavantajları şunlardır: + + \n\n1. Shizuku’yu indirin (önerilen). Şu anda kullandığınız ekran klavyesini değiştirmeniz gerekmez, ancak cihazınızı her yeniden başlattığınızda bir dakikalık kurulum gerektirir. + + \n\n2. Key Mapper Leanback Klavyesini indirin. Bu, Key Mapper ile kullanabileceğiniz Android TV için optimize edilmiş bir ekran klavyesidir, ancak şu anda kullandığınız klavyeyi (örneğin Gboard) kullanamazsınız. + + \n\n3. Hiçbir şey yapmayın ve dahili Key Mapper klavyesini kullanın. Bu önerilmez çünkü Key Mapper’ı kullandığınızda hiçbir ekran klavyeniz olmaz! Hiçbir avantajı yoktur. + Pil optimizasyonunu devre dışı bırak + HEPSİNİ okumanız ZORUNLU, aksi takdirde ileride sinir bozucu sorunlar yaşarsınız!\n\n“Kısmen düzelt” seçeneğine dokunmak, Android’in uygulamayı arka planda durdurmasını belki engelleyebilir.\n\nBu YETERLİ DEĞİL. MIUI veya Samsung Experience gibi OEM arayüzünüzde başka uygulama kapatma özellikleri olabilir, bu nedenle dontkillmyapp.com’daki çevrimiçi kılavuzu takip ederek Key Mapper için bunları da kapatmalısınız. + Erişilebilirlik servisini kapatıp açarak yeniden başlatın. + Bu tetikleyiciyi kullanmak, cihazınızın ayarlarındaki ekran sabitleme ayarını kullandıktan sonra cihazınızın kilidini açtığınızda siyah bir ekran oluşmasına neden olabilir. Bu, bir yeniden başlatma ile düzeltilebilir. Bu durum tüm cihazlarda olmaz, bu yüzden dikkatli olun ve sorun yaşarsanız ayarı kapatın! + Key Mapper kesintiye uğradı + Key Mapper arka planda çalışmaya çalıştı ancak sistem tarafından durduruldu.\nBu, pil veya bellek optimizasyonu açık olduğunda olabilir.\n\nBunu düzeltmek için çevrimiçi bir kılavuzu takip edebilirsiniz. İşiniz bittiğinde servisi de yeniden başlatmalısınız. + Devam et + Yoksay + Hata raporu oluşturulamadı + Hatayı düzelt + Key Mapper için dosya oluşturmanıza izin veren bir dosya uygulamanız yüklü değil. Lütfen bir dosya yöneticisi yükleyin. + Key Mapper için dosya seçmenize izin veren bir dosya uygulamanız yüklü değil. Lütfen bir dosya yöneticisi yükleyin. + Erişilebilirlik servisi etkinleştirilmeli + @string/accessibility_service_explanation + Rahatsız Etmeyin erişimini ver + Cihazınızın hangi uygulamaların Rahatsız Etmeyin durumunu değiştirebileceğini yönetebileceğiniz ayarlar sayfasına yönlendirileceksiniz. Bu bazı cihazlarda mevcut değildir, bu yüzden listede Key Mapper’ı görmüyorsanız “tekrar gösterme” seçeneğine dokunun. + Bilmenizde fayda var! + Bir tetikleyici tuşunun yanında bu sembolü (⌨) görüyorsanız, algılanması için bir Key Mapper klavyesi KULLANMALISINIZ. Bu, Android’deki bir kısıtlamadır ve yalnızca bazı düğmeler için gereklidir. + Önemli! + Key Mapper GUI Klavyesini, bu Key Mapper sürümüyle uyumlu olacak şekilde güncellemelisiniz. Güncelleme yapana kadar bazı tuş eşlemeleri çalışmayabilir! + Şimdi güncelle + Yoksay + Şuna göre sırala + Öncelikleri ayarlamak için tutamaçları sürükleyin. En üstteki öğe en önemlisidir. Ayrıca herhangi bir öğeye dokunarak sıralama sırasını tersine çevirebilirsiniz. + Örnek: Tuş eşlemelerini öncelikle Eylemlerine göre artan sırayla ve ikincil olarak Tetikleyicilerine göre azalan sırayla sıralamak için Eylemleri birinci sıraya, Tetikleyicileri ikinci sıraya taşıyın. + %1$s için tutamaç + Örnek göster + Tanınmayan tuş kodu + Basılı düğme, giriş sistemi tarafından tanınmadı. Geçmişte Key Mapper bu tür düğmeleri tek bir düğme olarak algılıyordu. Şu anda uygulama, düğmeyi tarama koduna göre ayırt etmeye çalışıyor; bu, daha benzersiz olmalıdır. Ancak bu, benzersizliği garanti etmeyen geçici ve eksik bir çözümdür. + Tamam + Kılavuz + Kılavuz + Değiştir + Kısmen düzelt + Tamam + Yeniden başlat + Tekrar gösterme + Uygula + Değişiklikleri iptal et + Kaydet + Anladım + Kapat + İptal + Tekrar gösterme + Düzenlemeye devam et + Gizle + Çevrimiçi kılavuz + Ayarlar + Belgeler + Değişiklik günlüğü + Shizuku + Key Mapper GUI Klavyesi + Key Mapper Leanback Klavyesi + Hiçbir şey yapma + Düzelt + Klavye seçici + Tuş eşlemelerini duraklat/devam ettir + Klavye gizli uyarısı + Key Mapper klavyesini aç/kapat + Yeni özellikler + Klavyenizi değiştirmek için dokunun. + Klavye seçici + Çalışıyor + Key Mapper’ı açmak için dokunun. + Duraklat + Duraklatıldı + Key Mapper’ı açmak için dokunun. + Devam ettir + Kapat + Yeniden başlat + Erişilebilirlik servisi devre dışı + Erişilebilirlik servisini başlatmak için dokunun. + Erişilebilirlik servisi yeniden başlatılmalı! + Erişilebilirlik servisi çöktü! Telefonunuz bunu agresif bir şekilde kapatmış olabilir! Erişilebilirlik servisini yeniden başlatmak için dokunun. + Servisi durdur + Klavye gizli! + Klavyeyi tekrar göstermeye başlamak için ‘klavyeyi göster’e dokunun. + Key Mapper klavyesini aç/kapat + Key Mapper klavyesine ve klavyenizden geçiş yapmak için ‘aç/kapat’a dokunun. + Aç/Kapat + Varsayılan uzun basış gecikmesi (ms) + Bir düğmenin uzun basış olarak algılanması için ne kadar süre basılı tutulması gerektiği. Varsayılan 500ms’dir. Bir tuş eşlemesinin seçeneklerinde geçersiz kılınabilir. + Varsayılan çift basış süresi (ms) + Bir düğmenin çift basış olarak algılanması için ne kadar hızlı çift basılması gerektiği. Varsayılan 300ms’dir. Bir tuş eşlemesinin seçeneklerinde geçersiz kılınabilir. + Bir tuş eşlemesi için titreşim etkinse ne kadar süre titreşeceği. Varsayılan 200ms’dir. Bir tuş eşlemesinin seçeneklerinde geçersiz kılınabilir. + Varsayılan titreşim süresi (ms) + Eylemin tekrarlanmaya başlaması için tetikleyicinin ne kadar süre basılı tutulması gerektiği. Varsayılan 400ms’dir. Bir tuş eşlemesinin seçeneklerinde geçersiz kılınabilir. + Varsayılan tekrar gecikmesi (ms) + Bir eylemin her tekrar arasındaki gecikme. Varsayılan 50ms’dir. Bir tuş eşlemesinin seçeneklerinde geçersiz kılınabilir. + Varsayılan tekrarlar arası gecikme (ms) + Bir sıra tetikleyicisini tamamlamak için izin verilen süre. Varsayılan 1000ms’dir. Bir tuş eşlemesinin seçeneklerinde geçersiz kılınabilir. + Varsayılan sıra tetikleyici zaman aşımı (ms) + Sıfırla + Tüm tuş eşlemelerini titreşime zorla. + Titreşimi zorla + Klavye seçici bildirimi + Klavye seçmenize olanak tanıyan kalıcı bir bildirim göster. + Tuş eşlemelerini duraklat/devam ettir bildirimi + Tuş eşlemelerinizi başlatan/duraklatan kalıcı bir bildirim göster. + Tuş eşlemelerini belirtilen bir konuma otomatik olarak yedekle + Konum seçilmedi. + Cihazları seç + Klavye seçiciyi otomatik olarak göster + Seçtiğiniz bir cihaz bağlandığında veya bağlantısı kesildiğinde klavye seçici otomatik olarak gösterilir. Aşağıdan cihazları seçin. + Bir cihaz (örneğin klavye) bağlandığında/bağlantısı kesildiğinde ekran klavyesini otomatik olarak değiştir + Seçilen bir cihaz bağlandığında son kullanılan Key Mapper klavyesi otomatik olarak seçilir. Cihazın bağlantısı kesildiğinde normal klavyeniz otomatik olarak seçilir. + Metin girmeye başladığınızda ekran klavyesini otomatik olarak değiştir + Klavyeyi açmaya çalıştığınızda son kullanılan Key Mapper dışı klavye otomatik olarak seçilir. Klavyeyi kullanmayı bıraktığınızda Key Mapper klavyeniz otomatik olarak seçilir. + Klavyeyi otomatik olarak değiştirirken ekranda bir mesaj göster + Key Mapper root iznine sahip + Yalnızca rootlu cihazlarda çalışan özellikleri/eylemleri kullanmak istiyorsanız bunu etkinleştirin. Bu özelliklerin çalışması için Key Mapper’ın root erişim yönetim uygulamanızdan (örneğin Magisk, SuperSU) root izni almış olması gerekir. + Bunu yalnızca cihazınızın rootlu olduğunu biliyor ve Key Mapper’a root izni verdiyseniz açın. + Tema seç + Açık ve koyu temalar mevcut + Bildirime dokunduğunuzda Key Mapper klavyesi ile varsayılan klavyeniz arasında geçiş yapın. + Key Mapper klavyesini aç/kapat bildirimi + Tuş eşlemelerini açarken/kapatırken klavyeyi otomatik olarak değiştir + Tuş eşlemelerinizi devam ettirdiğinizde Key Mapper klavyesini, duraklattığınızda ise varsayılan klavyenizi otomatik olarak seçin. + Ana ekran uyarılarını gizle + Ana ekranın üstündeki uyarıları gizle. + Cihaza özgü tetikleyiciler için cihaz kimliğinin ilk 5 karakterini göster + Bu, aynı ada sahip cihazları ayırt etmek için kullanışlıdır. + ABD İngilizcesine ayarlanmış klavyeleri düzelt + Bu, bir erişilebilirlik servisi etkinleştirildiğinde doğru klavye düzenine sahip olmayan klavyeleri düzeltir. Daha fazla bilgi okuyup yapılandırmak için dokunun. + ABD İngilizcesine ayarlanmış klavyeleri düzelt + Android 11’de bir hata var; bir erişilebilirlik servisi açıldığında Android, tüm harici cihazları aynı dahili sanal cihaz olarak görüyor. Bu cihazları doğru şekilde tanımlayamadığı için hangi klavye düzenini kullanacağını bilemiyor ve örneğin bir Alman klavyesi olsa bile varsayılan olarak ABD İngilizcesini kullanıyor. Aşağıdaki adımları izleyerek Key Mapper ile bu sorunu çözebilirsiniz. + 4. Cihazları seç + 1. Key Mapper GUI Klavyesini yükle (isteğe bağlı) + 1. Key Mapper Leanback Klavyesini yükle (isteğe bağlı) + 2. Key Mapper GUI Klavyesini veya Key Mapper Temel Giriş Yöntemi’ni etkinleştir + 2. Key Mapper Leanback Klavyesini veya Key Mapper Temel Giriş Yöntemi’ni etkinleştir + 3. Az önce etkinleştirdiğiniz klavyeyi kullan + (Önerilen) Bu ayar için kullanıcı kılavuzunu okuyun. + Ekstra günlüğü etkinleştir + Günlüğü görüntüle ve paylaş + Sorun bildir + Ses dosyalarını sil + Ses eylemi için kullanılabilecek ses dosyalarını sil. + İzin ver + İzin verildi + 1. Shizuku yüklü değil! Shizuku uygulamasını indirmek için dokunun. + 1. Shizuku yüklü. + 2. Shizuku başlatılmadı! Shizuku uygulamasını açmak için dokunun ve ardından nasıl başlatılacağına dair talimatlarını okuyun. + 2. Shizuku başlatıldı. + 3. Key Mapper’ın Shizuku’yu kullanma izni yok. Bu izni vermek için dokunun. + 3. Key Mapper otomatik olarak Shizuku’yu kullanacak. Key Mapper’ın hangi özelliklerinin Shizuku’yu kullandığını okumak için dokunun. + Varsayılan eşleme seçenekleri + Tuş eşlemeleriniz için varsayılan seçenekleri değiştirin. + Tüm ayarları sıfırla + TEHLİKE! Uygulamadaki tüm ayarları varsayılana sıfırla. Tuş eşlemeleriniz sıfırlanMAYACAK. + TEHLİKE! + Uygulamadaki tüm ayarları varsayılana sıfırlamak istediğinizden emin misiniz? Tuş eşlemeleriniz sıfırlanMAYACAK. Giriş ekranı ve tüm uyarı pop-up’ları tekrar görünecek. + Evet, sıfırla + Klavye seçiciyi otomatik olarak göster + Klavye seçiciyi otomatik olarak göstermeye olanak tanıyan ayarları görmek için dokunun. + Root ayarları + Bu seçenekler yalnızca rootlu cihazlarda çalışır! Rootun ne olduğunu veya cihazınızın rootlu olup olmadığını bilmiyorsanız, bunlar çalışmazsa lütfen kötü bir inceleme bırakmayın. :) + WRITE_SECURE_SETTINGS izni gerektirir + Bu seçenekler yalnızca Key Mapper WRITE_SECURE_SETTINGS iznine sahipse etkinleşir. İzni nasıl vereceğinizi öğrenmek için aşağıdaki düğmeye tıklayın. + Shizuku desteği + Shizuku, Key Mapper’ın yalnızca sistem uygulamalarının yapabileceği şeyleri yapmasını sağlayan bir uygulamadır. Örneğin, Key Mapper klavyesini kullanmanız gerekmez. Bunu nasıl kuracağınızı öğrenmek için dokunun. + Shizuku’yu kurmak için bu adımları izleyin. + Klavyeyi otomatik olarak değiştir + Bunlar gerçekten kullanışlı ayarlar ve kontrol etmeniz önerilir! + Günlük kaydı + Bu, tuş eşlemelerinize gecikme ekleyebilir, bu yüzden yalnızca uygulamayı hata ayıklamaya çalışıyorsanız veya geliştirici tarafından istenmişse açın. + Ses diyaloğunu göster + Titre + Ekranda mesaj göster + Uzun basışta tekrar titre + Ekran kapalıyken tetikleyiciyi algıla + Tekrarla + %dx + %dms sonra + her %dms’de + tekrar basılana kadar + bırakılana kadar + Tekrarla + Basılı tut + Tekrar basılana kadar basılı tut + Yeniden eşleme yapma + Bu tuş eşlemesini diğer uygulamaların tetiklemesine izin ver + Tuş eşleme kimliğini kopyala + + Erişilebilirlik + Alarm + DTMF + Müzik + Bildirimler + Zil + Sistem + Sesli arama + Normal + Titreşim + Sessiz + Ön + Arka + Alarmlar + Öncelik + Hiçbir şey + Tuş eşlemelerini duraklat + Tuş eşlemelerini devam ettir + Duraklatıldı + Çalışıyor + Servis Devre Dışı + Key Mapper erişilebilirlik servisi devre dışı + Key Mapper klavyesini aç/kapat + Ctrl + Sol Ctrl + Sağ Ctrl + Alt + Sol Alt + Sağ Alt + Shift + Sol Shift + Sağ Shift + Meta + Sol Meta + Sağ Meta + Sym + Func + Caps Lock + Num Lock + Scroll Lock + Bu eylemin çalışması için Key Mapper klavyelerinden birini kullanıyor olmanız gerekiyor! + %s paket adına sahip uygulama yüklü değil! + %s uygulaması devre dışı! + Key Mapper\'a sistem ayarlarını değiştirme izni vermeniz gerekiyor. + Bu işlem root izni gerektiriyor! + Bu eylem kamera izni gerektiriyor! + Android %s veya daha yeni bir sürüm gerektiriyor + Android %s veya daha eski bir sürüm gerektiriyor + Cihazınızda kamera bulunmuyor. + Cihazınız NFC\'yi desteklemiyor. + Cihazınızda parmak izi okuyucu bulunmuyor. + Cihazınız WiFi\'yi desteklemiyor. + Cihazınız Bluetooth\'u desteklemiyor. + Cihazınız cihaz politikası uygulamasını desteklemiyor. + Cihazınızda kamera flaşı bulunmuyor. + Cihazınızda telefon özellikleri bulunmuyor. + Klavye ayarları sayfası bulunamadı! + Key Mapper\'ın cihaz yöneticisi olması gerekiyor! + Key Mapper\'ın bu kısayolu kullanma izni yok + Uygulamanın Rahatsız Etmeyin durumunu değiştirmek için izne ihtiyacı var! + Bu eylem telefon durumunu okuma izni gerektiriyor! + WRITE_SETTINGS izin sayfası bulunamadı! + Bu uygulama kısayolu açılırken hata oluştu + Rahatsız Etmeyin erişim izni ayarları bulunamadı! + Key Mapper\'ın WRITE_SECURE_SETTINGS iznine ihtiyacı var. + Bu telefon aramasını başlatabilecek bir uygulama yok + Kamera kullanımda! + Kamera bağlantısı kesildi! + Kamera devre dışı! + Kamera hatası! + Maksimum kamera kullanımda! + Ön flaş yok + Arka flaş yok + Değişken flaş ışığı gücü desteklenmiyor + Erişilebilirlik servisinin etkinleştirilmesi gerekiyor! + Erişilebilirlik servisinin yeniden başlatılması gerekiyor! + Başlatıcınız kısayolları desteklemiyor. + Bir Key Mapper klavyesinin etkinleştirilmesi gerekiyor! + %s giriş yöntemi bulunamadı + Giriş yöntemi seçici gösterilemiyor! + Erişilebilirlik düğümü bulunamadı! + %s genel eylemi gerçekleştirilemedi! + Pil optimizasyon ayarları bulunamadı! Varsa, manuel olarak açın. + Ekstra (%s) bulunamadı! + Aynı kısıtlamaya iki kez sahip olamazsınız! + Boş olamaz! + Cihaz bulunamadı! + Boş JSON dosyası! + Dosya erişimi reddedildi! %s + Bilinmeyen G/Ç hatası! + İptal edildi! + Geçersiz numara! + En az %s olmalı! + En fazla %s olmalı! + Pil optimizasyonu açık! Key Mapper\'ın rastgele durmasını engellemek için bunu kapatın. + Bildirim erişim izni reddedildi! + Geçersiz! + Telefon araması başlatma izni reddedildi! + Bu yedeği kullanmak için Key Mapper\'ı en son sürüme güncellemeniz gerekiyor. + Sesli asistan yüklü değil! + Yetersiz izinler + Yalnızca Key Mapper klavyeleri yüklü! + Medya oynatan bir uygulama yok! + Kaynak dosya bulunamadı! %s + Hedef dosya bulunamadı! %s + Hareket girişi başarısız! + Sistem ayarı %s değiştirilemedi! + %s etkinleştirilmeli! + Giriş yöntemi değiştirilemedi! + Cihazınızda kamera uygulaması yok! + Cihazınızda asistan yok! + Cihazınızda ayarlar uygulaması yok! + Bu URL\'yi açabilecek bir uygulama yok! + Klasör değil! %s + Dosya değil! %s + Dizin bulunamadı! %s + Ses dosyası bulunamadı! + Depolama izni reddedildi! + Kaynak ve hedef aynı olamaz! + Hedefte yer kalmadı! %s + Shizuku izni reddedildi! + Shizuku başlatılmadı! + Bu dosyanın adı yok! + Geçersiz dosya. Key Mapper\'dan dışa aktarılmış bir zip olmalı. + Key Mapper\'a eşleştirilmiş Bluetooth cihazlarını görme izni vermelisiniz. + Hatalı URL. http:// kısmını unuttunuz mu? + Hassas konum okuma izni reddedildi! + Telefon aramalarını yanıtlama ve sonlandırma izni reddedildi! + Eşleştirilmiş Bluetooth cihazlarını görme izni reddedildi! + Bildirim gösterme izni reddedildi! + 2 veya daha fazla olmalı! + %d veya daha az olmalı! + 0\'dan büyük olmalı! + 0\'dan büyük olmalı! + 0\'dan büyük olmalı! + 0\'dan büyük olmalı! + %d veya daha az olmalı! + UI öğesi bulunamadı! + WiFi\'yi aç/kapat + WiFi\'yi aç + WiFi\'yi kapat + Bluetooth\'u aç/kapat + Bluetooth\'u aç + Bluetooth\'u kapat + Sesi artır + Sesi azalt + Sesi kapat + Sesi aç/kapat + Sesi aç + Ses kontrol panelini göster + Akışı artır + %s akışını artır + Akışı azalt + %s akışını azalt + Zil modlarını değiştir (Normal, Titreşim, Sessiz) + Zil modlarını değiştir (Normal, Titreşim) + Zil modunu değiştir + %s moduna geç + Rahatsız Etmeyin modunu aç/kapat + Yalnızca %s için Rahatsız Etmeyin modunu aç/kapat + Rahatsız Etmeyin modunu aç + Yalnızca %s için Rahatsız Etmeyin modunu aç + Rahatsız Etmeyin modunu kapat + Otomatik döndürmeyi aç + Otomatik döndürmeyi kapat + Otomatik döndürmeyi aç/kapat + Dikey mod + Yatay mod + Yönü değiştir + Dönüşleri sırayla değiştir + %s dönüşlerini sırayla değiştir + Mobil veriyi aç/kapat + Mobil veriyi aç + Mobil veriyi kapat + Otomatik parlaklığı aç/kapat + Otomatik parlaklığı kapat + Otomatik parlaklığı aç + Ekran parlaklığını artır + Ekran parlaklığını azalt + Bildirim panelini aç + Bildirim panelini aç/kapat + Hızlı ayarları aç + Hızlı ayarlar panelini aç/kapat + Durum çubuğunu kapat + Medya oynatmayı duraklat + Bir uygulama için medya oynatmayı duraklat + %s için medyayı duraklat + Medya oynatmayı devam ettir + Bir uygulama için medya oynatmayı devam ettir + %s için medyayı devam ettir + Medya oynatmayı oynat/duraklat + Bir uygulama için medya oynatmayı oynat/duraklat + %s için medyayı oynat/duraklat + Sonraki parça + Bir uygulama için sonraki parça + %s için sonraki parça + Önceki parça + Bir uygulama için önceki parça + %s için önceki parça + Hızlı ileri sar + Bir uygulama için hızlı ileri sar + %s için hızlı ileri sar + Tüm medya uygulamaları hızlı ileri sarmayı desteklemez. Örn. Google Play Music. + Geri sar + Bir uygulama için geri sar + %s için geri sar + Tüm medya uygulamaları geri sarmayı desteklemez. Örn. Google Play Music. + Geri dön + Ana ekrana git + Son uygulamaları aç + Menüyü aç + Bölünmüş ekranı aç/kapat + Son uygulamaya git (Son uygulamalara çift bas) + Flaş ışığını aç/kapat + Flaş ışığını aç + Flaş ışığını kapat + Flaş ışığını aç/kapat + Flaş ışığını aç/kapat (%s) + Flaş ışığını aç + Flaş ışığını aç (%s) + Flaş ışığını kapat + Flaş ışığı parlaklığını değiştir + Flaş ışığını %s artır + Flaş ışığını %s azalt + Ön flaş ışığını aç/kapat + Ön flaş ışığını aç/kapat (%s) + Ön flaş ışığını aç + Ön flaş ışığını aç (%s) + Ön flaş ışığını kapat + Ön flaş ışığı parlaklığını değiştir + Ön flaş ışığını %s artır + Ön flaş ışığını %s azalt + NFC\'yi aç + NFC\'yi kapat + NFC\'yi aç/kapat + Ekran görüntüsü al + Sesli asistanı başlat + Cihaz asistanını başlat + Kamerayı aç + Cihazı kilitle + Cihazı güvenli bir şekilde kilitle + Tekrar giriş yapmak için yalnızca PIN\'inizi kullanabilirsiniz. Parmak izi tarayıcı ve yüz tanıma devre dışı bırakılacaktır. Bu, Android Pie 9.0 öncesi root olmayan cihazları kilitlemenin bulduğum tek güvenilir yoludur. + Cihazı uyut/uyandır + Ekran kapalıyken tetikleyiciyi algılama seçeneğini açmanız gerekiyor! + Hiçbir şey yapma + İmleci sona taşı + Bu eylem bazı uygulamalarda beklendiği gibi çalışmayabilir. + Klavyeyi aç/kapat + Bu eylem yalnızca klavyenin gösterilmesi gereken bir giriş alanına dokunduğunuzda çalışır. + Klavyeyi göster + Klavyeyi gizle + Klavye seçiciyi göster + Klavyeyi değiştir + %s klavyesine geç + Kes + Kopyala + Yapıştır + İmlecin olduğu kelimeyi seç + Ayarları aç + Güç menüsünü göster + Uçak modunu aç/kapat + Uçak modunu aç + Uçak modunu devre dışı bırak + Uygulamayı başlat + Bazı cihazlar, uygulamaların arka planda başka uygulamaları başlatabilmesi için izne ihtiyaç duyar. Web sitemizdeki talimatları görmek için \"Daha fazla oku\"ya dokunun. + Daha fazla oku + Yoksay + Uyarı! + Uygulama kısayolunu başlat + Tuş kodunu gir + Tuş olayını gir + Ekrana dokun + Ekranı kaydır + Ekranı sıkıştır + Metin gir + URL aç + Intent gönder + Telefon araması başlat + Telefon aramasını cevapla + Telefon aramasını sonlandır + Ses çal + En son bildirimi kaldır + Tüm bildirimleri kaldır + Cihaz kontrol ekranı + HTTP isteği + HTTP Yöntemi + Açıklama + Boş bırakılamaz! + URL + Boş bırakılamaz! + Hatalı URL. http:// kısmını unuttunuz mu? + İstek gövdesi (opsiyonel) + Yetkilendirme başlığı (opsiyonel) + Gerekirse \'Bearer\' ön ekini kullanın + Uygulama öğesiyle etkileşime geç + Key Mapper, menüler, sekmeler, düğmeler ve onay kutuları gibi uygulama öğelerini algılayabilir ve bunlarla etkileşime girebilir. Key Mapper\'ın ne yapmak istediğinizi bilmesi için, uygulama öğesiyle etkileşiminizi kaydetmeniz gerekmektedir. + Kaydetmeye başla + Kaydetmeyi durdur (%s dakika kaldı) + + %d öğe seçildi + %d öğe seçildi + + Tekrar kaydet + Uygulama öğesini seçin + Tuş haritanızın etkileşime geçmesini istediğiniz öğeyi seçin. + Aradığınızı bulamıyor musunuz? + Tüm uygulamalar uyumlu değildir. Uyumlu olmayan uygulamalar için bunun yerine Ekrana Dokun eylemini deneyebilirsiniz. + Olası etkileşimler + UI öğesiyle nasıl etkileşim kurmak istediğinizi seçin. + Etkileşim türünü filtrele + Herhangi biri + Dokun + Dokun ve basılı tut + Odakla + Seç + İleri kaydır + Geriye kaydır + Genişlet + Daralt + Bilinmeyen: %d + Etkileşim detayları + Açıklama + Uygulama + Metin / içerik açıklaması + Sınıf adı + Kaynak kimliğini görüntüle + Özgün kimlik + Etkileşim türü + Navigasyon + Ses + Medya + Klavye + Uygulamalar + Giriş + Kamera ve Ses + Bağlantı + İçerik + Arayüz + Telefon + Ekran + Bildirimler + Boolean + Boolean dizisi + Integer + Tamsayı dizisi + Dize + Dize dizisi + Uzun + Uzun dizi + Bayt + Bayt dizisi + Çift + Çift dizi + Karakter + Karakter dizisi + Kayan Nokta + Kayan Nokta dizisi + Kısa + Kısa dizi + Yalnızca \"true\" veya \"false\" olabilir + \"true\" ve \"false\" içeren virgülle ayrılmış bir liste. Örn. true,false,true + Java programlama dilinde geçerli bir Tamsayı. + Java programlama dilinde geçerli Tamsayıların virgülle ayrılmış bir listesi. Örn. 100,399 + Virgülle ayrılmış bir liste. Örn. kategori1,kategori2 + Herhangi bir metin. + Dizelerin virgülle ayrılmış bir listesi. Örn. dize1,dize2 + Java programlama dilinde geçerli bir Uzun. + Java programlama dilinde geçerli Uzunların virgülle ayrılmış bir listesi. Örn. 102302234234234,399083423234429 + Java programlama dilinde geçerli bir Bayt. + Java programlama dilinde geçerli Baytların virgülle ayrılmış bir listesi. Örn. 123,3 + Java programlama dilinde geçerli bir Çift. + Java programlama dilinde geçerli Çiftlerin virgülle ayrılmış bir listesi. Örn. 1.0,3.234 + Java programlama dilinde geçerli bir Karakter. Örn. \'a\' veya \'b\' + Java programlama dilinde geçerli Karakterlerin virgülle ayrılmış bir listesi. Örn. a,b,c + Java programlama dilinde geçerli bir Kayan Nokta. Örn. 3.145 + Java programlama dilinde geçerli Kayan Noktaların virgülle ayrılmış bir listesi. Örn. 1241.123 + Java programlama dilinde geçerli bir Kısa. Örn. 2342 + Java programlama dilinde geçerli Kısa sayıların virgülle ayrılmış bir listesi. Örn. 3242,12354 + Intent bayrakları bit bayrakları olarak saklanır. Bu bayraklar Intent\'in nasıl işleneceğini değiştirir. Bir Etkinlik Intent\'i için bu alan boş bırakılırsa, Key Mapper varsayılan olarak FLAG_ACTIVITY_NEW_TASK kullanır. Daha fazla bilgi için Android geliştirici belgelerini görmek üzere \'dokümanlar\'a dokunun. + Hızlı Başlangıç Kılavuzu + Eğer takılırsanız Hızlı Başlangıç Kılavuzu\'na göz atın. + GitHub + Web Sitesi + Çeviriler + Sürüm %s + Oyla + Değişiklik Günlüğü + Discord + Sıkıcı şeyler + Lisans + Bu uygulamanın açık kaynak lisansı. + Gizlilik Politikası + Kişisel bilgi toplamıyoruz ama işte bunu belirten bir gizlilik politikası. + Ekibimiz + Geliştirici + Kullanıcı Deneyimi Tasarımcısı + Çevirmen (Lehçe) + Çevirmen (Çekçe) + Çevirmen (İspanyolca) + Key Mapper: Yan Tuş + Herhangi bir asistan + Yan tuş/güç düğmesi + Sesli asistan + Gelişmiş tetikleyiciler + Geliştirici, reklamların sürdürülebilir veya kullanıcı dostu bir gelir modeli olduğuna inanmıyor, bu nedenle bu ücretli tetikleyiciler geliştirmeyi desteklemeye yardımcı oluyor ❤️. Ayrıca öncelikli destek de alacaksınız. + Yan tuş ve Asistan tetikleyicisi + Yan tuşunuzu, güç düğmenizi veya cihaz asistanınızı yeniden eşleyebileceğinizi biliyor muydunuz? Asistanı veya güç menüsünü başlatmak yerine, cihazınız seçtiğiniz bir eylemi gerçekleştirebilir. Ekran kapalıyken bile çalışır! + Yan tuş tetikleyici özelliğini satın almanız gerekiyor. + Daha fazla bilgi + Kayan düğmeleri satın almanız gerekiyor. + Düğme silindi. + Kayan düğme + Yan tuş tetikleyicisini ayarla + Dikkat! + Bu tetikleyiciyi nasıl ayarlayacağınızı açıklayan web sitemizdeki talimatları okumanız gerekiyor. Key Mapper size rehberlik etmeyecek. + Talimatları oku + Tetikleyici türünü seç + Satın alma doğrulanamıyor. İnternet bağlantınız var mı? + Kilidi aç (%s) + Kullan + Yükleniyor… + Fiyatı tekrar almayı dene + Satın alma iptal edildi. + Bu, yalnızca Google Play\'den Key Mapper indirilerek satın alınabilen ücretli bir özellik gerektiriyor. + Ağ hatası oluştu. İnternet bağlantınız var mı? + Bu ürün bulunamadı. + Google Play bir hata ile karşılaştı. + Google Play\'den satın alımın başarılı olduğuna dair onay bekliyoruz. Kartınız başarıyla ücretlendirildiğinde Key Mapper uygulamasını yeniden açın. + Geçersiz satın alma. + Ödeme bekleniyor + Bir şeyler ters gitti 😕 + Tekrar dene + Geliştiriciyle iletişime geç + Yan tuş tetikleyici özelliğini satın almanız gerekiyor! Tuş eşlemesine dokunun ve ardından \'Gelişmiş tetikleyiciler\'e tıklayarak satın alın. + Kayan düğmeler özelliğini satın almanız gerekiyor! Tuş eşlemesine dokunun ve ardından \'Gelişmiş tetikleyiciler\'e tıklayarak satın alın. + Uygulamayı desteklediğiniz için teşekkürler ❤️! + Satın alma işleminiz başarılı oldu. Key Mapper\'ın ücretli bir kullanıcısı olarak uygulamayı kullanmanıza yardımcı olmak için öncelikli destek alacaksınız. Artık bu sayfada geliştiriciyle iletişime geçmek için bir düğme var. + Gelişmiş tetikleyiciler ücretli bir özelliktir ancak siz FOSS yapısını indirdiniz ve bu yapı Google Play faturalandırmasını içermiyor. Bu özelliğe erişmek için lütfen Key Mapper\'ı Google Play\'den indirin. + Play sürümünü indir + DPAD düğmelerini yeniden eşlemek ister misiniz? + Aşağıdaki adımları izleyerek Key Mapper GUI Klavyesini ayarlamanız gerekiyor. + 1. Klavye uygulamasını yükle + Yükle + Yüklendi + 2. Klavyeyi etkinleştir + Etkinleştir + Etkinleştirildi + 3. Klavyeyi kullan + Klavyeyi değiştir + Klavye seçildi + Kurulum tamamlandı! \'Bitti\'ye dokunun ve DPAD tetikleyiciniz çalışmalı. + Düğme algılanmadı mı? + Erişilebilirlik servisi yerine tetikleyicinizi kaydetmek için Key Mapper GUI Klavye uygulamasını deneyebilirsiniz. + Kurulum tamamlandı! \'Bitti\'ye dokunun ve tetikleyicinizi tekrar kaydetmeyi deneyin. Eğer çalışmazsa Android bunu yeniden eşlemeye izin vermiyor demektir 🫤. + Dokun + bu menüyü göster/gizle. + Düğmeleri herhangi bir uygulamada, hatta kilit ekranında bile yerleştirebilirsiniz. + Kapat + Yardım + Düzeni değiştir + Düğme ekle + Geri dön + Düğmeleri gizle + Düğmeleri göster + Sil + Yapılandır + Tetikleyici olarak kullan + Sil + Düğme metni (İpucu: emoji kullanın) + Düğme boyutu: + Kenar opaklığı: + Arka plan opaklığı: + İptal + Bitti + Düğmenin metni olmalı! + Kayan düğmeler + Kayan düğmeler istediğiniz uygulamaların üzerinde görünür. Gerçek düğmeler gibi çalışırlar ve onları istediğiniz gibi yerleştirebilir, stil verebilir ve eşleyebilirsiniz. + Kayan düğme %s (%s) + Silinen kayan düğme + Kısıtlamalar + Bu düğmenin yalnızca bazı uygulamalarda ekranda olmasını ister misiniz? Bu tuş eşlemesi için “Kısıtlamalar” sekmesinde bir “Ön plandaki uygulama” kısıtlaması ekleyin. + Bir düzen seçin + Geri + Yardım + Yardım mı lazım? + Düğme oluştur + Düğme seç + Çık + Tetikleyici olarak kullanmadan önce bir kayan düğme oluşturmanız gerekiyor. + Tetikleyici olarak kullanmak için bir kayan düğme seçmeniz gerekiyor. + Düğmeyi yapılandır + Düzeni düzenle + Android 11 veya daha yeni bir sürüm gerektiriyor. + Yeterince düğmeniz yok mu? Artık kendi düğmelerinizi yapabilirsiniz! + Kayan düğmeler istediğiniz uygulamaların üzerinde görünür. Gerçek düğmeler gibi çalışırlar ve onları istediğiniz gibi yerleştirebilir, stil verebilir ve eşleyebilirsiniz. + Tetikleyici + Eylemler + Kısıtlamalar + Seçenekler + Tuş eşlemeleri + Kayan düğmeler + Yeni tuş eşlemesi + Yeni düzen + Bir tuş eşlemesini yapılandırmak için dokunun.\nDaha fazla seçenek için uzun basın. + Bir tuş eşlemesi oluşturun! + Yeterince düğmeniz yok mu? + Kendi düğmelerinizi yapın! + Kayan düğmeler istediğiniz uygulamaların üzerinde görünür. Gerçek düğmeler gibi çalışırlar ve onları istediğiniz gibi yerleştirebilir, stil verebilir ve eşleyebilirsiniz. + Bu sayfayı gizle + Key Mapper\'ı desteklediğiniz için teşekkürler ❤️! + İlk düzeninizi oluşturun! + Düzenler, kayan düğmeleri gruplar halinde organize etmenizi sağlar. Hangi düğmelerin gösterilebileceğini etkilemez. Herhangi bir düzendeki herhangi bir düğme aynı anda gösterilebilir. + %s düzeninin adını değiştir + %s düzenini sil + Kayan düğmeler + Düğme yok + Düğmeleri düzenle + Düğme ekle + Düzen adını değiştir + Kaydet + Ad boş olamaz! + Ad benzersiz olmalı! + %s sil + Bu kayan düğme düzenini silmek istediğinizden emin misiniz? + Evet, sil + İptal + Kayan düzenleri gizle + Kayan düğmeleri, bir tetikleyici oluştururken Gelişmiş Tetikleyiciler düğmesinde bulabilirsiniz. + Kayan düğmeler + Menü + Sırala + Daha fazla + Yardım + Tümünü seç + Tüm seçimi kaldır + Seçimi durdur + Bir grup yukarı çık + Duraklatıldı + + 1 uyarı + %d uyarı + + Çalışıyor + Ayarlar + Grubu sil + Hakkında + Tümünü dışa aktar + İçe aktar + Klavye seç + İçe aktarılıyor… + İçe aktarma başarılı! + Dışa aktarılıyor… + Başarısız: %s + Dosya yükleniyor… + İçe aktarma başarılı! + İçe aktarılıyor… + Hata + Uygulamayı aç + + 1 tuş eşlemesi içe aktarılıyor + %d tuş eşlemesi içe aktarılıyor + + Mevcut tuş eşlemelerinizin tümünü değiştirmek mi yoksa listeye eklemek mi istiyorsunuz? + İptal + Kapat + Ekle + Değiştir + Çoğalt + Sil + Dışa aktar + Etkin + Devre dışı + Karışık + Gruba taşı + + 1 tuş eşlemesini sil + %d tuş eşlemesini sil + + Bu tuş eşlemelerini silmek istediğinizden emin misiniz? + Evet, sil + İptal + Dosyalara kaydet + Yeni grup + Yeni alt grup + Tümünü görüntüle + Gizle + Grup kısıtlamaları + Yeni kısıtlama + Grup kısıtlamasını sil + Bu grup + Kaldır + Düzenle + Tetikleyici tuş seçenekleri + Cihaz + Asistan türü + Parmak izi hareket türü + Tıklama türü + Kayan düğme kullan + Parmak izi hareketi kullan + Yan tuş tetikleyicisi kullan + Kayan düğme + Yan tuş tetikleyicisi + Parmak izi hareketi + Kayan düğme: %s + Parmak izi okuyucuda yukarı kaydır + Parmak izi okuyucuda aşağı kaydır + Parmak izi okuyucuda sola kaydır + Parmak izi okuyucuda sağa kaydır + Gelişmiş tetikleyiciler + Kaldır + Düzenle + Test et + Tetiklendiğinde tuş eşlemesinin ne yapacağını ayarlamak için eylemler ekleyin. + Son kullanılan eylemler + Eylem seçenekleri + Sıfırla + Varsayılan: %s + Bir eylem seçin + Ara… + Burada hiçbir şey yok! + Eylemi sil + Bu eylemi silmek istediğinizden emin misiniz? + Evet, sil + İptal + Tuş eşlemesi tetiklendiğinde bu eylemleri çalıştır: + Taraf seç + Parlaklık + Minimum + Yarı + Maksimum + Test et + Android 13 veya daha yeni bir sürüm gerektiriyor. + Bu cihaz parlaklık değişikliğine izin vermiyor. + Parlaklık değişikliği + Desteklenmiyor + Tuş eşlemelerinin yalnızca belirli durumlarda çalışmasını istiyorsanız kısıtlamalar ekleyin. + Son kullanılan kısıtlamalar + Kaldır + Kısıtlamayı sil + Bu kısıtlamayı silmek istediğinizden emin misiniz? + Evet, sil + İptal + Mantık modu + Bir kısıtlama seçin + Bu tuş eşlemesi yalnızca şu durumlarda çalışır: + Adsız grup + Grup adını düzenle + Grup adını kaydet + Ad benzersiz olmalı! + Ana Sayfa + Grubu sil + %s grubunu sil + Bu grubu silmek istediğinizden emin misiniz? Bu gruptaki ve alt gruplarındaki tüm tuş eşlemeleri de silinecek! + Evet, sil + İptal + + +%d devralınan kısıtlamalar + +%d devralınan kısıtlamalar + diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/ar/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ar/short_description.txt b/fastlane/metadata/android/ar/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/ar/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ar/title.txt b/fastlane/metadata/android/ar/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/ar/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/full_description.txt b/fastlane/metadata/android/cs_CZ/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/cs_CZ/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/short_description.txt b/fastlane/metadata/android/cs_CZ/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/cs_CZ/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/title.txt b/fastlane/metadata/android/cs_CZ/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/cs_CZ/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/full_description.txt b/fastlane/metadata/android/de_DE/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/de_DE/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/short_description.txt b/fastlane/metadata/android/de_DE/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/de_DE/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/title.txt b/fastlane/metadata/android/de_DE/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/de_DE/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/full_description.txt b/fastlane/metadata/android/es_ES/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/es_ES/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/short_description.txt b/fastlane/metadata/android/es_ES/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/es_ES/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/title.txt b/fastlane/metadata/android/es_ES/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/es_ES/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/full_description.txt b/fastlane/metadata/android/fr_FR/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/fr_FR/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/short_description.txt b/fastlane/metadata/android/fr_FR/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/fr_FR/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/title.txt b/fastlane/metadata/android/fr_FR/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/fr_FR/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/full_description.txt b/fastlane/metadata/android/hu_HU/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/hu_HU/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/short_description.txt b/fastlane/metadata/android/hu_HU/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/hu_HU/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/title.txt b/fastlane/metadata/android/hu_HU/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/hu_HU/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/full_description.txt b/fastlane/metadata/android/id_ID/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/id_ID/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/short_description.txt b/fastlane/metadata/android/id_ID/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/id_ID/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/title.txt b/fastlane/metadata/android/id_ID/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/id_ID/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/full_description.txt b/fastlane/metadata/android/ka_GE/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/ka_GE/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/short_description.txt b/fastlane/metadata/android/ka_GE/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/ka_GE/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/title.txt b/fastlane/metadata/android/ka_GE/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/ka_GE/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/full_description.txt b/fastlane/metadata/android/ko_KR/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/ko_KR/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/short_description.txt b/fastlane/metadata/android/ko_KR/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/ko_KR/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/title.txt b/fastlane/metadata/android/ko_KR/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/ko_KR/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/full_description.txt b/fastlane/metadata/android/pl_PL/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/pl_PL/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/short_description.txt b/fastlane/metadata/android/pl_PL/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/pl_PL/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/title.txt b/fastlane/metadata/android/pl_PL/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/pl_PL/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/full_description.txt b/fastlane/metadata/android/pt_BR/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/pt_BR/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/short_description.txt b/fastlane/metadata/android/pt_BR/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/pt_BR/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/title.txt b/fastlane/metadata/android/pt_BR/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/pt_BR/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/full_description.txt b/fastlane/metadata/android/ru_RU/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/ru_RU/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/short_description.txt b/fastlane/metadata/android/ru_RU/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/ru_RU/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/title.txt b/fastlane/metadata/android/ru_RU/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/ru_RU/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/sk/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/sk/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/sk/title.txt b/fastlane/metadata/android/sk/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/sk/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/tr_TR/full_description.txt b/fastlane/metadata/android/tr_TR/full_description.txt new file mode 100644 index 0000000000..b5be52ef5e --- /dev/null +++ b/fastlane/metadata/android/tr_TR/full_description.txt @@ -0,0 +1,55 @@ +# Klavyenizde veya oyun kumandanızda özel makrolar oluşturun, herhangi bir uygulamada ekran üstü düğmeler yapın ve ses düğmelerinizden yeni işlevler açın! + +Key Mapper, çok çeşitli düğme ve tuşları destekler*: + +- TÜM telefon düğmeleriniz (ses VE yan tuş) +- Oyun kumandaları (D-pad, ABXY ve çoğu diğer tuşlar) +- Klavyeler +- Kulaklık setleri ve kulaklıklar +- Parmak izi sensörü + +Yeterli tuş yok mu? Kendi ekran üstü düğme düzenlerinizi tasarlayın ve bunları gerçek tuşlar gibi yeniden atayın! + + +## Ne tür kısayollar oluşturabilirim? +-------------------------- + +100'den fazla bireysel eylemle, sınır gökyüzüdür. +Ekran dokunuşları ve hareketleri, klavye girişleri, uygulama açma, medya kontrolü ve hatta diğer uygulamalara doğrudan intent gönderme ile karmaşık makrolar oluşturun. + + +## Ne kadar kontrole sahibim? +--------------------------- + +TETİKLEYİCİLER: Bir tuş haritasını nasıl tetikleyeceğinize siz karar verirsiniz. Uzun basma, çift basma, istediğiniz kadar basma! Farklı cihazlardaki tuşları birleştirin ve hatta ekran üstü düğmelerinizi de dahil edin. + +EYLEMLER: Yapmak istediğiniz şey için özel makrolar tasarlayın. 100'den fazla eylemi birleştirin ve her biri arasındaki gecikmeyi seçin. Yavaş görevleri otomatikleştirmek ve hızlandırmak için tekrarlayan eylemler ayarlayın. + +KISITLAMALAR: Tuş haritalarının ne zaman çalışacağını ve ne zaman çalışmayacağını siz seçersiniz. Sadece belirli bir uygulamada mı gerekli? Ya da medya oynatılırken mi? Kilit ekranınızda mı? Maksimum kontrol için tuş haritalarınızı kısıtlayın. + +* Çoğu cihaz zaten desteklenmektedir ve zamanla yeni cihazlar eklenmektedir. Sizin için çalışmıyorsa bize bildirin, cihazınıza öncelik verebiliriz. + +Şu anda desteklenmeyen: + - Fare düğmeleri + - Oyun kumandalarındaki joystick ve tetikler (LT, RT) + + +Güvenlik ve erişilebilirlik hizmetleri +--------------------------- + +Bu uygulama, odaktaki uygulamayı algılamak ve tuş basımlarını kullanıcı tarafından tanımlanan tuş haritalarına uyarlamak için Android Erişilebilirlik API’sini kullanan Key Mapper Erişilebilirlik hizmetimizi içermektedir. Ayrıca, diğer uygulamaların üzerinde yardımcı Floating Button (Yüzen Düğme) katmanları çizmek için de kullanılmaktadır. + +Erişilebilirlik hizmetini çalıştırmayı kabul ettiğinizde, uygulama cihazınızı kullanırken tuş vuruşlarını izleyebilecektir. Ayrıca, uygulamada bu hareketleri kullanıyorsanız, kaydırma ve yakınlaştırma/daraltma hareketlerini de taklit edecektir. + +Herhangi bir kullanıcı verisi toplamayacak veya herhangi bir veriyi göndermek üzere internete bağlanmayacaktır. + +Erişilebilirlik hizmetimiz, yalnızca kullanıcı cihazındaki fiziksel bir tuşa bastığında tetiklenir. Kullanıcı, sistem erişilebilirlik ayarlarından bu hizmeti istediği zaman kapatabilir. + +Discord topluluğumuza gelip merhaba deyin! +www.keymapper.club + +Kodu kendiniz görün! (Açık kaynak) +code.keymapper.club + +Belgeleri okuyun: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/tr_TR/short_description.txt b/fastlane/metadata/android/tr_TR/short_description.txt new file mode 100644 index 0000000000..9dbca48bd4 --- /dev/null +++ b/fastlane/metadata/android/tr_TR/short_description.txt @@ -0,0 +1 @@ +HER ŞEY için kısayollar oluşturun! Ses, güç, klavye veya kayan düğmeleri yeniden atayın! \ No newline at end of file diff --git a/fastlane/metadata/android/tr_TR/title.txt b/fastlane/metadata/android/tr_TR/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/tr_TR/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/uk/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/uk/short_description.txt b/fastlane/metadata/android/uk/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/uk/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/uk/title.txt b/fastlane/metadata/android/uk/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/uk/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/vi/full_description.txt b/fastlane/metadata/android/vi/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/vi/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/vi/short_description.txt b/fastlane/metadata/android/vi/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/vi/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/vi/title.txt b/fastlane/metadata/android/vi/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/vi/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/full_description.txt b/fastlane/metadata/android/zh_CN/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/zh_CN/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/short_description.txt b/fastlane/metadata/android/zh_CN/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/zh_CN/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/title.txt b/fastlane/metadata/android/zh_CN/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/zh_CN/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/full_description.txt b/fastlane/metadata/android/zh_TW/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/zh_TW/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/short_description.txt b/fastlane/metadata/android/zh_TW/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/zh_TW/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/title.txt b/fastlane/metadata/android/zh_TW/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/zh_TW/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file From 5e18b3e25b60f3537110323bb2121fc957ff923f Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 8 May 2025 14:48:51 +0200 Subject: [PATCH 24/95] #257 add option to show additional elements that dumps all the elements from the window --- .../20.json | 460 ++++++++++++++++++ .../uielement/ChooseUiElementScreen.kt | 24 +- .../uielement/InteractUiElementScreen.kt | 1 + .../uielement/InteractUiElementViewModel.kt | 26 +- .../actions/uielement/NodeInteractionType.kt | 1 - .../sds100/keymapper/backup/BackupManager.kt | 3 + .../sds100/keymapper/data/db/AppDatabase.kt | 5 +- .../data/db/dao/AccessibilityNodeDao.kt | 3 + .../data/entities/AccessibilityNodeEntity.kt | 15 + .../data/migration/AutoMigration19To20.kt | 5 + .../AccessibilityNodeRecorder.kt | 69 ++- .../BaseAccessibilityServiceController.kt | 29 +- .../accessibility/IAccessibilityService.kt | 1 + .../accessibility/MyAccessibilityService.kt | 10 + app/src/main/res/values/strings.xml | 4 +- 15 files changed, 615 insertions(+), 41 deletions(-) create mode 100644 app/schemas/io.github.sds100.keymapper.data.db.AppDatabase/20.json create mode 100644 app/src/main/java/io/github/sds100/keymapper/data/migration/AutoMigration19To20.kt diff --git a/app/schemas/io.github.sds100.keymapper.data.db.AppDatabase/20.json b/app/schemas/io.github.sds100.keymapper.data.db.AppDatabase/20.json new file mode 100644 index 0000000000..3ee2ff236a --- /dev/null +++ b/app/schemas/io.github.sds100.keymapper.data.db.AppDatabase/20.json @@ -0,0 +1,460 @@ +{ + "formatVersion": 1, + "database": { + "version": 20, + "identityHash": "f2f5eac59b7bdee472c0dd7ff9bae4b2", + "entities": [ + { + "tableName": "keymaps", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `trigger` TEXT NOT NULL, `action_list` TEXT NOT NULL, `constraint_list` TEXT NOT NULL, `constraint_mode` INTEGER NOT NULL, `flags` INTEGER NOT NULL, `is_enabled` INTEGER NOT NULL, `uid` TEXT NOT NULL, `group_uid` TEXT, FOREIGN KEY(`group_uid`) REFERENCES `groups`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "trigger", + "columnName": "trigger", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actionList", + "columnName": "action_list", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "constraintList", + "columnName": "constraint_list", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "constraintMode", + "columnName": "constraint_mode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "flags", + "columnName": "flags", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isEnabled", + "columnName": "is_enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "groupUid", + "columnName": "group_uid", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_keymaps_uid", + "unique": true, + "columnNames": [ + "uid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_keymaps_uid` ON `${TABLE_NAME}` (`uid`)" + } + ], + "foreignKeys": [ + { + "table": "groups", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "group_uid" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "fingerprintmaps", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `action_list` TEXT NOT NULL, `constraint_list` TEXT NOT NULL, `constraint_mode` INTEGER NOT NULL, `extras` TEXT NOT NULL, `flags` INTEGER NOT NULL, `is_enabled` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "actionList", + "columnName": "action_list", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "constraintList", + "columnName": "constraint_list", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "constraintMode", + "columnName": "constraint_mode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "extras", + "columnName": "extras", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "flags", + "columnName": "flags", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isEnabled", + "columnName": "is_enabled", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "log", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `time` INTEGER NOT NULL, `severity` INTEGER NOT NULL, `message` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "severity", + "columnName": "severity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "floating_layouts", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`uid`))", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uid" + ] + }, + "indices": [ + { + "name": "index_floating_layouts_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_floating_layouts_name` ON `${TABLE_NAME}` (`name`)" + } + ] + }, + { + "tableName": "floating_buttons", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` TEXT NOT NULL, `layout_uid` TEXT NOT NULL, `text` TEXT NOT NULL, `button_size` INTEGER NOT NULL, `x` INTEGER NOT NULL, `y` INTEGER NOT NULL, `orientation` TEXT NOT NULL, `display_width` INTEGER NOT NULL, `display_height` INTEGER NOT NULL, `border_opacity` REAL, `background_opacity` REAL, PRIMARY KEY(`uid`), FOREIGN KEY(`layout_uid`) REFERENCES `floating_layouts`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "layoutUid", + "columnName": "layout_uid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "buttonSize", + "columnName": "button_size", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "x", + "columnName": "x", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "y", + "columnName": "y", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "orientation", + "columnName": "orientation", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayWidth", + "columnName": "display_width", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "displayHeight", + "columnName": "display_height", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "borderOpacity", + "columnName": "border_opacity", + "affinity": "REAL" + }, + { + "fieldPath": "backgroundOpacity", + "columnName": "background_opacity", + "affinity": "REAL" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uid" + ] + }, + "indices": [ + { + "name": "index_floating_buttons_layout_uid", + "unique": false, + "columnNames": [ + "layout_uid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_floating_buttons_layout_uid` ON `${TABLE_NAME}` (`layout_uid`)" + } + ], + "foreignKeys": [ + { + "table": "floating_layouts", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "layout_uid" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "groups", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` TEXT NOT NULL, `name` TEXT NOT NULL, `constraints` TEXT NOT NULL, `constraint_mode` INTEGER NOT NULL, `parent_uid` TEXT, `last_opened_date` INTEGER, PRIMARY KEY(`uid`), FOREIGN KEY(`parent_uid`) REFERENCES `groups`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "constraintList", + "columnName": "constraints", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "constraintMode", + "columnName": "constraint_mode", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "parentUid", + "columnName": "parent_uid", + "affinity": "TEXT" + }, + { + "fieldPath": "lastOpenedDate", + "columnName": "last_opened_date", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "uid" + ] + }, + "foreignKeys": [ + { + "table": "groups", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "parent_uid" + ], + "referencedColumns": [ + "uid" + ] + } + ] + }, + { + "tableName": "accessibility_nodes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `package_name` TEXT NOT NULL, `text` TEXT, `content_description` TEXT, `class_name` TEXT, `view_resource_id` TEXT, `unique_id` TEXT, `actions` INTEGER NOT NULL, `interacted` INTEGER NOT NULL DEFAULT false, `tooltip` TEXT DEFAULT NULL, `hint` TEXT DEFAULT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packageName", + "columnName": "package_name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT" + }, + { + "fieldPath": "contentDescription", + "columnName": "content_description", + "affinity": "TEXT" + }, + { + "fieldPath": "className", + "columnName": "class_name", + "affinity": "TEXT" + }, + { + "fieldPath": "viewResourceId", + "columnName": "view_resource_id", + "affinity": "TEXT" + }, + { + "fieldPath": "uniqueId", + "columnName": "unique_id", + "affinity": "TEXT" + }, + { + "fieldPath": "actions", + "columnName": "actions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "interacted", + "columnName": "interacted", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "tooltip", + "columnName": "tooltip", + "affinity": "TEXT", + "defaultValue": "NULL" + }, + { + "fieldPath": "hint", + "columnName": "hint", + "affinity": "TEXT", + "defaultValue": "NULL" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f2f5eac59b7bdee472c0dd7ff9bae4b2')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt index 612cb913af..e38a7bf080 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.util.State +import io.github.sds100.keymapper.util.ui.compose.CheckBoxText import io.github.sds100.keymapper.util.ui.compose.KeyMapperDropdownMenu import io.github.sds100.keymapper.util.ui.compose.SearchAppBarActions @@ -59,6 +60,7 @@ fun ChooseElementScreen( onQueryChange: (String) -> Unit = {}, onClickElement: (Long) -> Unit = {}, onSelectInteractionType: (NodeInteractionType?) -> Unit = {}, + onAdditionalElementsCheckedChange: (Boolean) -> Unit = {}, ) { var interactionTypeExpanded by rememberSaveable { mutableStateOf(false) } @@ -144,10 +146,25 @@ fun ChooseElementScreen( is State.Data -> { val listItems = state.data.listItems + CheckBoxText( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + text = stringResource(R.string.action_interact_ui_element_checkbox_additional_elements), + isChecked = state.data.showAdditionalElements, + onCheckedChange = onAdditionalElementsCheckedChange, + ) + + Spacer(modifier = Modifier.height(8.dp)) + if (listItems.isEmpty()) { - EmptyList(modifier = Modifier.fillMaxSize()) + EmptyList( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + ) } else { - KeyMapperDropdownMenu( + KeyMapperDropdownMenu( modifier = Modifier.padding(horizontal = 16.dp), expanded = interactionTypeExpanded, onExpandedChange = { interactionTypeExpanded = it }, @@ -309,6 +326,7 @@ private fun Empty() { listItems = emptyList(), interactionTypes = emptyList(), selectedInteractionType = null, + showAdditionalElements = false, ), ), query = "Key Mapper", @@ -343,6 +361,7 @@ private fun Loaded() { NodeInteractionType.LONG_CLICK, NodeInteractionType.SCROLL_FORWARD, ), + interacted = true, ), ) @@ -354,6 +373,7 @@ private fun Loaded() { NodeInteractionType.LONG_CLICK to "Tap and hold", ), selectedInteractionType = null, + showAdditionalElements = true, ) KeyMapperTheme { diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt index 482b679396..0c0ec02e9e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt @@ -161,6 +161,7 @@ fun InteractUiElementScreen( navController.popBackStack(route = DEST_LANDING, inclusive = false) }, onSelectInteractionType = viewModel::onSelectInteractionTypeFilter, + onAdditionalElementsCheckedChange = viewModel::onAdditionalElementsCheckedChanged, ) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt index 80d255273b..dd1a267519 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt @@ -139,13 +139,20 @@ class InteractUiElementViewModel( private val selectedInteractionTypeFilter = MutableStateFlow(null) + private val showAdditionalElements: MutableStateFlow = MutableStateFlow(false) + private val filteredElementListItems = combine( elementListItems, elementSearchQuery, selectedInteractionTypeFilter, - ) { state, query, interactionType -> + showAdditionalElements, + ) { state, query, interactionType, showAdditionalElements -> state.mapData { listItems -> listItems.filter { model -> + if (!showAdditionalElements && !model.interacted) { + return@filter false + } + if (interactionType != null && !model.interactionTypes.contains(interactionType)) { return@filter false } @@ -166,7 +173,8 @@ class InteractUiElementViewModel( filteredElementListItems, interactionTypesFilterItems, selectedInteractionTypeFilter, - ) { listItemsState, interactionTypesState, selectedInteractionType -> + showAdditionalElements, + ) { listItemsState, interactionTypesState, selectedInteractionType, showAdditionalElements -> val listItems = listItemsState.dataOrNull() ?: return@combine State.Loading val interactionTypes = interactionTypesState.dataOrNull() ?: return@combine State.Loading @@ -174,6 +182,7 @@ class InteractUiElementViewModel( listItems = listItems, interactionTypes = interactionTypes, selectedInteractionType = selectedInteractionType, + showAdditionalElements = showAdditionalElements, ) State.Data(newState) }.stateIn(viewModelScope, SharingStarted.Lazily, State.Loading) @@ -288,6 +297,10 @@ class InteractUiElementViewModel( } } + fun onAdditionalElementsCheckedChanged(checked: Boolean) { + showAdditionalElements.update { checked } + } + private suspend fun startRecording() { useCase.startRecording().onFailure { error -> if (error == Error.AccessibilityServiceDisabled) { @@ -325,9 +338,10 @@ class InteractUiElementViewModel( nodeViewResourceId = resourceIdText, nodeText = node.text ?: node.contentDescription, nodeClassName = node.className, - nodeUniqueId = node.uniqueId?.toString(), + nodeUniqueId = node.uniqueId, interactionTypesText = node.actions.joinToString { getInteractionTypeString(it) }, interactionTypes = node.actions, + interacted = node.interacted, ) } @@ -352,7 +366,6 @@ class InteractUiElementViewModel( NodeInteractionType.CLICK -> getString(R.string.action_interact_ui_element_interaction_type_click) NodeInteractionType.LONG_CLICK -> getString(R.string.action_interact_ui_element_interaction_type_long_click) NodeInteractionType.FOCUS -> getString(R.string.action_interact_ui_element_interaction_type_focus) - NodeInteractionType.SELECT -> getString(R.string.action_interact_ui_element_interaction_type_select) NodeInteractionType.SCROLL_FORWARD -> getString(R.string.action_interact_ui_element_interaction_type_scroll_forward) NodeInteractionType.SCROLL_BACKWARD -> getString(R.string.action_interact_ui_element_interaction_type_scroll_backward) NodeInteractionType.EXPAND -> getString(R.string.action_interact_ui_element_interaction_type_expand) @@ -399,6 +412,7 @@ data class SelectUiElementState( val listItems: List, val interactionTypes: List>, val selectedInteractionType: NodeInteractionType?, + val showAdditionalElements: Boolean, ) data class UiElementListItemModel( @@ -409,4 +423,8 @@ data class UiElementListItemModel( val nodeUniqueId: String?, val interactionTypesText: String, val interactionTypes: Set, + /** + * Whether the user interacted with this element. + */ + val interacted: Boolean, ) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/NodeInteractionType.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/NodeInteractionType.kt index f31a6520f5..8d02874b45 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/NodeInteractionType.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/NodeInteractionType.kt @@ -6,7 +6,6 @@ enum class NodeInteractionType(val accessibilityActionId: Int) { CLICK(AccessibilityNodeInfo.ACTION_CLICK), LONG_CLICK(AccessibilityNodeInfo.ACTION_LONG_CLICK), FOCUS(AccessibilityNodeInfo.ACTION_FOCUS), - SELECT(AccessibilityNodeInfo.ACTION_SELECT), SCROLL_FORWARD(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD), SCROLL_BACKWARD(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD), EXPAND(AccessibilityNodeInfo.ACTION_EXPAND), diff --git a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt index f2e22b2ce8..8e524a033b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt +++ b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt @@ -252,6 +252,9 @@ class BackupManagerImpl( // Do nothing. Just added the accessibility node table. JsonMigration(18, 19) { json -> json }, + + // Do nothing. Just added columns to the accessibility node table. + JsonMigration(19, 20) { json -> json }, ) if (keyMapListJsonArray != null) { diff --git a/app/src/main/java/io/github/sds100/keymapper/data/db/AppDatabase.kt b/app/src/main/java/io/github/sds100/keymapper/data/db/AppDatabase.kt index e4644b1055..bba74e2b66 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/db/AppDatabase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/db/AppDatabase.kt @@ -32,6 +32,7 @@ import io.github.sds100.keymapper.data.migration.AutoMigration14To15 import io.github.sds100.keymapper.data.migration.AutoMigration15To16 import io.github.sds100.keymapper.data.migration.AutoMigration16To17 import io.github.sds100.keymapper.data.migration.AutoMigration18To19 +import io.github.sds100.keymapper.data.migration.AutoMigration19To20 import io.github.sds100.keymapper.data.migration.Migration10To11 import io.github.sds100.keymapper.data.migration.Migration11To12 import io.github.sds100.keymapper.data.migration.Migration13To14 @@ -60,6 +61,8 @@ import io.github.sds100.keymapper.data.migration.Migration9To10 AutoMigration(from = 16, to = 17, spec = AutoMigration16To17::class), // Adds accessibility node table AutoMigration(from = 18, to = 19, spec = AutoMigration18To19::class), + // Adds interacted, tooltip, and hint fields to accessibility node entity + AutoMigration(from = 19, to = 20, spec = AutoMigration19To20::class), ], ) @TypeConverters( @@ -72,7 +75,7 @@ import io.github.sds100.keymapper.data.migration.Migration9To10 abstract class AppDatabase : RoomDatabase() { companion object { const val DATABASE_NAME = "key_map_database" - const val DATABASE_VERSION = 19 + const val DATABASE_VERSION = 20 val MIGRATION_1_2 = object : Migration(1, 2) { diff --git a/app/src/main/java/io/github/sds100/keymapper/data/db/dao/AccessibilityNodeDao.kt b/app/src/main/java/io/github/sds100/keymapper/data/db/dao/AccessibilityNodeDao.kt index ed2bd36250..1aa13584a5 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/db/dao/AccessibilityNodeDao.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/db/dao/AccessibilityNodeDao.kt @@ -19,6 +19,9 @@ interface AccessibilityNodeDao { const val KEY_VIEW_RESOURCE_ID = "view_resource_id" const val KEY_UNIQUE_ID = "unique_id" const val KEY_ACTIONS = "actions" + const val KEY_INTERACTED = "interacted" + const val KEY_TOOLTIP = "tooltip" + const val KEY_HINT = "hint" } @Query("SELECT * FROM $TABLE_NAME WHERE $KEY_ID = (:id)") diff --git a/app/src/main/java/io/github/sds100/keymapper/data/entities/AccessibilityNodeEntity.kt b/app/src/main/java/io/github/sds100/keymapper/data/entities/AccessibilityNodeEntity.kt index ca3ba0ee1c..f84769395c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/entities/AccessibilityNodeEntity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/entities/AccessibilityNodeEntity.kt @@ -8,9 +8,12 @@ import io.github.sds100.keymapper.actions.uielement.NodeInteractionType import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_ACTIONS import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_CLASS_NAME import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_CONTENT_DESCRIPTION +import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_HINT import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_ID +import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_INTERACTED import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_PACKAGE_NAME import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_TEXT +import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_TOOLTIP import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_UNIQUE_ID import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.KEY_VIEW_RESOURCE_ID import io.github.sds100.keymapper.data.db.dao.AccessibilityNodeDao.Companion.TABLE_NAME @@ -45,4 +48,16 @@ data class AccessibilityNodeEntity( @ColumnInfo(name = KEY_ACTIONS) val actions: Set, + + /** + * Whether the user interacted with this node. + */ + @ColumnInfo(name = KEY_INTERACTED, defaultValue = false.toString()) + val interacted: Boolean, + + @ColumnInfo(name = KEY_TOOLTIP, defaultValue = "NULL") + val tooltip: String?, + + @ColumnInfo(name = KEY_HINT, defaultValue = "NULL") + val hint: String?, ) : Parcelable diff --git a/app/src/main/java/io/github/sds100/keymapper/data/migration/AutoMigration19To20.kt b/app/src/main/java/io/github/sds100/keymapper/data/migration/AutoMigration19To20.kt new file mode 100644 index 0000000000..edec936175 --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/data/migration/AutoMigration19To20.kt @@ -0,0 +1,5 @@ +package io.github.sds100.keymapper.data.migration + +import androidx.room.migration.AutoMigrationSpec + +class AutoMigration19To20 : AutoMigrationSpec diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityNodeRecorder.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityNodeRecorder.kt index b0e1250a56..becc2fcf78 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityNodeRecorder.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/AccessibilityNodeRecorder.kt @@ -1,6 +1,6 @@ package io.github.sds100.keymapper.system.accessibility -import android.graphics.Rect +import android.accessibilityservice.AccessibilityService import android.os.Build import android.os.CountDownTimer import android.view.accessibility.AccessibilityEvent @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.update class AccessibilityNodeRecorder( private val nodeRepository: AccessibilityNodeRepository, + private val service: AccessibilityService, ) { companion object { private const val RECORD_DURATION = 60000L @@ -60,51 +61,54 @@ class AccessibilityNodeRecorder( return } - val source = event.source ?: return - val sourceBounds = Rect() - source.getBoundsInScreen(sourceBounds) - - val root: AccessibilityNodeInfo = source.window.root ?: return - - // This searches for all nodes that are within the bounds of the source of the - // AccessibilityEvent because the source is not necessarily the element - // the user wants to tap. - val entities = getNodesInBounds(root, sourceBounds).toTypedArray() - nodeRepository.insert(*entities) + if (event.eventType == AccessibilityEvent.TYPE_VIEW_CLICKED || + event.eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED + ) { + val source = event.source ?: return + + buildNodeEntity(source, interacted = true)?.also { nodeRepository.insert(it) } + } else if (event.eventType == AccessibilityEvent.TYPE_WINDOWS_CHANGED) { + // Only dump the whole window when a window is added because there can be + // many windows changed events sent in rapid succession. + val windowRoot: AccessibilityNodeInfo = service.rootInActiveWindow ?: return + + // This searches for all nodes that are within the bounds of the source of the + // AccessibilityEvent because the source is not necessarily the element + // the user wants to tap. + val entities = getNodesRecursively(windowRoot).toTypedArray() + nodeRepository.insert(*entities) + } } - /** - * Get all the nodes that are within the given bounds. - */ - private fun getNodesInBounds( + private fun getNodesRecursively( node: AccessibilityNodeInfo, - bounds: Rect, ): Set { val set = mutableSetOf() - val nodeBounds = Rect() - node.getBoundsInScreen(nodeBounds) - - if (bounds.contains(nodeBounds)) { - val entity = buildNodeEntity(node) + val entity = buildNodeEntity(node, interacted = false) - if (entity != null) { - set.add(entity) - } + if (entity != null) { + set.add(entity) } if (node.childCount > 0) { for (i in 0 until node.childCount) { val child = node.getChild(i) ?: continue - set.addAll(getNodesInBounds(child, bounds)) + set.addAll(getNodesRecursively(child)) } } return set } - private fun buildNodeEntity(source: AccessibilityNodeInfo): AccessibilityNodeEntity? { + /** + * @param interacted Whether the user interacted with this node. + */ + private fun buildNodeEntity( + source: AccessibilityNodeInfo, + interacted: Boolean, + ): AccessibilityNodeEntity? { val interactionTypes = source.actionList.mapNotNull { action -> NodeInteractionType.entries.find { it.accessibilityActionId == action.id } }.distinct() @@ -125,6 +129,17 @@ class AccessibilityNodeRecorder( null }, actions = interactionTypes.toSet(), + interacted = interacted, + tooltip = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + source.tooltipText?.toString() + } else { + null + }, + hint = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + source.hintText?.toString() + } else { + null + }, ) } diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt index d0a3347b75..1f53574fe0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt @@ -85,6 +85,8 @@ abstract class BaseAccessibilityServiceController( * How long should the accessibility service record a trigger in seconds. */ private const val RECORD_TRIGGER_TIMER_LENGTH = 5 + + private const val DEFAULT_NOTIFICATION_TIMEOUT = 200L } private val triggerKeyMapFromOtherAppsController = TriggerKeyMapFromOtherAppsController( @@ -107,7 +109,7 @@ abstract class BaseAccessibilityServiceController( ) private val accessibilityNodeRecorder: AccessibilityNodeRecorder = - AccessibilityNodeRecorder(nodeRepository) + AccessibilityNodeRecorder(nodeRepository, service) private var recordingTriggerJob: Job? = null private val recordingTrigger: Boolean @@ -179,6 +181,9 @@ abstract class BaseAccessibilityServiceController( val serviceEventTypes: MutableStateFlow = MutableStateFlow(AccessibilityEvent.TYPE_WINDOWS_CHANGED) + private val serviceNotificationTimeout: MutableStateFlow = + MutableStateFlow(DEFAULT_NOTIFICATION_TIMEOUT) + init { serviceFlags.onEach { flags -> @@ -202,6 +207,13 @@ abstract class BaseAccessibilityServiceController( } }.launchIn(coroutineScope) + serviceNotificationTimeout.onEach { timeout -> + // check that it isn't null because this can only be called once the service is bound + if (service.notificationTimeout != null) { + service.notificationTimeout = timeout + } + }.launchIn(coroutineScope) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { combine( detectKeyMapsUseCase.requestFingerprintGestureDetection, @@ -274,11 +286,11 @@ abstract class BaseAccessibilityServiceController( val imeInputFocusEvents = AccessibilityEvent.TYPE_VIEW_FOCUSED or AccessibilityEvent.TYPE_VIEW_CLICKED + // Listen to WINDOWS_CHANGED event in case no events are received when the user + // interacts directly with elements. val recordNodeEvents = AccessibilityEvent.TYPE_VIEW_FOCUSED or AccessibilityEvent.TYPE_VIEW_CLICKED or - AccessibilityEvent.TYPE_VIEW_LONG_CLICKED or - AccessibilityEvent.TYPE_VIEW_SELECTED or - AccessibilityEvent.TYPE_VIEW_SCROLLED + AccessibilityEvent.TYPE_WINDOWS_CHANGED coroutineScope.launch { combine( @@ -304,6 +316,14 @@ abstract class BaseAccessibilityServiceController( newEventTypes } + + serviceNotificationTimeout.update { + if (recordState is RecordAccessibilityNodeState.CountingDown) { + 0L + } else { + DEFAULT_NOTIFICATION_TIMEOUT + } + } }.collect() } } @@ -312,6 +332,7 @@ abstract class BaseAccessibilityServiceController( service.serviceFlags = serviceFlags.value service.serviceFeedbackType = serviceFeedbackType.value service.serviceEventTypes = serviceEventTypes.value + service.notificationTimeout = serviceNotificationTimeout.value // check if fingerprint gestures are supported if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt index 59d9ac5e42..4e45865eb9 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/IAccessibilityService.kt @@ -40,6 +40,7 @@ interface IAccessibilityService { var serviceFlags: Int? var serviceFeedbackType: Int? var serviceEventTypes: Int? + var notificationTimeout: Long? fun performActionOnNode( findNode: (node: AccessibilityNodeModel) -> Boolean, diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt index c25ce900ec..81871a3575 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt @@ -124,6 +124,16 @@ class MyAccessibilityService : } } + override var notificationTimeout: Long? + get() = serviceInfo?.notificationTimeout + set(value) { + if (serviceInfo != null && value != null) { + serviceInfo = serviceInfo.apply { + notificationTimeout = value + } + } + } + private val relayServiceCallback: IKeyEventRelayServiceCallback = object : IKeyEventRelayServiceCallback.Stub() { override fun onKeyEvent(event: KeyEvent?): Boolean { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2dc876b22f..927a5b57f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,7 +25,7 @@ Restart accessibility service Share Nothing here! - Nothing here! + Key Mapper did not detect any interactions. Try showing additional elements. Stop repeating when… Trigger is released Trigger is pressed again @@ -1132,12 +1132,12 @@ Possible interactions Select how you want to interact with the UI element. Filter interaction type + Show additional elements Any Tap Tap and hold Focus - Select Scroll forward Scroll backward Expand From 110e13616e1c1d3ad8b33a6cb2a1f138c42775ec Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 8 May 2025 15:16:35 +0200 Subject: [PATCH 25/95] #257 change how nodes are matched --- .../actions/PerformActionsUseCase.kt | 44 ++++++++++--------- .../BaseAccessibilityServiceController.kt | 7 +-- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt index fdfb55b8de..a3661b0721 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt @@ -844,7 +844,7 @@ class PerformActionsUseCaseImpl( matchAccessibilityNode(node, action) }, performAction = { AccessibilityNodeAction(action = action.nodeAction.accessibilityActionId) }, - ) + ).otherwise { Error.UiElementNotFound } } } } @@ -944,32 +944,36 @@ class PerformActionsUseCaseImpl( node: AccessibilityNodeModel, action: ActionData.InteractUiElement, ): Boolean { + if (!node.actions.contains(action.nodeAction.accessibilityActionId)) { + return false + } + if (compareIfNonNull(node.uniqueId, action.uniqueId)) { return true } - val viewResourceIdMatches = node.viewResourceId == action.viewResourceId - val classNameMatches = node.className == action.className + if (action.contentDescription == null && action.text == null) { + if (compareIfNonNull(node.viewResourceId, action.viewResourceId)) { + return true + } - if (compareIfNonNull( - node.contentDescription, - action.contentDescription, - ) && - viewResourceIdMatches && - classNameMatches - ) { - return true - } + if (compareIfNonNull(node.className, action.className)) { + return true + } + } else { + if (compareIfNonNull(node.contentDescription, action.contentDescription) || + compareIfNonNull(node.text, action.text) + ) { + if (action.viewResourceId != null) { + return node.viewResourceId == action.viewResourceId + } - if (compareIfNonNull(node.text, action.text) && - viewResourceIdMatches && - classNameMatches - ) { - return true - } + if (action.className != null) { + return node.className == action.className + } - if (viewResourceIdMatches) { - return true + return true + } } return false diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt index 1f53574fe0..cd1a7ef200 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt @@ -286,11 +286,8 @@ abstract class BaseAccessibilityServiceController( val imeInputFocusEvents = AccessibilityEvent.TYPE_VIEW_FOCUSED or AccessibilityEvent.TYPE_VIEW_CLICKED - // Listen to WINDOWS_CHANGED event in case no events are received when the user - // interacts directly with elements. - val recordNodeEvents = AccessibilityEvent.TYPE_VIEW_FOCUSED or - AccessibilityEvent.TYPE_VIEW_CLICKED or - AccessibilityEvent.TYPE_WINDOWS_CHANGED + val recordNodeEvents = + AccessibilityEvent.TYPE_VIEW_FOCUSED or AccessibilityEvent.TYPE_VIEW_CLICKED coroutineScope.launch { combine( From d56f7a7ac9878caa99d847894a70fe70366a353e Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 13:54:28 +0200 Subject: [PATCH 26/95] fix: export log files as .txt instead of .zip files. --- CHANGELOG.md | 1 + .../main/java/io/github/sds100/keymapper/logging/LogFragment.kt | 2 +- .../java/io/github/sds100/keymapper/system/files/FileUtils.kt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32babf419d..029b253347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ## Bug fixes - #1683 key event actions work in Minecraft and other apps again. +- Export log files as .txt instead of .zip files. ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) diff --git a/app/src/main/java/io/github/sds100/keymapper/logging/LogFragment.kt b/app/src/main/java/io/github/sds100/keymapper/logging/LogFragment.kt index 6e1effbd57..50760f51d0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/logging/LogFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/logging/LogFragment.kt @@ -44,7 +44,7 @@ class LogFragment : SimpleRecyclerViewFragment() { private val recyclerViewController by lazy { RecyclerViewController() } private val saveLogToFileLauncher = - registerForActivityResult(CreateDocument(FileUtils.MIME_TYPE_ZIP)) { + registerForActivityResult(CreateDocument(FileUtils.MIME_TYPE_TEXT)) { it ?: return@registerForActivityResult viewModel.onPickFileToSaveTo(it.toString()) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/files/FileUtils.kt b/app/src/main/java/io/github/sds100/keymapper/system/files/FileUtils.kt index d2a4b14628..d870339f02 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/files/FileUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/files/FileUtils.kt @@ -26,6 +26,7 @@ object FileUtils { const val MIME_TYPE_AUDIO = "audio/*" const val MIME_TYPE_ZIP = "application/zip" const val MIME_TYPE_JSON = "text/json" + const val MIME_TYPE_TEXT = "text/plain" @SuppressLint("SimpleDateFormat") fun createFileDate(): String { From f79f738cb64ad9a24e3c78fa5c5c4c0507ecf1d7 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 13:54:39 +0200 Subject: [PATCH 27/95] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 773194701f..1085b82907 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.0 -VERSION_CODE=112 +VERSION_CODE=114 VERSION_NUM=0 \ No newline at end of file From d900fe8019ef5463947916e58ba4ec185a7f0367 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 13:54:53 +0200 Subject: [PATCH 28/95] delete unused turkish translation --- app/src/main/res/values-tr/strings.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index e32a75e124..700903f43f 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -885,7 +885,6 @@ Dokun Dokun ve basılı tut Odakla - Seç İleri kaydır Geriye kaydır Genişlet From 7d3744bc69afcdb06a569d06393d7f80adb05808 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 13:57:27 +0200 Subject: [PATCH 29/95] #257 show tooltip and hint text --- .../sds100/keymapper/actions/ActionData.kt | 2 ++ .../actions/ActionDataEntityMapper.kt | 12 ++++++++ .../uielement/ChooseUiElementScreen.kt | 8 ++++++ .../uielement/InteractUiElementScreen.kt | 14 +++++++++- .../uielement/InteractUiElementViewModel.kt | 28 +++++++++++++------ .../keymapper/data/entities/ActionEntity.kt | 2 ++ app/src/main/res/values/strings.xml | 3 +- 7 files changed, 59 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt index 56dfd47265..9a2734bb14 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt @@ -883,6 +883,8 @@ sealed class ActionData : Comparable { val nodeAction: NodeInteractionType, val packageName: String, val text: String?, + val tooltip: String?, + val hint: String?, val contentDescription: String?, val className: String?, val viewResourceId: String?, diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt index 8eedce9b51..ce246ca4cf 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt @@ -557,6 +557,12 @@ object ActionDataEntityMapper { val text = entity.extras.getData(ActionEntity.EXTRA_ACCESSIBILITY_TEXT).valueOrNull() + val tooltip = + entity.extras.getData(ActionEntity.EXTRA_ACCESSIBILITY_TOOLTIP).valueOrNull() + + val hint = + entity.extras.getData(ActionEntity.EXTRA_ACCESSIBILITY_HINT).valueOrNull() + val className = entity.extras.getData(ActionEntity.EXTRA_ACCESSIBILITY_CLASS_NAME).valueOrNull() @@ -582,6 +588,8 @@ object ActionDataEntityMapper { packageName = packageName, text = text, contentDescription = contentDescription, + tooltip = tooltip, + hint = hint, className = className, viewResourceId = viewResourceId, uniqueId = uniqueId, @@ -855,6 +863,10 @@ object ActionDataEntityMapper { data.text?.let { add(EntityExtra(ActionEntity.EXTRA_ACCESSIBILITY_TEXT, it)) } + data.tooltip?.let { add(EntityExtra(ActionEntity.EXTRA_ACCESSIBILITY_TOOLTIP, it)) } + + data.hint?.let { add(EntityExtra(ActionEntity.EXTRA_ACCESSIBILITY_HINT, it)) } + data.className?.let { add( EntityExtra( diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt index e38a7bf080..4d262d60f2 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt @@ -276,6 +276,13 @@ private fun UiElementListItem( ) } + if (model.nodeTooltipHint != null) { + TextWithLeadingLabel( + title = stringResource(R.string.action_interact_ui_element_tooltip_label), + text = model.nodeTooltipHint, + ) + } + if (model.nodeUniqueId != null) { TextWithLeadingLabel( title = stringResource(R.string.action_interact_ui_element_unique_id_label), @@ -355,6 +362,7 @@ private fun Loaded() { nodeClassName = "android.widget.ImageButton", nodeViewResourceId = "menu_button", nodeUniqueId = "123456789", + nodeTooltipHint = "Open menu", interactionTypesText = "Tap, Tap and hold, Scroll forward", interactionTypes = setOf( NodeInteractionType.CLICK, diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt index 0c0ec02e9e..4b680630d9 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt @@ -530,6 +530,17 @@ private fun SelectedElementSection( Spacer(modifier = Modifier.height(8.dp)) + if (state.nodeToolTipHint != null) { + Text( + text = stringResource(R.string.action_interact_ui_element_tooltip_label), + style = MaterialTheme.typography.titleSmall, + ) + + Text(text = state.nodeToolTipHint, style = MaterialTheme.typography.bodyMedium) + } + + Spacer(modifier = Modifier.height(8.dp)) + if (state.nodeClassName != null) { Text( text = stringResource(R.string.action_interact_ui_element_class_name_label), @@ -575,7 +586,7 @@ private fun SelectedElementSection( Spacer(modifier = Modifier.height(8.dp)) - KeyMapperDropdownMenu( + KeyMapperDropdownMenu( expanded = interactionTypeExpanded, onExpandedChange = { interactionTypeExpanded = it }, values = state.interactionTypes, @@ -612,6 +623,7 @@ private fun PreviewSelectedElement() { appName = "Test App", appIcon = ComposeIconInfo.Drawable(appIcon), nodeText = "Test Node", + nodeToolTipHint = "Test tooltip", nodeClassName = "android.widget.ImageButton", nodeViewResourceId = "io.github.sds100.keymapper:id/menu_button", nodeUniqueId = "123", diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt index dd1a267519..cc49251b80 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt @@ -89,6 +89,7 @@ class InteractUiElementViewModel( } }.stateIn(viewModelScope, SharingStarted.Lazily, State.Loading) + private val selectedElementEntity = MutableStateFlow(null) private val _selectedElementState = MutableStateFlow(null) val selectedElementState: StateFlow = _selectedElementState.asStateFlow() @@ -160,6 +161,8 @@ class InteractUiElementViewModel( val modelString = buildString { append(model.nodeText) append(" ") + append(model.nodeTooltipHint) + append(" ") append(model.nodeClassName) append(" ") append(model.nodeViewResourceId) @@ -198,6 +201,7 @@ class InteractUiElementViewModel( appName = appName, appIcon = appIcon, nodeText = action.text ?: action.contentDescription, + nodeToolTipHint = action.tooltip ?: action.hint, nodeClassName = action.className, nodeViewResourceId = action.viewResourceId, nodeUniqueId = action.uniqueId, @@ -211,7 +215,9 @@ class InteractUiElementViewModel( fun onDoneClick() { val selectedElementState = _selectedElementState.value - if (selectedElementState == null) { + val selectedElementEntity = selectedElementEntity.value + + if (selectedElementState == null || selectedElementEntity == null) { return } @@ -222,13 +228,15 @@ class InteractUiElementViewModel( val action = ActionData.InteractUiElement( description = selectedElementState.description, nodeAction = selectedElementState.selectedInteraction, - packageName = selectedElementState.packageName, - text = selectedElementState.nodeText, - contentDescription = selectedElementState.nodeText, - className = selectedElementState.nodeClassName, - viewResourceId = selectedElementState.nodeViewResourceId, - uniqueId = selectedElementState.nodeUniqueId, - nodeActions = selectedElementState.interactionTypes.map { it.first }.toSet(), + packageName = selectedElementEntity.packageName, + text = selectedElementEntity.text, + contentDescription = selectedElementEntity.contentDescription, + tooltip = selectedElementEntity.tooltip, + hint = selectedElementEntity.hint, + className = selectedElementEntity.className, + viewResourceId = selectedElementEntity.viewResourceId, + uniqueId = selectedElementEntity.uniqueId, + nodeActions = selectedElementEntity.actions, ) viewModelScope.launch { @@ -271,6 +279,7 @@ class InteractUiElementViewModel( appIcon = appIcon, nodeText = interaction.text ?: interaction.contentDescription, nodeClassName = interaction.className, + nodeToolTipHint = interaction.tooltip ?: interaction.hint, nodeViewResourceId = interaction.viewResourceId, nodeUniqueId = interaction.uniqueId, interactionTypes = buildInteractionTypeFilterItems(interaction.actions), @@ -339,6 +348,7 @@ class InteractUiElementViewModel( nodeText = node.text ?: node.contentDescription, nodeClassName = node.className, nodeUniqueId = node.uniqueId, + nodeTooltipHint = node.tooltip ?: node.hint, interactionTypesText = node.actions.joinToString { getInteractionTypeString(it) }, interactionTypes = node.actions, interacted = node.interacted, @@ -390,6 +400,7 @@ data class SelectedUiElementState( val appName: String, val appIcon: ComposeIconInfo.Drawable?, val nodeText: String?, + val nodeToolTipHint: String?, val nodeClassName: String?, val nodeViewResourceId: String?, val nodeUniqueId: String?, @@ -419,6 +430,7 @@ data class UiElementListItemModel( val id: Long, val nodeViewResourceId: String?, val nodeText: String?, + val nodeTooltipHint: String?, val nodeClassName: String?, val nodeUniqueId: String?, val interactionTypesText: String, diff --git a/app/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt b/app/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt index 4385e0b3a4..be62380df3 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/entities/ActionEntity.kt @@ -95,6 +95,8 @@ data class ActionEntity( const val EXTRA_ACCESSIBILITY_CONTENT_DESCRIPTION = "extra_accessibility_content_description" const val EXTRA_ACCESSIBILITY_TEXT = "extra_accessibility_text" + const val EXTRA_ACCESSIBILITY_TOOLTIP = "extra_accessibility_tooltip" + const val EXTRA_ACCESSIBILITY_HINT = "extra_accessibility_hint" const val EXTRA_ACCESSIBILITY_CLASS_NAME = "extra_accessibility_class_name" const val EXTRA_ACCESSIBILITY_VIEW_RESOURCE_ID = "extra_accessibility_view_resource_id" const val EXTRA_ACCESSIBILITY_UNIQUE_ID = "extra_accessibility_unique_id" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 927a5b57f0..603ef87d6f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1147,8 +1147,9 @@ Interaction details Description App - Text / content description + Text/content description Class name + Tooltip/hint View resource ID Unique ID Interaction types From 887a86659f803cb62e48cff60af84c2afcb4c4b1 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 14:10:43 +0200 Subject: [PATCH 30/95] #257 automatically show additional elements if there are none that were interacted with --- .../actions/uielement/InteractUiElementViewModel.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt index cc49251b80..7ffd6e993d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt @@ -38,6 +38,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -118,6 +119,15 @@ class InteractUiElementViewModel( private val interactionsByPackage: StateFlow>> = selectedApp .filterNotNull() .flatMapLatest { packageName -> useCase.getInteractionsByPackage(packageName) } + .onEach { state -> + // Automatically show additional elements if no elements that were interacted with + // were detected. + state.ifIsData { list -> + if (list.count { it.interacted } == 0) { + showAdditionalElements.update { true } + } + } + } .stateIn(viewModelScope, SharingStarted.Lazily, State.Loading) private val elementListItems: Flow>> = interactionsByPackage @@ -258,6 +268,7 @@ class InteractUiElementViewModel( fun onSelectApp(packageName: String) { elementSearchQuery.update { null } + showAdditionalElements.update { false } selectedApp.update { packageName } } From f1420d68f33297c42330025cc0b752c66e22a3a8 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 14:23:57 +0200 Subject: [PATCH 31/95] #257 automatically fill a description if the node has text or a view resource id --- .../uielement/InteractUiElementScreen.kt | 6 ++---- .../uielement/InteractUiElementViewModel.kt | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt index 4b680630d9..955bd04fa1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt @@ -526,10 +526,9 @@ private fun SelectedElementSection( ) Text(text = state.nodeText, style = MaterialTheme.typography.bodyMedium) + Spacer(modifier = Modifier.height(8.dp)) } - Spacer(modifier = Modifier.height(8.dp)) - if (state.nodeToolTipHint != null) { Text( text = stringResource(R.string.action_interact_ui_element_tooltip_label), @@ -537,10 +536,9 @@ private fun SelectedElementSection( ) Text(text = state.nodeToolTipHint, style = MaterialTheme.typography.bodyMedium) + Spacer(modifier = Modifier.height(8.dp)) } - Spacer(modifier = Modifier.height(8.dp)) - if (state.nodeClassName != null) { Text( text = stringResource(R.string.action_interact_ui_element_class_name_label), diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt index 7ffd6e993d..b8e5356157 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt @@ -268,7 +268,11 @@ class InteractUiElementViewModel( fun onSelectApp(packageName: String) { elementSearchQuery.update { null } - showAdditionalElements.update { false } + + if (packageName != selectedApp.value) { + showAdditionalElements.update { false } + } + selectedApp.update { packageName } } @@ -282,9 +286,19 @@ class InteractUiElementViewModel( val selectedInteraction = NodeInteractionType.entries.first { interaction.actions.contains(it) } + val interactionText = getInteractionTypeString(selectedInteraction) + val descriptionElement = + interaction.text ?: interaction.contentDescription ?: interaction.tooltip + ?: interaction.hint ?: interaction.viewResourceId + + val description = if (descriptionElement == null) { + "" + } else { + "$interactionText: $descriptionElement" + } val newState = SelectedUiElementState( - description = "", + description = description, packageName = interaction.packageName, appName = appName, appIcon = appIcon, From d03db42aa1c4b282f1c075c33ccc4443035bc31e Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 15:04:40 +0200 Subject: [PATCH 32/95] #257 adapt layouts for screens with a small height --- .idea/codeStyles/Project.xml | 35 +++ .../uielement/ChooseUiElementScreen.kt | 291 ++++++++++++------ .../uielement/InteractUiElementScreen.kt | 187 +++++++---- .../uielement/InteractUiElementViewModel.kt | 1 + 4 files changed, 360 insertions(+), 154 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 9b58552057..17554251a8 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -5,6 +5,41 @@ + + + diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt index 4d262d60f2..340f304358 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/ChooseUiElementScreen.kt @@ -17,6 +17,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ErrorOutline import androidx.compose.material3.BottomAppBar @@ -27,6 +29,7 @@ import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -41,14 +44,18 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowWidthSizeClass import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.ui.compose.CheckBoxText import io.github.sds100.keymapper.util.ui.compose.KeyMapperDropdownMenu import io.github.sds100.keymapper.util.ui.compose.SearchAppBarActions +import io.github.sds100.keymapper.util.ui.compose.WindowSizeClassExt.compareTo @Composable fun ChooseElementScreen( @@ -62,7 +69,9 @@ fun ChooseElementScreen( onSelectInteractionType: (NodeInteractionType?) -> Unit = {}, onAdditionalElementsCheckedChange: (Boolean) -> Unit = {}, ) { - var interactionTypeExpanded by rememberSaveable { mutableStateOf(false) } + val windowAdaptiveInfo = currentWindowAdaptiveInfo() + val widthSizeClass = windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass + val heightSizeClass = windowAdaptiveInfo.windowSizeClass.windowHeightSizeClass Scaffold( modifier.displayCutoutPadding(), @@ -107,82 +116,137 @@ fun ChooseElementScreen( style = MaterialTheme.typography.titleLarge, ) - Text( - modifier = Modifier.padding(horizontal = 16.dp), - text = stringResource(R.string.action_interact_ui_element_choose_element_text), - style = MaterialTheme.typography.bodyMedium, - ) - - Spacer(modifier = Modifier.height(8.dp)) + if (heightSizeClass == WindowHeightSizeClass.COMPACT || widthSizeClass >= WindowWidthSizeClass.EXPANDED) { + Row { + InfoSection( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .weight(1f), + state = state, + onSelectInteractionType = onSelectInteractionType, + onAdditionalElementsCheckedChange = onAdditionalElementsCheckedChange, + ) - Row( - modifier = Modifier.padding(horizontal = 16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - imageVector = Icons.Rounded.ErrorOutline, - contentDescription = null, - tint = MaterialTheme.colorScheme.error, + ListSection( + modifier = Modifier.weight(1f), + state = state, + onClickElement = onClickElement, + ) + } + } else { + InfoSection( + state = state, + onSelectInteractionType = onSelectInteractionType, + onAdditionalElementsCheckedChange = onAdditionalElementsCheckedChange, ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - text = stringResource(R.string.action_interact_ui_element_choose_element_not_found_subtitle), - color = MaterialTheme.colorScheme.error, - style = MaterialTheme.typography.titleSmall, + + ListSection( + modifier = Modifier.fillMaxSize(), + state = state, + onClickElement = onClickElement, ) } + } + } + } +} - Spacer(modifier = Modifier.height(8.dp)) +@Composable +private fun InfoSection( + modifier: Modifier = Modifier, + state: State, + onSelectInteractionType: (NodeInteractionType?) -> Unit, + onAdditionalElementsCheckedChange: (Boolean) -> Unit, +) { + Column(modifier = modifier) { + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.action_interact_ui_element_choose_element_text), + style = MaterialTheme.typography.bodyMedium, + ) - Text( - modifier = Modifier.padding(horizontal = 16.dp), - text = stringResource(R.string.action_interact_ui_element_choose_element_not_found_text), - style = MaterialTheme.typography.bodyMedium, - ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(8.dp)) - when (state) { - State.Loading -> LoadingList(modifier = Modifier.fillMaxSize()) - is State.Data -> { - val listItems = state.data.listItems + Row( + modifier = Modifier.padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Rounded.ErrorOutline, + contentDescription = null, + tint = MaterialTheme.colorScheme.error, + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.action_interact_ui_element_choose_element_not_found_subtitle), + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.titleSmall, + ) + } - CheckBoxText( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp), - text = stringResource(R.string.action_interact_ui_element_checkbox_additional_elements), - isChecked = state.data.showAdditionalElements, - onCheckedChange = onAdditionalElementsCheckedChange, - ) + Spacer(modifier = Modifier.height(8.dp)) - Spacer(modifier = Modifier.height(8.dp)) - - if (listItems.isEmpty()) { - EmptyList( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - ) - } else { - KeyMapperDropdownMenu( - modifier = Modifier.padding(horizontal = 16.dp), - expanded = interactionTypeExpanded, - onExpandedChange = { interactionTypeExpanded = it }, - label = { Text(stringResource(R.string.action_interact_ui_element_filter_interaction_type_dropdown)) }, - values = state.data.interactionTypes, - selectedValue = state.data.selectedInteractionType, - onValueChanged = onSelectInteractionType, - ) - - Spacer(modifier = Modifier.height(8.dp)) - - LoadedList( - modifier = Modifier.fillMaxSize(), - listItems = listItems, - onClick = onClickElement, - ) - } - } + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.action_interact_ui_element_choose_element_not_found_text), + style = MaterialTheme.typography.bodyMedium, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + if (state is State.Data) { + var interactionTypeExpanded by rememberSaveable { mutableStateOf(false) } + + CheckBoxText( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + text = stringResource(R.string.action_interact_ui_element_checkbox_additional_elements), + isChecked = state.data.showAdditionalElements, + onCheckedChange = onAdditionalElementsCheckedChange, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + KeyMapperDropdownMenu( + modifier = Modifier.padding(horizontal = 16.dp), + expanded = interactionTypeExpanded, + onExpandedChange = { interactionTypeExpanded = it }, + label = { Text(stringResource(R.string.action_interact_ui_element_filter_interaction_type_dropdown)) }, + values = state.data.interactionTypes, + selectedValue = state.data.selectedInteractionType, + onValueChanged = onSelectInteractionType, + ) + + Spacer(modifier = Modifier.height(8.dp)) + } + } +} + +@Composable +private fun ListSection( + modifier: Modifier = Modifier, + state: State, + onClickElement: (Long) -> Unit, +) { + when (state) { + State.Loading -> LoadingList(modifier = modifier.fillMaxSize()) + is State.Data -> { + val listItems = state.data.listItems + + Column(modifier = modifier) { + if (listItems.isEmpty()) { + EmptyList( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + ) + } else { + LoadedList( + modifier = Modifier.fillMaxSize(), + listItems = listItems, + onClick = onClickElement, + ) } } } @@ -352,41 +416,74 @@ private fun Loading() { } } +private val listItems = listOf( + UiElementListItemModel( + id = 1L, + nodeText = "Open Settings", + nodeClassName = "android.widget.ImageButton", + nodeViewResourceId = "menu_button", + nodeUniqueId = "123456789", + nodeTooltipHint = "Open menu", + interactionTypesText = "Tap, Tap and hold, Scroll forward", + interactionTypes = setOf( + NodeInteractionType.CLICK, + NodeInteractionType.LONG_CLICK, + NodeInteractionType.SCROLL_FORWARD, + ), + interacted = true, + ), +) + +private val loadedState = SelectUiElementState( + listItems = listItems, + interactionTypes = listOf( + null to "Any", + NodeInteractionType.CLICK to "Tap", + NodeInteractionType.LONG_CLICK to "Tap and hold", + ), + selectedInteractionType = null, + showAdditionalElements = true, +) + @Preview @Composable -private fun Loaded() { - val listItems = listOf( - UiElementListItemModel( - id = 1L, - nodeText = "Open Settings", - nodeClassName = "android.widget.ImageButton", - nodeViewResourceId = "menu_button", - nodeUniqueId = "123456789", - nodeTooltipHint = "Open menu", - interactionTypesText = "Tap, Tap and hold, Scroll forward", - interactionTypes = setOf( - NodeInteractionType.CLICK, - NodeInteractionType.LONG_CLICK, - NodeInteractionType.SCROLL_FORWARD, - ), - interacted = true, - ), - ) +private fun LoadedPortrait() { + KeyMapperTheme { + ChooseElementScreen( + state = State.Data(loadedState), + query = "Key Mapper", + ) + } +} - val state = SelectUiElementState( - listItems = listItems, - interactionTypes = listOf( - null to "Any", - NodeInteractionType.CLICK to "Tap", - NodeInteractionType.LONG_CLICK to "Tap and hold", - ), - selectedInteractionType = null, - showAdditionalElements = true, - ) +@Preview(widthDp = 800, heightDp = 300) +@Composable +private fun LoadedPhoneLandscape() { + KeyMapperTheme { + ChooseElementScreen( + state = State.Data(loadedState), + query = "Key Mapper", + ) + } +} +@Preview(device = Devices.TABLET) +@Composable +private fun LoadedTablet() { + KeyMapperTheme { + ChooseElementScreen( + state = State.Data(loadedState), + query = "Key Mapper", + ) + } +} + +@Preview(device = Devices.NEXUS_7) +@Composable +private fun LoadedTabletVertical() { KeyMapperTheme { ChooseElementScreen( - state = State.Data(state), + state = State.Data(loadedState), query = "Key Mapper", ) } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt index 955bd04fa1..7ff879f499 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -42,6 +43,7 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -57,12 +59,15 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowWidthSizeClass import com.google.accompanist.drawablepainter.rememberDrawablePainter import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme @@ -73,6 +78,7 @@ import io.github.sds100.keymapper.util.drawable import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo import io.github.sds100.keymapper.util.ui.compose.KeyMapperDropdownMenu import io.github.sds100.keymapper.util.ui.compose.OptionsHeaderRow +import io.github.sds100.keymapper.util.ui.compose.WindowSizeClassExt.compareTo import io.github.sds100.keymapper.util.ui.compose.icons.AdGroup import io.github.sds100.keymapper.util.ui.compose.icons.JumpToElement import io.github.sds100.keymapper.util.ui.compose.icons.KeyMapperIcons @@ -181,6 +187,10 @@ private fun LandingScreen( ) { val snackbarHostState = SnackbarHostState() + val windowAdaptiveInfo = currentWindowAdaptiveInfo() + val widthSizeClass = windowAdaptiveInfo.windowSizeClass.windowWidthSizeClass + val heightSizeClass = windowAdaptiveInfo.windowSizeClass.windowHeightSizeClass + Scaffold( modifier.displayCutoutPadding(), snackbarHost = { SnackbarHost(snackbarHostState) }, @@ -225,7 +235,7 @@ private fun LandingScreen( end = endPadding, ), ) { - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + Column { Text( modifier = Modifier.padding( start = 16.dp, @@ -237,44 +247,91 @@ private fun LandingScreen( style = MaterialTheme.typography.titleLarge, ) - Text( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - text = stringResource(R.string.action_interact_ui_element_description), - style = MaterialTheme.typography.bodyMedium, - ) - - Spacer(modifier = Modifier.height(8.dp)) - - RecordingSection( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - state = recordState, - onRecordClick = onRecordClick, - openSelectAppScreen = openSelectAppScreen, - ) - - Spacer(modifier = Modifier.height(8.dp)) - - if (selectedElementState != null) { - HorizontalDivider( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - Spacer(modifier = Modifier.height(8.dp)) - - SelectedElementSection( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - state = selectedElementState, - onSelectInteractionType = onSelectInteractionType, - onDescriptionChanged = onDescriptionChanged, - ) + if (heightSizeClass == WindowHeightSizeClass.COMPACT || widthSizeClass >= WindowWidthSizeClass.EXPANDED) { + Row { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .fillMaxHeight() + .weight(1f), + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = stringResource(R.string.action_interact_ui_element_description), + style = MaterialTheme.typography.bodyMedium, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + RecordingSection( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + state = recordState, + onRecordClick = onRecordClick, + openSelectAppScreen = openSelectAppScreen, + ) + + Spacer(modifier = Modifier.height(8.dp)) + } + + if (selectedElementState != null) { + SelectedElementSection( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .fillMaxHeight() + .padding(horizontal = 16.dp) + .weight(1f), + state = selectedElementState, + onSelectInteractionType = onSelectInteractionType, + onDescriptionChanged = onDescriptionChanged, + ) + } + } + } else { + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + text = stringResource(R.string.action_interact_ui_element_description), + style = MaterialTheme.typography.bodyMedium, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + RecordingSection( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + state = recordState, + onRecordClick = onRecordClick, + openSelectAppScreen = openSelectAppScreen, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + if (selectedElementState != null) { + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + SelectedElementSection( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + state = selectedElementState, + onSelectInteractionType = onSelectInteractionType, + onDescriptionChanged = onDescriptionChanged, + ) + } + } } } } @@ -609,36 +666,52 @@ private fun PreviewEmpty() { @Preview @Composable -private fun PreviewSelectedElement() { +private fun PreviewLoading() { + KeyMapperTheme { + LandingScreen( + recordState = State.Loading, + selectedElementState = null, + ) + } +} + +@Composable +private fun selectedUiElementState(): SelectedUiElementState { val appIcon = LocalContext.current.drawable(R.mipmap.ic_launcher_round) + return SelectedUiElementState( + description = "Tap test node", + packageName = "com.example.test", + appName = "Test App", + appIcon = ComposeIconInfo.Drawable(appIcon), + nodeText = "Test Node", + nodeToolTipHint = "Test tooltip", + nodeClassName = "android.widget.ImageButton", + nodeViewResourceId = "io.github.sds100.keymapper:id/menu_button", + nodeUniqueId = "123", + interactionTypes = listOf(NodeInteractionType.LONG_CLICK to "Tap and hold"), + selectedInteraction = NodeInteractionType.LONG_CLICK, + ) +} + +@Preview(device = Devices.PIXEL_7) +@Composable +private fun PreviewSelectedElementPortrait() { KeyMapperTheme { LandingScreen( recordState = State.Data(RecordUiElementState.Recorded(3)), - selectedElementState = SelectedUiElementState( - description = "Tap test node", - packageName = "com.example.test", - appName = "Test App", - appIcon = ComposeIconInfo.Drawable(appIcon), - nodeText = "Test Node", - nodeToolTipHint = "Test tooltip", - nodeClassName = "android.widget.ImageButton", - nodeViewResourceId = "io.github.sds100.keymapper:id/menu_button", - nodeUniqueId = "123", - interactionTypes = listOf(NodeInteractionType.LONG_CLICK to "Tap and hold"), - selectedInteraction = NodeInteractionType.LONG_CLICK, - ), + selectedElementState = selectedUiElementState(), ) } } -@Preview +@Preview(widthDp = 800, heightDp = 300) @Composable -private fun PreviewLoading() { +private fun PreviewSelectedElementLandscape() { KeyMapperTheme { LandingScreen( - recordState = State.Loading, - selectedElementState = null, + recordState = State.Data(RecordUiElementState.Recorded(3)), + selectedElementState = selectedUiElementState(), ) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt index b8e5356157..67dcac391c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementViewModel.kt @@ -311,6 +311,7 @@ class InteractUiElementViewModel( selectedInteraction = selectedInteraction, ) + selectedElementEntity.update { interaction } _selectedElementState.update { newState } } } From 9855cc780ae21fea8c5557c38eca9f833c900766 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 15:08:19 +0200 Subject: [PATCH 33/95] #1682 feat: show "Purchased!" text next to the use button for advanced triggers --- CHANGELOG.md | 1 + app/src/main/res/values/strings.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 029b253347..54fbf2380f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - #699 Time constraints ⏰ - #257 Action to interact with user interface elements inside other apps. - #1663 Actions to stop, step forward, and step backward playing media. +- #1682 Show "Purchased!" text next to the use button for advanced triggers. ## Changed diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 603ef87d6f..20ee36a04c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1283,6 +1283,7 @@ Unlock (%s) Use Loading… + Purchased! Retry fetching price Purchase cancelled. This requires a paid feature that can only be bought by downloading Key Mapper from Google Play. From e7b34034a1b4af06d084cc28100a6baf66c841b3 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 15:19:01 +0200 Subject: [PATCH 34/95] #1684 fix: removed the redundant and broken refresh devices button when configuring a key event action because they are automatically refreshed anyway --- CHANGELOG.md | 1 + .../keyevent/ConfigKeyEventActionFragment.kt | 6 ----- .../keyevent/ConfigKeyEventActionViewModel.kt | 12 ---------- .../system/devices/AndroidDevicesAdapter.kt | 8 +++---- .../res/layout/fragment_config_key_event.xml | 22 +++++-------------- 5 files changed, 10 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54fbf2380f..6608ffd78b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - #1683 key event actions work in Minecraft and other apps again. - Export log files as .txt instead of .zip files. +- #1684 Removed the redundant and broken refresh devices button when configuring a key event action because they are automatically refreshed anyway. ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt index 6f90c1ceaa..d674a421df 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt @@ -175,12 +175,6 @@ class ConfigKeyEventActionFragment : Fragment() { } } - override fun onResume() { - super.onResume() - - viewModel.rebuildUiState() - } - override fun onDestroyView() { _binding = null super.onDestroyView() diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt index e6734d0921..c28b02a48a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt @@ -33,7 +33,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import splitties.bitflags.hasFlag import splitties.bitflags.minusFlag import splitties.bitflags.withFlag @@ -63,11 +62,8 @@ class ConfigKeyEventActionViewModel( private val _returnResult = MutableSharedFlow() val returnResult = _returnResult.asSharedFlow() - private val rebuildUiState = MutableSharedFlow() - init { viewModelScope.launch { - combine( keyEventState, useCase.inputDevices, @@ -178,14 +174,6 @@ class ConfigKeyEventActionViewModel( } } - fun refreshDevices() { - rebuildUiState() - } - - fun rebuildUiState() { - runBlocking { rebuildUiState.emit(Unit) } - } - private fun buildUiState( state: KeyEventState, inputDeviceList: List, diff --git a/app/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt index daa1799a9a..f593683c97 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/devices/AndroidDevicesAdapter.kt @@ -125,8 +125,8 @@ class AndroidDevicesAdapter( } override fun getInputDeviceName(descriptor: String): Result { - InputDevice.getDeviceIds().forEach { - val device = InputDevice.getDevice(it) ?: return@forEach + for (id in InputDevice.getDeviceIds()) { + val device = InputDevice.getDevice(id) ?: continue if (device.descriptor == descriptor) { return Success(device.name) @@ -139,8 +139,8 @@ class AndroidDevicesAdapter( private fun updateInputDevices() { val devices = mutableListOf() - InputDevice.getDeviceIds().forEach { - val device = InputDevice.getDevice(it) ?: return@forEach + for (id in InputDevice.getDeviceIds()) { + val device = InputDevice.getDevice(id) ?: continue devices.add(InputDeviceUtils.createInputDeviceInfo(device)) } diff --git a/app/src/main/res/layout/fragment_config_key_event.xml b/app/src/main/res/layout/fragment_config_key_event.xml index 87b7853f12..c9947104c9 100644 --- a/app/src/main/res/layout/fragment_config_key_event.xml +++ b/app/src/main/res/layout/fragment_config_key_event.xml @@ -28,7 +28,9 @@ android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginBottom="@dimen/bottom_app_bar_height"> + android:layout_marginBottom="@dimen/bottom_app_bar_height" + app:layout_anchor="@+id/scrollView" + app:layout_anchorGravity="center"> @@ -126,20 +128,6 @@ - - Date: Fri, 9 May 2025 15:22:06 +0200 Subject: [PATCH 35/95] #1684 fix: attempt to fix the list of devices going empty randomly --- .../keyevent/ConfigKeyEventActionViewModel.kt | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt index c28b02a48a..c514e7a2c1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt @@ -27,11 +27,12 @@ import io.github.sds100.keymapper.util.ui.navigate import io.github.sds100.keymapper.util.valueOrNull import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import splitties.bitflags.hasFlag import splitties.bitflags.minusFlag @@ -50,32 +51,25 @@ class ConfigKeyEventActionViewModel( private val keyEventState = MutableStateFlow(KeyEventState()) - private val _uiState = MutableStateFlow( + val uiState: StateFlow = combine( + keyEventState, + useCase.inputDevices, + useCase.showDeviceDescriptors, + ) { state, inputDevices, showDeviceDescriptors -> + buildUiState(state, inputDevices, showDeviceDescriptors) + }.stateIn( + viewModelScope, + SharingStarted.Lazily, buildUiState( keyEventState.value, inputDeviceList = emptyList(), showDeviceDescriptors = false, ), ) - val uiState = _uiState.asStateFlow() private val _returnResult = MutableSharedFlow() val returnResult = _returnResult.asSharedFlow() - init { - viewModelScope.launch { - combine( - keyEventState, - useCase.inputDevices, - useCase.showDeviceDescriptors, - ) { state, inputDevices, showDeviceDescriptors -> - buildUiState(state, inputDevices, showDeviceDescriptors) - }.collectLatest { - _uiState.value = it - } - } - } - fun setModifierKeyChecked(modifier: Int, isChecked: Boolean) { val oldMetaState = keyEventState.value.metaState From 0c4f4abf09097e4796091bba9199d5731d39b8c4 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 17:43:35 +0200 Subject: [PATCH 36/95] #1687 fix: restoring key map groups would sometimes fail due to improper breadth first traversal of the group tree --- CHANGELOG.md | 1 + .../sds100/keymapper/backup/BackupManager.kt | 139 ++++++++++++------ .../github/sds100/keymapper/util/TreeNode.kt | 16 ++ .../sds100/keymapper/BackupManagerTest.kt | 57 ++++++- 4 files changed, 164 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/io/github/sds100/keymapper/util/TreeNode.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 6608ffd78b..89a7a0807b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - #1683 key event actions work in Minecraft and other apps again. - Export log files as .txt instead of .zip files. - #1684 Removed the redundant and broken refresh devices button when configuring a key event action because they are automatically refreshed anyway. +- #1687 restoring key map groups would sometimes fail. ## [3.0.1](https://github.com/sds100/KeyMapper/releases/tag/v3.0.1) diff --git a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt index 8e524a033b..d56aaf4d19 100644 --- a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt +++ b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt @@ -54,7 +54,9 @@ import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.Success +import io.github.sds100.keymapper.util.TreeNode import io.github.sds100.keymapper.util.UuidGenerator +import io.github.sds100.keymapper.util.breadFirstTraversal import io.github.sds100.keymapper.util.onFailure import io.github.sds100.keymapper.util.then import kotlinx.coroutines.CoroutineScope @@ -473,55 +475,11 @@ class BackupManagerImpl( // Group parents must be restored first so an SqliteConstraintException // is not thrown when restoring a child group. - val groupsToRestoreMap = backupContent.groups.associateBy { it.uid }.toMutableMap() - val groupRestoreQueue = LinkedList() + val groupRestoreTrees = buildGroupTrees(backupContent.groups) - // Order the groups into a queue such that a parent is always before a child. - for (group in backupContent.groups) { - if (groupsToRestoreMap.containsKey(group.uid)) { - groupRestoreQueue.addFirst(group) - } - - var parent = groupsToRestoreMap[group.parentUid] - - while (parent != null) { - groupRestoreQueue.addFirst(parent) - groupsToRestoreMap.remove(parent.uid) - parent = groupsToRestoreMap[parent.parentUid] - } - } - - for (group in groupRestoreQueue) { - // Set the last opened date to now so that the imported group - // shows as the most recent. - var modifiedGroup = group.copy(lastOpenedDate = currentTime) - - // If the group's parent wasn't backed up or doesn't exist - // then set it the parent to the root group - if (!groupUids.contains(group.parentUid)) { - modifiedGroup = modifiedGroup.copy(parentUid = null) - } - - val siblings = - groupRepository.getGroupsByParent(modifiedGroup.parentUid).first() - - modifiedGroup = RepositoryUtils.saveUniqueName( - modifiedGroup, - saveBlock = { renamedGroup -> - // Do not rename the group with a (1) if it is the same UID. Just overwrite the name. - if (siblings.any { sibling -> sibling.uid != renamedGroup.uid && sibling.name == renamedGroup.name }) { - throw IllegalStateException("Non unique group name") - } - }, - renameBlock = { entity, suffix -> - entity.copy(name = "${entity.name} $suffix") - }, - ) - - if (existingGroupUids.contains(modifiedGroup.uid)) { - groupRepository.update(modifiedGroup) - } else { - groupRepository.insert(modifiedGroup) + for (tree in groupRestoreTrees) { + tree.breadFirstTraversal { group -> + restoreGroup(group, currentTime, groupUids, existingGroupUids) } } } @@ -615,6 +573,91 @@ class BackupManagerImpl( } } + private suspend fun restoreGroup( + group: GroupEntity, + currentTime: Long, + groupUids: Set, + existingGroupUids: Set, + ) { + // Set the last opened date to now so that the imported group + // shows as the most recent. + var modifiedGroup = group.copy(lastOpenedDate = currentTime) + + // If the group's parent wasn't backed up or doesn't exist + // then set it the parent to the root group + if (!groupUids.contains(group.parentUid)) { + modifiedGroup = modifiedGroup.copy(parentUid = null) + } + + val siblings = + groupRepository.getGroupsByParent(modifiedGroup.parentUid).first() + + modifiedGroup = RepositoryUtils.saveUniqueName( + modifiedGroup, + saveBlock = { renamedGroup -> + // Do not rename the group with a (1) if it is the same UID. Just overwrite the name. + if (siblings.any { sibling -> sibling.uid != renamedGroup.uid && sibling.name == renamedGroup.name }) { + throw IllegalStateException("Non unique group name") + } + }, + renameBlock = { entity, suffix -> + entity.copy(name = "${entity.name} $suffix") + }, + ) + + if (existingGroupUids.contains(modifiedGroup.uid)) { + groupRepository.update(modifiedGroup) + } else { + groupRepository.insert(modifiedGroup) + } + } + + /** + * Converts the group relationships into trees. This first finds all the root groups which + * have no parent. Then it loops over all the other groups indefinitely until they have been + * added to their parent. If the parent does not exist while looping then it is skipped and + * processed in the next iteration. + * + * @return A list of the root nodes for all the group trees. + */ + private fun buildGroupTrees(groups: List): List> { + if (groups.isEmpty()) { + return emptyList() + } + + val nodeMap = mutableMapOf>() + val rootNodes = mutableListOf>() + + val groupQueue = LinkedList() + + for (group in groups) { + if (group.parentUid == null) { + val node = TreeNode(group) + nodeMap[group.uid] = node + rootNodes.add(node) + } else { + groupQueue.add(group) + } + } + + while (groupQueue.isNotEmpty()) { + val groupsToRemove = mutableListOf() + + for (group in groupQueue) { + if (nodeMap.containsKey(group.parentUid)) { + val node = TreeNode(group) + nodeMap[group.uid] = node + nodeMap[group.parentUid]!!.children.add(node) + groupsToRemove.add(group) + } + } + + groupQueue.removeAll(groupsToRemove.toSet()) + } + + return rootNodes + } + private suspend fun appendKeyMapsInRepository(keyMaps: List) = withContext(dispatchers.default()) { val randomUids = keyMaps.map { it.copy(uid = UUID.randomUUID().toString()) } keyMapRepository.insert(*randomUids.toTypedArray()) diff --git a/app/src/main/java/io/github/sds100/keymapper/util/TreeNode.kt b/app/src/main/java/io/github/sds100/keymapper/util/TreeNode.kt new file mode 100644 index 0000000000..52e63d0e30 --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/util/TreeNode.kt @@ -0,0 +1,16 @@ +package io.github.sds100.keymapper.util + +data class TreeNode(val value: T, val children: MutableList> = mutableListOf()) + +inline fun TreeNode.breadFirstTraversal( + action: (T) -> Unit, +) { + val queue = ArrayDeque>() + queue.add(this) + + while (queue.isNotEmpty()) { + val currentNode = queue.removeFirst() + action(currentNode.value) + queue.addAll(currentNode.children) + } +} diff --git a/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt b/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt index a76df90b1a..287e83fde1 100644 --- a/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/BackupManagerTest.kt @@ -136,6 +136,61 @@ class BackupManagerTest { Dispatchers.resetMain() } + /** + * Issue #1655. If the list of groups in the backup has a child before the parent then the + * parent must be restored first. Otherwise the SqliteConstraintException will be thrown. + */ + @Test + fun `restore groups breadth first so parents exist before children are restored with child first in the backup`() = runTest(testDispatcher) { + val parentGroup1 = GroupEntity( + uid = "parent_group_1_uid", + name = "parent_group_1_name", + parentUid = null, + lastOpenedDate = 0L, + ) + + val parentGroup2 = GroupEntity( + uid = "parent_group_2_uid", + name = "parent_group_2_name", + parentUid = null, + lastOpenedDate = 0L, + ) + + val childGroup = GroupEntity( + uid = "child_group_uid", + name = "child_group_name", + parentUid = parentGroup1.uid, + lastOpenedDate = 0L, + ) + + val grandChildGroup = GroupEntity( + uid = "grand_child_group_uid", + name = "grand_child_group_name", + parentUid = childGroup.uid, + lastOpenedDate = 0L, + ) + + val backupContent = BackupContent( + appVersion = Constants.VERSION_CODE, + dbVersion = AppDatabase.DATABASE_VERSION, + groups = listOf(childGroup, grandChildGroup, parentGroup1), + ) + + inOrder(mockGroupRepository) { + backupManager.restore( + RestoreType.REPLACE, + backupContent, + emptyList(), + currentTime = 0L, + ) + + verify(mockGroupRepository).insert(parentGroup1) + verify(mockGroupRepository).insert(childGroup) + verify(mockGroupRepository).insert(grandChildGroup) + verify(mockGroupRepository, never()).update(any()) + } + } + /** * Issue #1655. If the list of groups in the backup has a child before the parent then the * parent must be restored first. Otherwise the SqliteConstraintException will be thrown. @@ -184,10 +239,10 @@ class BackupManagerTest { currentTime = 0L, ) + verify(mockGroupRepository).insert(parentGroup2) verify(mockGroupRepository).insert(parentGroup1) verify(mockGroupRepository).insert(childGroup) verify(mockGroupRepository).insert(grandChildGroup) - verify(mockGroupRepository).insert(parentGroup2) verify(mockGroupRepository, never()).update(any()) } } From d9462bd721b42d7b943d96cb5457698de1aa5847 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 17:55:04 +0200 Subject: [PATCH 37/95] fix: update datastore library to try and fix the InvalidProtocolBufferException --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index e442585f81..13f120a407 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -225,7 +225,7 @@ dependencies { implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" implementation "androidx.room:room-runtime:$room_version" implementation "androidx.viewpager2:viewpager2:1.1.0" - implementation "androidx.datastore:datastore-preferences:1.2.0-alpha01" + implementation "androidx.datastore:datastore-preferences:1.2.0-alpha02" implementation "androidx.core:core-splashscreen:1.0.1" implementation "androidx.activity:activity-compose:1.10.1" implementation "androidx.navigation:navigation-compose:2.8.9" From 162651ae8c29c9dc5f488888ec08af8b9425891f Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 9 May 2025 17:56:03 +0200 Subject: [PATCH 38/95] #1688 fix: do not crash when inserting key maps if they already exist --- .../java/io/github/sds100/keymapper/actions/Action.kt | 10 +++------- .../keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt | 8 +++++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt b/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt index df4eccb63b..b5a2a5db4e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt @@ -33,11 +33,7 @@ data class Action( val multiplier: Int? = null, val delayBeforeNextAction: Int? = null, -) { - companion object { - const val REPEAT_DELAY_MIN = 0 - } -} +) object ActionEntityMapper { fun fromEntity(entity: ActionEntity): Action? { @@ -108,7 +104,7 @@ object ActionEntityMapper { ) } - fun toEntity(keyMap: KeyMap): List = keyMap.actionList.mapNotNull { action -> + fun toEntity(keyMap: KeyMap): List = keyMap.actionList.map { action -> val base = ActionDataEntityMapper.toEntity(action.data) val extras = mutableListOf().apply { @@ -187,7 +183,7 @@ object ActionEntityMapper { flags = flags.withFlag(ActionEntity.ACTION_FLAG_HOLD_DOWN) } - return@mapNotNull ActionEntity( + return@map ActionEntity( type = base.type, data = base.data, extras = base.extras.plus(extras), diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt index 5f69f2bff7..842264bfea 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.mappings.keymaps +import android.database.sqlite.SQLiteConstraintException import io.github.sds100.keymapper.actions.Action import io.github.sds100.keymapper.actions.ActionData import io.github.sds100.keymapper.actions.RepeatMode @@ -851,7 +852,12 @@ class ConfigKeyMapUseCaseController( val keyMap = keyMap.value.dataOrNull() ?: return if (keyMap.dbId == null) { - keyMapRepository.insert(KeyMapEntityMapper.toEntity(keyMap, 0)) + val entity = KeyMapEntityMapper.toEntity(keyMap, 0) + try { + keyMapRepository.insert(entity) + } catch (e: SQLiteConstraintException) { + keyMapRepository.update(entity) + } } else { keyMapRepository.update(KeyMapEntityMapper.toEntity(keyMap, keyMap.dbId)) } From 6f2a6f731c4529fbb3e6aa016cb0c7d1fd50eb38 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 11:20:54 +0200 Subject: [PATCH 39/95] #1684 fix: devices in config key event screen are cleared --- .../keyevent/ConfigKeyEventActionFragment.kt | 88 ++++++++++--------- .../res/layout/fragment_config_key_event.xml | 1 - 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt index d674a421df..1f6dfcf81d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionFragment.kt @@ -51,6 +51,14 @@ class ConfigKeyEventActionFragment : Fragment() { val binding: FragmentConfigKeyEventBinding get() = _binding!! + private val deviceArrayAdapter: ArrayAdapter by lazy { + ArrayAdapter( + requireContext(), + R.layout.dropdown_menu_popup_item, + mutableListOf(), + ) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -98,47 +106,9 @@ class ConfigKeyEventActionFragment : Fragment() { findNavController().navigateUp() } - viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.RESUMED) { - viewModel.returnResult.collectLatest { - setFragmentResult( - requestKey, - Bundle().apply { putJsonSerializable(EXTRA_RESULT, it) }, - ) - - findNavController().navigateUp() - } - } - - viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.RESUMED) { - viewModel.uiState.collectLatest { state -> - binding.epoxyRecyclerViewModifiers.withModels { - state.modifierListItems.forEach { listItem -> - configuredCheckBox(listItem) { isChecked -> - viewModel.setModifierKeyChecked(listItem.id.toInt(), isChecked) - } - } - } - - ArrayAdapter( - requireContext(), - R.layout.dropdown_menu_popup_item, - mutableListOf(), - ).apply { - clear() - add(str(R.string.from_no_device)) - - state.deviceListItems.forEach { - add(it.name) - } - - binding.dropdownDeviceId.setAdapter(this) - } - - binding.textInputLayoutKeyCode.error = state.keyCodeErrorMessage - } - } - binding.dropdownDeviceId.apply { + setAdapter(deviceArrayAdapter) + // set the default value setText(str(R.string.from_no_device), false) @@ -173,6 +143,44 @@ class ConfigKeyEventActionFragment : Fragment() { } } } + + viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.returnResult.collectLatest { + setFragmentResult( + requestKey, + Bundle().apply { putJsonSerializable(EXTRA_RESULT, it) }, + ) + + findNavController().navigateUp() + } + } + + viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.uiState.collect { state -> + binding.epoxyRecyclerViewModifiers.withModels { + state.modifierListItems.forEach { listItem -> + configuredCheckBox(listItem) { isChecked -> + viewModel.setModifierKeyChecked(listItem.id.toInt(), isChecked) + } + } + } + + deviceArrayAdapter.apply { + clear() + add(str(R.string.from_no_device)) + for (device in state.deviceListItems) { + add(device.name) + } + notifyDataSetChanged() + } + + // Filtering must be false so that the dropdown items aren't cleared + // when setting text. + binding.dropdownDeviceId.setText(state.chosenDeviceName, false) + + binding.textInputLayoutKeyCode.error = state.keyCodeErrorMessage + } + } } override fun onDestroyView() { diff --git a/app/src/main/res/layout/fragment_config_key_event.xml b/app/src/main/res/layout/fragment_config_key_event.xml index c9947104c9..41011c8334 100644 --- a/app/src/main/res/layout/fragment_config_key_event.xml +++ b/app/src/main/res/layout/fragment_config_key_event.xml @@ -123,7 +123,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="none" - android:text="@{viewModel.uiState.chosenDeviceName}" tools:ignore="LabelFor" /> From f4b1b05565a317a568cd1b035756b6e533476615 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 11:21:07 +0200 Subject: [PATCH 40/95] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 1085b82907..2beb635ef1 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.0 -VERSION_CODE=114 +VERSION_CODE=115 VERSION_NUM=0 \ No newline at end of file From d846114608ac1911a68e8c765d5e67e6293706de Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Sat, 10 May 2025 09:21:34 +0000 Subject: [PATCH 41/95] New Crowdin translations by GitHub Action --- app/src/main/res/values-tr/strings.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 700903f43f..90b10fd127 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -21,7 +21,6 @@ Erişilebilirlik servisini yeniden başlat Paylaş Burada hiçbir şey yok! - Burada hiçbir şey yok! Tekrarlamayı durdur… Tetikleyici bırakıldığında Tetikleyici tekrar basıldığında @@ -893,7 +892,6 @@ Etkileşim detayları Açıklama Uygulama - Metin / içerik açıklaması Sınıf adı Kaynak kimliğini görüntüle Özgün kimlik From f1cffed78889c62cbab0ba31e370f04863451d77 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 11:22:30 +0200 Subject: [PATCH 42/95] chore: update changelog and whats new --- CHANGELOG.md | 2 +- app/src/main/assets/whats-new.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89a7a0807b..d71394fc8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [3.1.0](https://github.com/sds100/KeyMapper/releases/tag/v3.1.0) -#### TO BE RELEASED +#### 10 May 2025 ## Added diff --git a/app/src/main/assets/whats-new.txt b/app/src/main/assets/whats-new.txt index d1de1ca498..d2b07cf8ea 100644 --- a/app/src/main/assets/whats-new.txt +++ b/app/src/main/assets/whats-new.txt @@ -1,9 +1,9 @@ +Fix for Minecraft 1.21.80! + ⏰ Time constraints. 🔎 Action to interact with app elements. -Fix for Minecraft 1.21.80. - == 3.0 features == 🫧 Floating Buttons: you can create custom on-screen buttons to trigger key maps. From aad848afac4887c4889a256af575784b5dffb9db Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 11:27:06 +0200 Subject: [PATCH 43/95] fix test for ConfigKeyEventActionViewModel --- .../keyevent/ConfigKeyEventActionViewModel.kt | 18 ++++++++---------- ...ConfigKeyServiceEventActionViewModelTest.kt | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt index c514e7a2c1..30d1236f18 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/keyevent/ConfigKeyEventActionViewModel.kt @@ -59,7 +59,7 @@ class ConfigKeyEventActionViewModel( buildUiState(state, inputDevices, showDeviceDescriptors) }.stateIn( viewModelScope, - SharingStarted.Lazily, + SharingStarted.Eagerly, buildUiState( keyEventState.value, inputDeviceList = emptyList(), @@ -133,17 +133,15 @@ class ConfigKeyEventActionViewModel( } fun chooseDevice(index: Int) { - viewModelScope.launch { - val chosenDevice = uiState.value.deviceListItems.getOrNull(index) - - if (chosenDevice == null) { - return@launch - } + val chosenDevice = uiState.value.deviceListItems.getOrNull(index) - keyEventState.value = keyEventState.value.copy( - chosenDevice = chosenDevice, - ) + if (chosenDevice == null) { + return } + + keyEventState.value = keyEventState.value.copy( + chosenDevice = chosenDevice, + ) } fun onDoneClick() { diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/keyevents/ConfigKeyServiceEventActionViewModelTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/keyevents/ConfigKeyServiceEventActionViewModelTest.kt index e28c7f0db1..298c1dc130 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/keyevents/ConfigKeyServiceEventActionViewModelTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/keyevents/ConfigKeyServiceEventActionViewModelTest.kt @@ -46,7 +46,7 @@ class ConfigKeyServiceEventActionViewModelTest { @Before fun init() { Dispatchers.setMain(testDispatcher) - inputDevices = MutableStateFlow(emptyList()) + inputDevices = MutableStateFlow(emptyList()) mockUseCase = mock { on { showDeviceDescriptors }.then { MutableStateFlow(false) } From 301f108b2bad4d4fff3bf155af05aa79e2105ae2 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 11:28:34 +0200 Subject: [PATCH 44/95] fastlane: delete redundant translations --- .../metadata/android/ar/full_description.txt | 55 ------------------- .../metadata/android/ar/short_description.txt | 1 - fastlane/metadata/android/ar/title.txt | 1 - .../android/cs_CZ/full_description.txt | 55 ------------------- .../android/cs_CZ/short_description.txt | 1 - fastlane/metadata/android/cs_CZ/title.txt | 1 - .../android/de_DE/full_description.txt | 55 ------------------- .../android/de_DE/short_description.txt | 1 - fastlane/metadata/android/de_DE/title.txt | 1 - .../android/es_ES/full_description.txt | 55 ------------------- .../android/es_ES/short_description.txt | 1 - fastlane/metadata/android/es_ES/title.txt | 1 - .../android/fr_FR/full_description.txt | 55 ------------------- .../android/fr_FR/short_description.txt | 1 - fastlane/metadata/android/fr_FR/title.txt | 1 - .../android/hu_HU/full_description.txt | 55 ------------------- .../android/hu_HU/short_description.txt | 1 - fastlane/metadata/android/hu_HU/title.txt | 1 - .../android/id_ID/full_description.txt | 55 ------------------- .../android/id_ID/short_description.txt | 1 - fastlane/metadata/android/id_ID/title.txt | 1 - .../android/ka_GE/full_description.txt | 55 ------------------- .../android/ka_GE/short_description.txt | 1 - fastlane/metadata/android/ka_GE/title.txt | 1 - .../android/ko_KR/full_description.txt | 55 ------------------- .../android/ko_KR/short_description.txt | 1 - fastlane/metadata/android/ko_KR/title.txt | 1 - .../android/pl_PL/full_description.txt | 55 ------------------- .../android/pl_PL/short_description.txt | 1 - fastlane/metadata/android/pl_PL/title.txt | 1 - .../android/pt_BR/full_description.txt | 55 ------------------- .../android/pt_BR/short_description.txt | 1 - fastlane/metadata/android/pt_BR/title.txt | 1 - .../android/ru_RU/full_description.txt | 55 ------------------- .../android/ru_RU/short_description.txt | 1 - fastlane/metadata/android/ru_RU/title.txt | 1 - .../metadata/android/sk/full_description.txt | 55 ------------------- .../metadata/android/sk/short_description.txt | 1 - fastlane/metadata/android/sk/title.txt | 1 - .../metadata/android/uk/full_description.txt | 55 ------------------- .../metadata/android/uk/short_description.txt | 1 - fastlane/metadata/android/uk/title.txt | 1 - .../metadata/android/vi/full_description.txt | 55 ------------------- .../metadata/android/vi/short_description.txt | 1 - fastlane/metadata/android/vi/title.txt | 1 - .../android/zh_CN/full_description.txt | 55 ------------------- .../android/zh_CN/short_description.txt | 1 - fastlane/metadata/android/zh_CN/title.txt | 1 - .../android/zh_TW/full_description.txt | 55 ------------------- .../android/zh_TW/short_description.txt | 1 - fastlane/metadata/android/zh_TW/title.txt | 1 - 51 files changed, 969 deletions(-) delete mode 100644 fastlane/metadata/android/ar/full_description.txt delete mode 100644 fastlane/metadata/android/ar/short_description.txt delete mode 100644 fastlane/metadata/android/ar/title.txt delete mode 100644 fastlane/metadata/android/cs_CZ/full_description.txt delete mode 100644 fastlane/metadata/android/cs_CZ/short_description.txt delete mode 100644 fastlane/metadata/android/cs_CZ/title.txt delete mode 100644 fastlane/metadata/android/de_DE/full_description.txt delete mode 100644 fastlane/metadata/android/de_DE/short_description.txt delete mode 100644 fastlane/metadata/android/de_DE/title.txt delete mode 100644 fastlane/metadata/android/es_ES/full_description.txt delete mode 100644 fastlane/metadata/android/es_ES/short_description.txt delete mode 100644 fastlane/metadata/android/es_ES/title.txt delete mode 100644 fastlane/metadata/android/fr_FR/full_description.txt delete mode 100644 fastlane/metadata/android/fr_FR/short_description.txt delete mode 100644 fastlane/metadata/android/fr_FR/title.txt delete mode 100644 fastlane/metadata/android/hu_HU/full_description.txt delete mode 100644 fastlane/metadata/android/hu_HU/short_description.txt delete mode 100644 fastlane/metadata/android/hu_HU/title.txt delete mode 100644 fastlane/metadata/android/id_ID/full_description.txt delete mode 100644 fastlane/metadata/android/id_ID/short_description.txt delete mode 100644 fastlane/metadata/android/id_ID/title.txt delete mode 100644 fastlane/metadata/android/ka_GE/full_description.txt delete mode 100644 fastlane/metadata/android/ka_GE/short_description.txt delete mode 100644 fastlane/metadata/android/ka_GE/title.txt delete mode 100644 fastlane/metadata/android/ko_KR/full_description.txt delete mode 100644 fastlane/metadata/android/ko_KR/short_description.txt delete mode 100644 fastlane/metadata/android/ko_KR/title.txt delete mode 100644 fastlane/metadata/android/pl_PL/full_description.txt delete mode 100644 fastlane/metadata/android/pl_PL/short_description.txt delete mode 100644 fastlane/metadata/android/pl_PL/title.txt delete mode 100644 fastlane/metadata/android/pt_BR/full_description.txt delete mode 100644 fastlane/metadata/android/pt_BR/short_description.txt delete mode 100644 fastlane/metadata/android/pt_BR/title.txt delete mode 100644 fastlane/metadata/android/ru_RU/full_description.txt delete mode 100644 fastlane/metadata/android/ru_RU/short_description.txt delete mode 100644 fastlane/metadata/android/ru_RU/title.txt delete mode 100644 fastlane/metadata/android/sk/full_description.txt delete mode 100644 fastlane/metadata/android/sk/short_description.txt delete mode 100644 fastlane/metadata/android/sk/title.txt delete mode 100644 fastlane/metadata/android/uk/full_description.txt delete mode 100644 fastlane/metadata/android/uk/short_description.txt delete mode 100644 fastlane/metadata/android/uk/title.txt delete mode 100644 fastlane/metadata/android/vi/full_description.txt delete mode 100644 fastlane/metadata/android/vi/short_description.txt delete mode 100644 fastlane/metadata/android/vi/title.txt delete mode 100644 fastlane/metadata/android/zh_CN/full_description.txt delete mode 100644 fastlane/metadata/android/zh_CN/short_description.txt delete mode 100644 fastlane/metadata/android/zh_CN/title.txt delete mode 100644 fastlane/metadata/android/zh_TW/full_description.txt delete mode 100644 fastlane/metadata/android/zh_TW/short_description.txt delete mode 100644 fastlane/metadata/android/zh_TW/title.txt diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/ar/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ar/short_description.txt b/fastlane/metadata/android/ar/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ar/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ar/title.txt b/fastlane/metadata/android/ar/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/ar/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/full_description.txt b/fastlane/metadata/android/cs_CZ/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/cs_CZ/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/short_description.txt b/fastlane/metadata/android/cs_CZ/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/cs_CZ/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/title.txt b/fastlane/metadata/android/cs_CZ/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/cs_CZ/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/full_description.txt b/fastlane/metadata/android/de_DE/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/de_DE/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/short_description.txt b/fastlane/metadata/android/de_DE/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/de_DE/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/title.txt b/fastlane/metadata/android/de_DE/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/de_DE/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/full_description.txt b/fastlane/metadata/android/es_ES/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/es_ES/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/short_description.txt b/fastlane/metadata/android/es_ES/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/es_ES/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/title.txt b/fastlane/metadata/android/es_ES/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/es_ES/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/full_description.txt b/fastlane/metadata/android/fr_FR/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/fr_FR/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/short_description.txt b/fastlane/metadata/android/fr_FR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/fr_FR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/title.txt b/fastlane/metadata/android/fr_FR/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/fr_FR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/full_description.txt b/fastlane/metadata/android/hu_HU/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/hu_HU/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/short_description.txt b/fastlane/metadata/android/hu_HU/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/hu_HU/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/title.txt b/fastlane/metadata/android/hu_HU/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/hu_HU/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/full_description.txt b/fastlane/metadata/android/id_ID/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/id_ID/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/short_description.txt b/fastlane/metadata/android/id_ID/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/id_ID/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/title.txt b/fastlane/metadata/android/id_ID/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/id_ID/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/full_description.txt b/fastlane/metadata/android/ka_GE/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/ka_GE/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/short_description.txt b/fastlane/metadata/android/ka_GE/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ka_GE/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/title.txt b/fastlane/metadata/android/ka_GE/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/ka_GE/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/full_description.txt b/fastlane/metadata/android/ko_KR/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/ko_KR/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/short_description.txt b/fastlane/metadata/android/ko_KR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ko_KR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/title.txt b/fastlane/metadata/android/ko_KR/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/ko_KR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/full_description.txt b/fastlane/metadata/android/pl_PL/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/pl_PL/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/short_description.txt b/fastlane/metadata/android/pl_PL/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/pl_PL/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/title.txt b/fastlane/metadata/android/pl_PL/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/pl_PL/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/full_description.txt b/fastlane/metadata/android/pt_BR/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/pt_BR/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/short_description.txt b/fastlane/metadata/android/pt_BR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/pt_BR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/title.txt b/fastlane/metadata/android/pt_BR/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/pt_BR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/full_description.txt b/fastlane/metadata/android/ru_RU/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/ru_RU/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/short_description.txt b/fastlane/metadata/android/ru_RU/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ru_RU/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/title.txt b/fastlane/metadata/android/ru_RU/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/ru_RU/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/sk/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/sk/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/sk/title.txt b/fastlane/metadata/android/sk/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/sk/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/uk/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/uk/short_description.txt b/fastlane/metadata/android/uk/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/uk/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/uk/title.txt b/fastlane/metadata/android/uk/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/uk/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/vi/full_description.txt b/fastlane/metadata/android/vi/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/vi/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/vi/short_description.txt b/fastlane/metadata/android/vi/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/vi/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/vi/title.txt b/fastlane/metadata/android/vi/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/vi/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/full_description.txt b/fastlane/metadata/android/zh_CN/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/zh_CN/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/short_description.txt b/fastlane/metadata/android/zh_CN/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/zh_CN/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/title.txt b/fastlane/metadata/android/zh_CN/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/zh_CN/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/full_description.txt b/fastlane/metadata/android/zh_TW/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/zh_TW/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/short_description.txt b/fastlane/metadata/android/zh_TW/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/zh_TW/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/title.txt b/fastlane/metadata/android/zh_TW/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/zh_TW/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file From 992354ad597f543540007c195a6822a599101e12 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 11:55:27 +0200 Subject: [PATCH 45/95] fastlane: shorten turkish short description --- fastlane/metadata/android/tr_TR/short_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/tr_TR/short_description.txt b/fastlane/metadata/android/tr_TR/short_description.txt index 9dbca48bd4..11ab9493ec 100644 --- a/fastlane/metadata/android/tr_TR/short_description.txt +++ b/fastlane/metadata/android/tr_TR/short_description.txt @@ -1 +1 @@ -HER ŞEY için kısayollar oluşturun! Ses, güç, klavye veya kayan düğmeleri yeniden atayın! \ No newline at end of file +HER ŞEY için kısayollar oluşturun! \ No newline at end of file From 13a1d0ccfa012c3572eba5969d18000d0244be75 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 12:05:31 +0200 Subject: [PATCH 46/95] chore: bump version to 3.1.1 --- app/version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/version.properties b/app/version.properties index 2beb635ef1..51a39df92c 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ -VERSION_NAME=3.1.0 -VERSION_CODE=115 +VERSION_NAME=3.1.1 +VERSION_CODE=116 VERSION_NUM=0 \ No newline at end of file From 551a95db3af1c23674ae3ed1391cc74cf5f4ff07 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 12:19:04 +0200 Subject: [PATCH 47/95] fix: do not automatically select the key mapper keyboard when the accessibility service starts --- CHANGELOG.md | 8 ++++++++ .../system/accessibility/MyAccessibilityService.kt | 11 ----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d71394fc8b..bddc45fde1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [3.1.1](https://github.com/sds100/KeyMapper/releases/tag/v3.1.1) + +#### TO BE RELEASED + +## Bug fixes + +- Do not automatically select the key mapper keyboard when the accessibility service starts. + ## [3.1.0](https://github.com/sds100/KeyMapper/releases/tag/v3.1.0) #### 10 May 2025 diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt index 81871a3575..c640bcf18f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt @@ -22,9 +22,7 @@ import androidx.lifecycle.LifecycleRegistry import androidx.savedstate.SavedStateRegistry import androidx.savedstate.SavedStateRegistryController import androidx.savedstate.SavedStateRegistryOwner -import io.github.sds100.keymapper.Constants import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.ServiceLocator import io.github.sds100.keymapper.actions.pinchscreen.PinchScreenType import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback import io.github.sds100.keymapper.api.KeyEventRelayService @@ -40,7 +38,6 @@ import io.github.sds100.keymapper.util.InputEventType import io.github.sds100.keymapper.util.MathUtils import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.Success -import io.github.sds100.keymapper.util.onSuccess import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -202,14 +199,6 @@ class MyAccessibilityService : override fun onServiceConnected() { super.onServiceConnected() - val inputMethodAdapter = ServiceLocator.inputMethodAdapter(this) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - inputMethodAdapter.getInfoByPackageName(Constants.PACKAGE_NAME).onSuccess { - softKeyboardController.setInputMethodEnabled(it.id, true) - softKeyboardController.switchToInputMethod(it.id) - } - } Timber.i("Accessibility service: onServiceConnected") lifecycleRegistry.currentState = Lifecycle.State.STARTED From 241ac706a63a73d01d440c1b603cab3d6f623bc4 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 12:34:29 +0200 Subject: [PATCH 48/95] #1686 fix: do not show some screens behind system bars on the left/right side of the device. --- CHANGELOG.md | 1 + .../sds100/keymapper/home/HomeKeyMapListScreen.kt | 5 ++++- .../github/sds100/keymapper/home/KeyMapListAppBar.kt | 10 ++++++++-- .../keymapper/mappings/keymaps/ConfigKeyMapFragment.kt | 9 ++++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bddc45fde1..5aa93a622b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Bug fixes - Do not automatically select the key mapper keyboard when the accessibility service starts. +- #1686 do not show some screens behind system bars on the left/right side of the device. ## [3.1.0](https://github.com/sds100/KeyMapper/releases/tag/v3.1.0) diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt index dc46702f69..c169e8c47d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.rememberLazyListState @@ -172,7 +173,9 @@ fun HomeKeyMapListScreen( }, listContent = { KeyMapList( - modifier = Modifier.animateContentSize(), + modifier = Modifier + .windowInsetsPadding(WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal)) + .animateContentSize(), lazyListState = rememberLazyListState(), listItems = state.listItems, footerText = stringResource(R.string.home_key_map_list_footer_text), diff --git a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt index 2990b29b39..460dce2aa3 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt @@ -21,13 +21,17 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -394,6 +398,7 @@ private fun RootGroupAppBar( Surface(color = appBarContainerColor) { GroupRow( modifier = Modifier + .windowInsetsPadding(WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal)) .padding(horizontal = 8.dp) .fillMaxWidth(), groups = state.subGroups, @@ -405,6 +410,7 @@ private fun RootGroupAppBar( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun ChildGroupAppBar( modifier: Modifier = Modifier, @@ -439,7 +445,7 @@ private fun ChildGroupAppBar( Column { Row( Modifier - .statusBarsPadding() + .windowInsetsPadding(TopAppBarDefaults.windowInsets) .fillMaxWidth() .heightIn(min = 48.dp) .padding(vertical = 8.dp) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt index be3ad1e6c7..24d56ea5f8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt @@ -4,7 +4,12 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment @@ -65,7 +70,9 @@ class ConfigKeyMapFragment : Fragment() { setContent { KeyMapperTheme { ConfigKeyMapScreen( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .windowInsetsPadding(WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal)) + .fillMaxSize(), viewModel = viewModel, navigateBack = findNavController()::navigateUp, ) From 6bf9e99739c33fee6912acf76be0f8eea5a52560 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 12:37:23 +0200 Subject: [PATCH 49/95] fix: Use same sized list items when choosing a constraint. --- CHANGELOG.md | 1 + .../keymapper/constraints/ChooseConstraintScreen.kt | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa93a622b..203fcf2d01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Do not automatically select the key mapper keyboard when the accessibility service starts. - #1686 do not show some screens behind system bars on the left/right side of the device. +- Use same sized list items when choosing a constraint. ## [3.1.0](https://github.com/sds100/KeyMapper/releases/tag/v3.1.0) diff --git a/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintScreen.kt b/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintScreen.kt index 6ed94f2c43..958b557d29 100644 --- a/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintScreen.kt @@ -49,7 +49,7 @@ import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo -import io.github.sds100.keymapper.util.ui.compose.SimpleListItem +import io.github.sds100.keymapper.util.ui.compose.SimpleListItemFixedHeight import io.github.sds100.keymapper.util.ui.compose.SimpleListItemModel import kotlinx.coroutines.flow.update @@ -246,7 +246,7 @@ private fun ListScreen( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { items(listItems, key = { it.id }) { model -> - SimpleListItem( + SimpleListItemFixedHeight( modifier = Modifier.fillMaxWidth(), model = model, onClick = { onClickAction(model.id) }, @@ -291,12 +291,12 @@ private fun PreviewGrid() { state = State.Data( listOf( SimpleListItemModel( - "app", + "app1", title = "App in foreground", icon = ComposeIconInfo.Vector(Icons.Rounded.Android), ), SimpleListItemModel( - "app", + "app2", title = "App not in foreground", icon = ComposeIconInfo.Vector(Icons.Rounded.Android), subtitle = "Error", From 58c15ca43a48b3226c93861448e16bfa1a63ee27 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 12:51:01 +0200 Subject: [PATCH 50/95] #1637 feat: show a home screen error if notification permission is not granted --- CHANGELOG.md | 4 ++++ .../keymapper/home/ShowHomeScreenAlertsUseCase.kt | 10 ++++++++++ .../mappings/keymaps/KeyMapListViewModel.kt | 14 +++++++++++++- app/src/main/res/values/strings.xml | 1 + 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 203fcf2d01..71db0deb3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ #### TO BE RELEASED +## Added + +- #1637 show a home screen error if notification permission is not granted. + ## Bug fixes - Do not automatically select the key mapper keyboard when the accessibility service starts. diff --git a/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt index 84cf3a0fb2..edbadbe221 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt @@ -52,6 +52,13 @@ class ShowHomeScreenAlertsUseCaseImpl( override fun disableLogging() { preferences.set(Keys.log, false) } + + override val isNotificationPermissionGranted: Flow = + permissions.isGrantedFlow(Permission.POST_NOTIFICATIONS) + + override fun requestNotificationPermission() { + permissions.request(Permission.POST_NOTIFICATIONS) + } } interface ShowHomeScreenAlertsUseCase { @@ -68,4 +75,7 @@ interface ShowHomeScreenAlertsUseCase { val isLoggingEnabled: Flow fun disableLogging() + + val isNotificationPermissionGranted: Flow + fun requestNotificationPermission() } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt index 5d4d4e4b7c..1037bec89f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt @@ -94,6 +94,7 @@ class KeyMapListViewModel( const val ID_ACCESSIBILITY_SERVICE_CRASHED_LIST_ITEM = "accessibility_service_crashed" const val ID_BATTERY_OPTIMISATION_LIST_ITEM = "battery_optimised" const val ID_LOGGING_ENABLED_LIST_ITEM = "logging_enabled" + const val ID_NOTIFICATION_PERMISSION_DENIED_LIST_ITEM = "notification_permission_denied" } val sortViewModel = SortViewModel(coroutineScope, sortKeyMaps) @@ -152,7 +153,8 @@ class KeyMapListViewModel( showAlertsUseCase.accessibilityServiceState, showAlertsUseCase.hideAlerts, showAlertsUseCase.isLoggingEnabled, - ) { isBatteryOptimised, serviceState, isHidden, isLoggingEnabled -> + showAlertsUseCase.isNotificationPermissionGranted, + ) { isBatteryOptimised, serviceState, isHidden, isLoggingEnabled, isNotificationPermissionGranted -> if (isHidden) { return@combine emptyList() } @@ -187,6 +189,15 @@ class KeyMapListViewModel( ) } // don't show a success message for this + if (!isNotificationPermissionGranted) { + add( + HomeWarningListItem( + ID_NOTIFICATION_PERMISSION_DENIED_LIST_ITEM, + getString(R.string.home_error_notification_permission), + ), + ) + } + if (isLoggingEnabled) { add( HomeWarningListItem( @@ -672,6 +683,7 @@ class KeyMapListViewModel( ID_BATTERY_OPTIMISATION_LIST_ITEM -> showAlertsUseCase.disableBatteryOptimisation() ID_LOGGING_ENABLED_LIST_ITEM -> showAlertsUseCase.disableLogging() + ID_NOTIFICATION_PERMISSION_DENIED_LIST_ITEM -> showAlertsUseCase.requestNotificationPermission() } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20ee36a04c..e63716332c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -90,6 +90,7 @@ The accessibility service is enabled! Your key maps should work. Extra logging is turned on! Turn this off if you aren\'t trying to fix an issue. Turn off + Turn on notifications for better on-screen messages, more actions and service updates. About From 8832c2b3c1756c00d0ba27ab345dc485641c3400 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 13:56:34 +0200 Subject: [PATCH 51/95] fix: trim search query string when filtering --- .../main/java/io/github/sds100/keymapper/util/FilterUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/util/FilterUtils.kt b/app/src/main/java/io/github/sds100/keymapper/util/FilterUtils.kt index ea8a9073cc..4f723f7a07 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/FilterUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/FilterUtils.kt @@ -30,5 +30,5 @@ suspend fun List.filterByQuery(query: String?): Flow Date: Sat, 10 May 2025 14:13:41 +0200 Subject: [PATCH 52/95] #1435 feat: allow playing system sounds/ringtones for the Sound action --- CHANGELOG.md | 1 + .../github/sds100/keymapper/KeyMapperApp.kt | 5 ++ .../github/sds100/keymapper/ServiceLocator.kt | 3 + .../io/github/sds100/keymapper/UseCases.kt | 4 + .../sds100/keymapper/actions/ActionData.kt | 31 ++++++-- .../actions/ActionDataEntityMapper.kt | 30 +++++-- .../keymapper/actions/ActionErrorSnapshot.kt | 10 ++- .../keymapper/actions/ActionUiHelper.kt | 12 ++- .../keymapper/actions/CreateActionDelegate.kt | 7 +- .../keymapper/actions/DisplayActionUseCase.kt | 1 + .../actions/GetActionErrorUseCase.kt | 3 + .../actions/PerformActionsUseCase.kt | 11 ++- .../actions/sound/ChooseSoundFileFragment.kt | 31 +++++++- .../actions/sound/ChooseSoundFileResult.kt | 9 --- .../actions/sound/ChooseSoundFileViewModel.kt | 42 +++++++--- .../keymapper/mappings/PauseKeyMapsUseCase.kt | 3 + .../mappings/keymaps/DisplayKeyMapUseCase.kt | 14 ++-- .../comparators/KeyMapActionsComparator.kt | 3 +- .../system/ringtones/RingtoneAdapter.kt | 79 +++++++++++++++++++ .../sds100/keymapper/util/FilterUtils.kt | 2 +- .../keymapper/util/ui/NavDestination.kt | 3 +- .../keymapper/util/ui/NavigationViewModel.kt | 3 +- .../res/layout/fragment_choose_sound_file.xml | 20 ++++- app/src/main/res/values/strings.xml | 4 +- 24 files changed, 271 insertions(+), 60 deletions(-) delete mode 100644 app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileResult.kt create mode 100644 app/src/main/java/io/github/sds100/keymapper/system/ringtones/RingtoneAdapter.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 71db0deb3e..d190e184a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Added - #1637 show a home screen error if notification permission is not granted. +- #1435 Pick system sounds/ringtones for the Sound action. ## Bug fixes diff --git a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt index 162bbd5858..4019a372cc 100644 --- a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt +++ b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt @@ -50,6 +50,7 @@ import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.phone.AndroidPhoneAdapter import io.github.sds100.keymapper.system.popup.AndroidToastAdapter import io.github.sds100.keymapper.system.power.AndroidPowerAdapter +import io.github.sds100.keymapper.system.ringtones.AndroidRingtoneAdapter import io.github.sds100.keymapper.system.root.SuAdapterImpl import io.github.sds100.keymapper.system.url.AndroidOpenUrlAdapter import io.github.sds100.keymapper.system.vibrator.AndroidVibratorAdapter @@ -174,6 +175,10 @@ class KeyMapperApp : MultiDexApplication() { PurchasingManagerImpl(this.applicationContext, appCoroutineScope) } + val ringtoneManagerAdapter: AndroidRingtoneAdapter by lazy { + AndroidRingtoneAdapter(this) + } + private val loggingTree by lazy { KeyMapperLoggingTree( appCoroutineScope, diff --git a/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt b/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt index c0dd401af4..4151e715e2 100755 --- a/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt @@ -49,6 +49,7 @@ import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter import io.github.sds100.keymapper.system.phone.PhoneAdapter import io.github.sds100.keymapper.system.popup.PopupMessageAdapter import io.github.sds100.keymapper.system.power.PowerAdapter +import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter import io.github.sds100.keymapper.system.root.SuAdapter import io.github.sds100.keymapper.system.url.OpenUrlAdapter import io.github.sds100.keymapper.system.vibrator.VibratorAdapter @@ -296,6 +297,8 @@ object ServiceLocator { fun purchasingManager(context: Context): PurchasingManagerImpl = (context.applicationContext as KeyMapperApp).purchasingManager + fun ringtoneAdapter(context: Context): RingtoneAdapter = (context.applicationContext as KeyMapperApp).ringtoneManagerAdapter + private fun createDatabase(context: Context): AppDatabase = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, diff --git a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt index 260a3eca4a..3950710c45 100644 --- a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt +++ b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt @@ -57,6 +57,7 @@ object UseCases { ServiceLocator.accessibilityServiceAdapter(ctx), ServiceLocator.settingsRepository(ctx), ServiceLocator.purchasingManager(ctx), + ServiceLocator.ringtoneAdapter(ctx), getActionError(ctx), getConstraintError(ctx), ) @@ -71,6 +72,7 @@ object UseCases { ServiceLocator.cameraAdapter(ctx), ServiceLocator.soundsManager(ctx), ServiceLocator.shizukuAdapter(ctx), + ServiceLocator.ringtoneAdapter(ctx), ) fun getConstraintError(ctx: Context) = GetConstraintErrorUseCaseImpl( @@ -100,6 +102,7 @@ object UseCases { fun pauseKeyMaps(ctx: Context) = PauseKeyMapsUseCaseImpl( ServiceLocator.settingsRepository(ctx), ServiceLocator.mediaAdapter(ctx), + ServiceLocator.ringtoneAdapter(ctx), ) fun showImePicker(ctx: Context): ShowInputMethodPickerUseCase = ShowInputMethodPickerUseCaseImpl( @@ -163,6 +166,7 @@ object UseCases { ServiceLocator.soundsManager(ctx), ServiceLocator.permissionAdapter(ctx), ServiceLocator.notificationReceiverAdapter(ctx), + ServiceLocator.ringtoneAdapter(ctx), ) fun detectKeyMaps( diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt index 9a2734bb14..7889ee4f2a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionData.kt @@ -67,15 +67,32 @@ sealed class ActionData : Comparable { } @Serializable - data class Sound( - val soundUid: String, - val soundDescription: String, - ) : ActionData() { + sealed class Sound : ActionData() { override val id = ActionId.SOUND - override fun compareTo(other: ActionData) = when (other) { - is Sound -> soundUid.compareTo(other.soundUid) - else -> super.compareTo(other) + @Serializable + data class SoundFile( + val soundUid: String, + val soundDescription: String, + ) : Sound() { + override fun compareTo(other: ActionData): Int { + return when (other) { + is SoundFile -> soundUid.compareTo(other.soundUid) + else -> super.compareTo(other) + } + } + } + + @Serializable + data class Ringtone( + val uri: String, + ) : Sound() { + override fun compareTo(other: ActionData): Int { + return when (other) { + is Ringtone -> uri.compareTo(other.uri) + else -> super.compareTo(other) + } + } } } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt index ce246ca4cf..b56a2856ee 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionDataEntityMapper.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.actions +import androidx.core.net.toUri import io.github.sds100.keymapper.actions.pinchscreen.PinchScreenType import io.github.sds100.keymapper.actions.uielement.NodeInteractionType import io.github.sds100.keymapper.data.db.typeconverter.ConstantTypeConverters @@ -239,11 +240,24 @@ object ActionDataEntityMapper { ActionId.PHONE_CALL -> ActionData.PhoneCall(number = entity.data) ActionId.SOUND -> { - val soundFileDescription = - entity.extras.getData(ActionEntity.EXTRA_SOUND_FILE_DESCRIPTION) - .valueOrNull() ?: return null + val isRingtoneUri = try { + entity.data.toUri().scheme != null + } catch (e: Exception) { + false + } + + if (isRingtoneUri) { + return ActionData.Sound.Ringtone(entity.data) + } else { + val soundFileDescription = + entity.extras.getData(ActionEntity.EXTRA_SOUND_FILE_DESCRIPTION) + .valueOrNull() ?: return null - ActionData.Sound(soundUid = entity.data, soundDescription = soundFileDescription) + ActionData.Sound.SoundFile( + soundUid = entity.data, + soundDescription = soundFileDescription, + ) + } } ActionId.VOLUME_INCREASE_STREAM, @@ -659,7 +673,11 @@ object ActionDataEntityMapper { is ActionData.PinchScreen -> "${data.x},${data.y},${data.distance},${data.pinchType},${data.fingerCount},${data.duration}" is ActionData.Text -> data.text is ActionData.Url -> data.url - is ActionData.Sound -> data.soundUid + is ActionData.Sound -> when (data) { + is ActionData.Sound.Ringtone -> data.uri + is ActionData.Sound.SoundFile -> data.soundUid + } + is ActionData.InteractUiElement -> data.description is ActionData.ControlMediaForApp.Rewind -> SYSTEM_ACTION_ID_MAP[data.id]!! is ActionData.ControlMediaForApp.Stop -> SYSTEM_ACTION_ID_MAP[data.id]!! @@ -822,7 +840,7 @@ object ActionDataEntityMapper { is ActionData.Text -> emptyList() is ActionData.Url -> emptyList() - is ActionData.Sound -> listOf( + is ActionData.Sound.SoundFile -> listOf( EntityExtra(ActionEntity.EXTRA_SOUND_FILE_DESCRIPTION, data.soundDescription), ) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionErrorSnapshot.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionErrorSnapshot.kt index 9ba101fa40..11c4129b8c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionErrorSnapshot.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionErrorSnapshot.kt @@ -10,6 +10,7 @@ import io.github.sds100.keymapper.system.inputmethod.KeyMapperImeHelper import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter +import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.onFailure import io.github.sds100.keymapper.util.onSuccess @@ -22,6 +23,7 @@ class LazyActionErrorSnapshot( cameraAdapter: CameraAdapter, private val soundsManager: SoundsManager, shizukuAdapter: ShizukuAdapter, + private val ringtoneAdapter: RingtoneAdapter, ) : ActionErrorSnapshot, IsActionSupportedUseCase by IsActionSupportedUseCaseImpl( systemFeatureAdapter, @@ -99,12 +101,18 @@ class LazyActionErrorSnapshot( return Error.PermissionDenied(Permission.ROOT) } - is ActionData.Sound -> { + is ActionData.Sound.SoundFile -> { soundsManager.getSound(action.soundUid).onFailure { error -> return error } } + is ActionData.Sound.Ringtone -> { + if (!ringtoneAdapter.exists(action.uri)) { + return Error.CantFindSoundFile + } + } + is ActionData.VoiceAssistant -> { if (!isVoiceAssistantInstalled) { return Error.NoVoiceAssistant diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt index 08c7b3c5c9..77515ce344 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt @@ -434,7 +434,17 @@ class ActionUiHelper( is ActionData.Text -> getString(R.string.description_text_block, action.text) is ActionData.Url -> getString(R.string.description_url, action.url) - is ActionData.Sound -> getString(R.string.description_sound, action.soundDescription) + is ActionData.Sound.SoundFile -> getString( + R.string.description_sound, + action.soundDescription, + ) + + is ActionData.Sound.Ringtone -> { + getRingtoneLabel(action.uri).handle( + onSuccess = { getString(R.string.description_sound, it) }, + onError = { getString(R.string.description_sound_unknown) }, + ) + } ActionData.AirplaneMode.Disable -> getString(R.string.action_disable_airplane_mode) ActionData.AirplaneMode.Enable -> getString(R.string.action_enable_airplane_mode) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt b/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt index 0e1a52efe1..e034b3e9f8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/CreateActionDelegate.kt @@ -688,14 +688,9 @@ class CreateActionDelegate( } ActionId.SOUND -> { - val result = navigate( + return navigate( "choose_sound_file", NavDestination.ChooseSound, - ) ?: return null - - return ActionData.Sound( - soundUid = result.soundUid, - soundDescription = result.description, ) } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/DisplayActionUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/DisplayActionUseCase.kt index 65b59b2bde..4166825086 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/DisplayActionUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/DisplayActionUseCase.kt @@ -10,6 +10,7 @@ interface DisplayActionUseCase : GetActionErrorUseCase { fun getAppName(packageName: String): Result fun getAppIcon(packageName: String): Result fun getInputMethodLabel(imeId: String): Result + fun getRingtoneLabel(uri: String): Result suspend fun fixError(error: Error) fun neverShowDndTriggerError() fun startAccessibilityService(): Boolean diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/GetActionErrorUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/GetActionErrorUseCase.kt index e8e1b6c9d9..ce8a18b9e1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/GetActionErrorUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/GetActionErrorUseCase.kt @@ -7,6 +7,7 @@ import io.github.sds100.keymapper.system.camera.CameraAdapter import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.system.permissions.SystemFeatureAdapter +import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest @@ -22,6 +23,7 @@ class GetActionErrorUseCaseImpl( private val cameraAdapter: CameraAdapter, private val soundsManager: SoundsManager, private val shizukuAdapter: ShizukuAdapter, + private val ringtoneAdapter: RingtoneAdapter, ) : GetActionErrorUseCase { private val invalidateActionErrors = merge( @@ -51,6 +53,7 @@ class GetActionErrorUseCaseImpl( cameraAdapter, soundsManager, shizukuAdapter, + ringtoneAdapter, ) } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt index a3661b0721..11d66a7ae0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/PerformActionsUseCase.kt @@ -40,6 +40,7 @@ import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.system.phone.PhoneAdapter import io.github.sds100.keymapper.system.popup.PopupMessageAdapter +import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter import io.github.sds100.keymapper.system.root.SuAdapter import io.github.sds100.keymapper.system.shell.ShellAdapter import io.github.sds100.keymapper.system.url.OpenUrlAdapter @@ -108,6 +109,7 @@ class PerformActionsUseCaseImpl( private val soundsManager: SoundsManager, private val permissionAdapter: PermissionAdapter, private val notificationReceiverAdapter: ServiceAdapter, + private val ringtoneAdapter: RingtoneAdapter, ) : PerformActionsUseCase { private val openMenuHelper by lazy { @@ -359,12 +361,19 @@ class PerformActionsUseCaseImpl( result = openUrlAdapter.openUrl(action.url) } - is ActionData.Sound -> { + is ActionData.Sound.SoundFile -> { + ringtoneAdapter.stopPlaying() result = soundsManager.getSound(action.soundUid).then { file -> mediaAdapter.playFile(file.uri, VolumeStream.ACCESSIBILITY) } } + is ActionData.Sound.Ringtone -> { + result = mediaAdapter.stopFileMedia().then { + ringtoneAdapter.play(action.uri) + } + } + is ActionData.Wifi.Toggle -> { result = if (networkAdapter.isWifiEnabled()) { networkAdapter.disableWifi() diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileFragment.kt b/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileFragment.kt index 4eb05e9058..9584e52bd0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileFragment.kt @@ -1,11 +1,16 @@ package io.github.sds100.keymapper.actions.sound +import android.app.Activity +import android.content.Intent +import android.media.RingtoneManager +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.addCallback import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.IntentCompat import androidx.core.os.bundleOf import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat @@ -23,7 +28,8 @@ import io.github.sds100.keymapper.util.Inject import io.github.sds100.keymapper.util.launchRepeatOnLifecycle import io.github.sds100.keymapper.util.ui.showPopups import kotlinx.coroutines.flow.collectLatest -import kotlinx.serialization.encodeToString +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.update import kotlinx.serialization.json.Json /** @@ -49,6 +55,19 @@ class ChooseSoundFileFragment : Fragment() { viewModel.onChooseNewSoundFile(it.toString()) } + private val chooseRingtoneLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.data != null && result.resultCode == Activity.RESULT_OK) { + val uri = IntentCompat.getParcelableExtra( + result.data!!, + RingtoneManager.EXTRA_RINGTONE_PICKED_URI, + Uri::class.java, + ) ?: return@registerForActivityResult + + viewModel.onChooseRingtone(uri.toString()) + } + } + /** * Scoped to the lifecycle of the fragment's view (between onCreateView and onDestroyView) */ @@ -87,6 +106,13 @@ class ChooseSoundFileFragment : Fragment() { } } + viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.chooseSystemRingtone.collectLatest { + val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER) + chooseRingtoneLauncher.launch(intent) + } + } + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { findNavController().navigateUp() } @@ -96,12 +122,13 @@ class ChooseSoundFileFragment : Fragment() { } viewLifecycleOwner.launchRepeatOnLifecycle(Lifecycle.State.RESUMED) { - viewModel.returnResult.collectLatest { result -> + viewModel.returnResult.filterNotNull().collect { result -> setFragmentResult( requestKey, bundleOf(EXTRA_RESULT to Json.encodeToString(result)), ) findNavController().navigateUp() + viewModel.returnResult.update { null } } } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileResult.kt b/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileResult.kt deleted file mode 100644 index a019055164..0000000000 --- a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileResult.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.sds100.keymapper.actions.sound - -import kotlinx.serialization.Serializable - -/** - * Created by sds100 on 22/06/2021. - */ -@Serializable -data class ChooseSoundFileResult(val soundUid: String, val description: String) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileViewModel.kt index 79f478a536..4967227dd6 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/sound/ChooseSoundFileViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import io.github.sds100.keymapper.R +import io.github.sds100.keymapper.actions.ActionData import io.github.sds100.keymapper.util.getFullMessage import io.github.sds100.keymapper.util.onFailure import io.github.sds100.keymapper.util.onSuccess @@ -15,11 +16,13 @@ import io.github.sds100.keymapper.util.ui.ResourceProvider import io.github.sds100.keymapper.util.ui.showPopup import io.github.sds100.keymapper.util.valueOrNull import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch /** @@ -35,6 +38,9 @@ class ChooseSoundFileViewModel( private val _chooseSoundFile = MutableSharedFlow() val chooseSoundFile = _chooseSoundFile.asSharedFlow() + private val _chooseSystemRingtone = MutableSharedFlow() + val chooseSystemRingtone = _chooseSystemRingtone.asSharedFlow() + val soundFileListItems: StateFlow> = useCase.soundFiles.map { sounds -> sounds.map { @@ -42,8 +48,8 @@ class ChooseSoundFileViewModel( } }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) - private val _returnResult = MutableSharedFlow() - val returnResult = _returnResult.asSharedFlow() + val returnResult: MutableStateFlow = + MutableStateFlow(null) fun onChooseSoundFileButtonClick() { viewModelScope.launch { @@ -51,6 +57,12 @@ class ChooseSoundFileViewModel( } } + fun onChooseSystemRingtoneButtonClick() { + viewModelScope.launch { + _chooseSystemRingtone.emit(Unit) + } + } + fun onFileListItemClick(id: String) { viewModelScope.launch { val soundFileInfo = useCase.soundFiles.value.find { it.uid == id } ?: return@launch @@ -63,12 +75,12 @@ class ChooseSoundFileViewModel( val soundDescription = showPopup("file_description", dialog) ?: return@launch - _returnResult.emit( - ChooseSoundFileResult( + returnResult.update { + ActionData.Sound.SoundFile( soundUid = soundFileInfo.uid, - description = soundDescription, - ), - ) + soundDescription = soundDescription, + ) + } } } @@ -88,7 +100,12 @@ class ChooseSoundFileViewModel( useCase.saveSound(uri) .onSuccess { soundFileUid -> - _returnResult.emit(ChooseSoundFileResult(soundFileUid, soundDescription)) + returnResult.update { + ActionData.Sound.SoundFile( + soundFileUid, + soundDescription, + ) + } }.onFailure { error -> val toast = PopupUi.Toast(error.getFullMessage(this@ChooseSoundFileViewModel)) showPopup("failed_toast", toast) @@ -96,13 +113,18 @@ class ChooseSoundFileViewModel( } } + fun onChooseRingtone(uri: String) { + viewModelScope.launch { + returnResult.update { ActionData.Sound.Ringtone(uri) } + } + } + @Suppress("UNCHECKED_CAST") class Factory( private val resourceProvider: ResourceProvider, private val useCase: ChooseSoundFileUseCase, ) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = - ChooseSoundFileViewModel(resourceProvider, useCase) as T + override fun create(modelClass: Class): T = ChooseSoundFileViewModel(resourceProvider, useCase) as T } } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt index 82c4dc88fe..3bb0f20a98 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt @@ -3,6 +3,7 @@ package io.github.sds100.keymapper.mappings import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.repositories.PreferenceRepository import io.github.sds100.keymapper.system.media.MediaAdapter +import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import timber.log.Timber @@ -14,6 +15,7 @@ import timber.log.Timber class PauseKeyMapsUseCaseImpl( private val preferenceRepository: PreferenceRepository, private val mediaAdapter: MediaAdapter, + private val ringtoneAdapter: RingtoneAdapter, ) : PauseKeyMapsUseCase { override val isPaused: Flow = @@ -22,6 +24,7 @@ class PauseKeyMapsUseCaseImpl( override fun pause() { preferenceRepository.set(Keys.mappingsPaused, true) mediaAdapter.stopFileMedia() + ringtoneAdapter.stopPlaying() Timber.d("Pause mappings") } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt index 8e87d9d122..8eee6ce3ff 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt @@ -1,7 +1,6 @@ package io.github.sds100.keymapper.mappings.keymaps import android.graphics.drawable.Drawable -import android.view.KeyEvent import io.github.sds100.keymapper.actions.DisplayActionUseCase import io.github.sds100.keymapper.actions.GetActionErrorUseCase import io.github.sds100.keymapper.constraints.DisplayConstraintUseCase @@ -19,6 +18,7 @@ import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter import io.github.sds100.keymapper.system.inputmethod.KeyMapperImeHelper import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter +import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.State @@ -49,18 +49,12 @@ class DisplayKeyMapUseCaseImpl( private val accessibilityServiceAdapter: ServiceAdapter, private val preferences: PreferenceRepository, private val purchasingManager: PurchasingManager, + private val ringtoneAdapter: RingtoneAdapter, getActionError: GetActionErrorUseCase, getConstraintError: GetConstraintErrorUseCase, ) : DisplayKeyMapUseCase, GetActionErrorUseCase by getActionError, GetConstraintErrorUseCase by getConstraintError { - private companion object { - val keysThatRequireDndAccess = arrayOf( - KeyEvent.KEYCODE_VOLUME_DOWN, - KeyEvent.KEYCODE_VOLUME_UP, - ) - } - private val keyMapperImeHelper = KeyMapperImeHelper(inputMethodAdapter) private val showDpadImeSetupError: Flow = @@ -199,6 +193,10 @@ class DisplayKeyMapUseCaseImpl( override fun neverShowDndTriggerError() { preferenceRepository.set(Keys.neverShowDndAccessError, true) } + + override fun getRingtoneLabel(uri: String): Result { + return ringtoneAdapter.getLabel(uri) + } } interface DisplayKeyMapUseCase : diff --git a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapActionsComparator.kt b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapActionsComparator.kt index 1feb8fc728..94158219b8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapActionsComparator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapActionsComparator.kt @@ -68,7 +68,8 @@ class KeyMapActionsComparator( is ActionData.App -> displayActions.getAppName(action.packageName) is ActionData.AppShortcut -> Success(action.shortcutTitle) is ActionData.InputKeyEvent -> Success(action.keyCode.toString()) - is ActionData.Sound -> Success(action.soundDescription) + is ActionData.Sound.SoundFile -> Success(action.soundDescription) + is ActionData.Sound.Ringtone -> Success(action.uri) is ActionData.Volume.Stream -> Success(action.volumeStream.toString()) is ActionData.Volume.SetRingerMode -> Success(action.ringerMode.toString()) is ActionData.Flashlight -> Success(action.lens.toString()) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/ringtones/RingtoneAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/ringtones/RingtoneAdapter.kt new file mode 100644 index 0000000000..ae036aaa7e --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/system/ringtones/RingtoneAdapter.kt @@ -0,0 +1,79 @@ +package io.github.sds100.keymapper.system.ringtones + +import android.content.Context +import android.media.Ringtone +import android.media.RingtoneManager +import android.os.Build +import androidx.core.net.toUri +import io.github.sds100.keymapper.util.Error +import io.github.sds100.keymapper.util.Result +import io.github.sds100.keymapper.util.Success + +class AndroidRingtoneAdapter(context: Context) : RingtoneAdapter { + private val ctx: Context = context.applicationContext + private val ringtoneManager: RingtoneManager by lazy { + RingtoneManager(ctx).apply { + setType(RingtoneManager.TYPE_ALL) + stopPreviousRingtone = true + } + } + + private val lock = Any() + private var playingRingtone: Ringtone? = null + + override fun getLabel(uri: String): Result { + val ringtone = getRingtone(uri) + + if (ringtone == null) { + return Error.CantFindSoundFile + } + + return Success(ringtone.getTitle(ctx)) + } + + override fun exists(uri: String): Boolean { + return getRingtone(uri) != null + } + + override fun play(uri: String): Result { + val ringtone = getRingtone(uri) + + if (ringtone == null) { + return Error.CantFindSoundFile + } else { + ringtoneManager.stopPreviousRingtone() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ringtone.isLooping = false + } + + synchronized(lock) { + playingRingtone?.stop() + playingRingtone = ringtone + ringtone.play() + } + + return Success(Unit) + } + } + + override fun stopPlaying() { + ringtoneManager.stopPreviousRingtone() + + synchronized(lock) { + playingRingtone?.stop() + playingRingtone = null + } + } + + private fun getRingtone(uri: String): Ringtone? { + return RingtoneManager.getRingtone(ctx, uri.toUri()) + } +} + +interface RingtoneAdapter { + fun getLabel(uri: String): Result + fun exists(uri: String): Boolean + fun play(uri: String): Result + fun stopPlaying() +} diff --git a/app/src/main/java/io/github/sds100/keymapper/util/FilterUtils.kt b/app/src/main/java/io/github/sds100/keymapper/util/FilterUtils.kt index 4f723f7a07..c944f94985 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/FilterUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/FilterUtils.kt @@ -11,7 +11,7 @@ import java.util.Locale * Created by sds100 on 22/03/2021. */ -suspend fun List.filterByQuery(query: String?): Flow>> = flow { +fun List.filterByQuery(query: String?): Flow>> = flow { if (query.isNullOrBlank()) { emit(State.Data(this@filterByQuery)) } else { diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavDestination.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavDestination.kt index de4cb1bea8..eababf264b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavDestination.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavDestination.kt @@ -2,7 +2,6 @@ package io.github.sds100.keymapper.util.ui import io.github.sds100.keymapper.actions.ActionData import io.github.sds100.keymapper.actions.pinchscreen.PinchPickCoordinateResult -import io.github.sds100.keymapper.actions.sound.ChooseSoundFileResult import io.github.sds100.keymapper.actions.swipescreen.SwipePickCoordinateResult import io.github.sds100.keymapper.actions.tapscreen.PickCoordinateResult import io.github.sds100.keymapper.constraints.Constraint @@ -80,7 +79,7 @@ sealed class NavDestination { override val id: String = ID_CHOOSE_ACTIVITY } - data object ChooseSound : NavDestination() { + data object ChooseSound : NavDestination() { override val id: String = ID_CHOOSE_SOUND } diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt index fc9c239439..510eb75792 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt @@ -15,7 +15,6 @@ import io.github.sds100.keymapper.actions.keyevent.ConfigKeyEventActionFragment import io.github.sds100.keymapper.actions.pinchscreen.PinchPickCoordinateResult import io.github.sds100.keymapper.actions.pinchscreen.PinchPickDisplayCoordinateFragment import io.github.sds100.keymapper.actions.sound.ChooseSoundFileFragment -import io.github.sds100.keymapper.actions.sound.ChooseSoundFileResult import io.github.sds100.keymapper.actions.swipescreen.SwipePickCoordinateResult import io.github.sds100.keymapper.actions.swipescreen.SwipePickDisplayCoordinateFragment import io.github.sds100.keymapper.actions.tapscreen.PickCoordinateResult @@ -306,7 +305,7 @@ fun NavigationViewModel.sendNavResultFromBundle( NavDestination.ID_CHOOSE_SOUND -> { val json = bundle.getString(ChooseSoundFileFragment.EXTRA_RESULT)!! - val result = Json.decodeFromString(json) + val result = Json.decodeFromString(json) onNavResult(NavResult(requestKey, result)) } diff --git a/app/src/main/res/layout/fragment_choose_sound_file.xml b/app/src/main/res/layout/fragment_choose_sound_file.xml index e71e4b9784..3da224818a 100644 --- a/app/src/main/res/layout/fragment_choose_sound_file.xml +++ b/app/src/main/res/layout/fragment_choose_sound_file.xml @@ -37,25 +37,41 @@ app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintTop_toBottomOf="@id/buttonChooseSystemRingtone" /> Key Mapper log What\'s New - The sound file will be copied to Key Mapper\'s private data folder, which means your actions will still work even if the file is moved or deleted. It will also be backed up with your key maps in the zip folder. + You can either use a system ringtone or select a custom sound file.\n\nThe custom sound file will be copied to Key Mapper\'s private data folder, which means your actions will still work even if the file is moved or deleted. It will also be backed up with your key maps in the zip folder. You can delete saved sound files in the settings. Can\'t find any paired devices. Is Bluetooth turned on? @@ -108,6 +108,7 @@ %s with %d finger(s) on coordinates %d/%d to with a pinch distance of %dpx %dms (%s) Call %s Play sound: %s + Play unknown sound @@ -410,6 +411,7 @@ Set flags No limit Choose sound file + Choose system ringtone Edit action Replace action From b186091dfbe77e4fb9b966bea40743278757c0a0 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 16:44:07 +0200 Subject: [PATCH 53/95] fix tests --- .../actions/GetActionFailedUseCaseTest.kt | 93 +++++++++---------- .../actions/PerformActionsUseCaseTest.kt | 1 + 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt index 91aad88f8d..608de9dcfa 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt @@ -53,6 +53,7 @@ class GetActionFailedUseCaseTest { cameraAdapter = mock(), soundsManager = mock(), shizukuAdapter = mockShizukuAdapter, + ringtoneAdapter = mock(), ) } @@ -60,58 +61,56 @@ class GetActionFailedUseCaseTest { * #776 */ @Test - fun `don't show Shizuku errors if a compatible ime is selected`() = - testScope.runTest { - // GIVEN - whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } - whenever(mockInputMethodAdapter.chosenIme).then { - MutableStateFlow( - ImeInfo( - id = "ime_id", - packageName = "io.github.sds100.keymapper.inputmethod.latin", - label = "Key Mapper GUI Keyboard", - isEnabled = true, - isChosen = true, - ), - ) - } - - val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) - - // WHEN - val error = useCase.actionErrorSnapshot.first().getError(action) - - // THEN - assertThat(error, nullValue()) + fun `don't show Shizuku errors if a compatible ime is selected`() = testScope.runTest { + // GIVEN + whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } + whenever(mockInputMethodAdapter.chosenIme).then { + MutableStateFlow( + ImeInfo( + id = "ime_id", + packageName = "io.github.sds100.keymapper.inputmethod.latin", + label = "Key Mapper GUI Keyboard", + isEnabled = true, + isChosen = true, + ), + ) } + val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) + + // WHEN + val error = useCase.actionErrorSnapshot.first().getError(action) + + // THEN + assertThat(error, nullValue()) + } + /** * #776 */ @Test - fun `show Shizuku errors if a compatible ime is not selected and Shizuku is installed`() = - testScope.runTest { - // GIVEN - whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } - whenever(mockShizukuAdapter.isStarted).then { MutableStateFlow(false) } - - whenever(mockInputMethodAdapter.chosenIme).then { - MutableStateFlow( - ImeInfo( - id = "ime_id", - packageName = "io.gboard", - label = "Gboard", - isEnabled = true, - isChosen = true, - ), - ) - } - - val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) - // WHEN - val error = useCase.actionErrorSnapshot.first().getError(action) - - // THEN - assertThat(error, `is`(Error.ShizukuNotStarted)) + fun `show Shizuku errors if a compatible ime is not selected and Shizuku is installed`() = testScope.runTest { + // GIVEN + whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } + whenever(mockShizukuAdapter.isStarted).then { MutableStateFlow(false) } + + whenever(mockInputMethodAdapter.chosenIme).then { + MutableStateFlow( + ImeInfo( + id = "ime_id", + packageName = "io.gboard", + label = "Gboard", + isEnabled = true, + isChosen = true, + ), + ) } + + val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) + // WHEN + val error = useCase.actionErrorSnapshot.first().getError(action) + + // THEN + assertThat(error, `is`(Error.ShizukuNotStarted)) + } } diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt index 69b71487f1..a08354e869 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt @@ -86,6 +86,7 @@ class PerformActionsUseCaseTest { shizukuInputEventInjector = mock(), permissionAdapter = mock(), notificationReceiverAdapter = mock(), + ringtoneAdapter = mock() ) } From a29cc6cd2cc3b0e82cc9d535088cbbcaf2b405ba Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 16:45:33 +0200 Subject: [PATCH 54/95] #1394 WIP: compile libevdev --- app/build.gradle | 1 + nativelib/.gitignore | 2 + nativelib/build.gradle.kts | 54 + nativelib/consumer-rules.pro | 0 nativelib/proguard-rules.pro | 21 + .../nativelib/ExampleInstrumentedTest.kt | 24 + nativelib/src/main/AndroidManifest.xml | 4 + nativelib/src/main/cpp/CMakeLists.txt | 43 + .../include/linux/freebsd/input-event-codes.h | 980 +++++++ .../main/cpp/include/linux/freebsd/input.h | 513 ++++ .../main/cpp/include/linux/freebsd/uinput.h | 232 ++ nativelib/src/main/cpp/include/linux/input.h | 5 + .../include/linux/linux/input-event-codes.h | 787 ++++++ .../src/main/cpp/include/linux/linux/input.h | 207 ++ .../src/main/cpp/include/linux/linux/uinput.h | 231 ++ nativelib/src/main/cpp/include/linux/uinput.h | 5 + nativelib/src/main/cpp/libevdev/.gitignore | 1 + nativelib/src/main/cpp/libevdev/Makefile.am | 42 + .../src/main/cpp/libevdev/libevdev-int.h | 332 +++ .../src/main/cpp/libevdev/libevdev-names.c | 212 ++ .../main/cpp/libevdev/libevdev-uinput-int.h | 13 + .../src/main/cpp/libevdev/libevdev-uinput.c | 490 ++++ .../src/main/cpp/libevdev/libevdev-uinput.h | 254 ++ .../src/main/cpp/libevdev/libevdev-util.h | 68 + nativelib/src/main/cpp/libevdev/libevdev.c | 1882 +++++++++++++ nativelib/src/main/cpp/libevdev/libevdev.h | 2379 +++++++++++++++++ nativelib/src/main/cpp/libevdev/libevdev.sym | 123 + .../src/main/cpp/libevdev/make-event-names.py | 231 ++ nativelib/src/main/cpp/nativelib.cpp | 45 + .../sds100/keymapper/nativelib/NativeLib.kt | 17 + .../keymapper/nativelib/ExampleUnitTest.kt | 17 + settings.gradle | 1 + 32 files changed, 9216 insertions(+) create mode 100644 nativelib/.gitignore create mode 100644 nativelib/build.gradle.kts create mode 100644 nativelib/consumer-rules.pro create mode 100644 nativelib/proguard-rules.pro create mode 100644 nativelib/src/androidTest/java/io/github/sds100/keymapper/nativelib/ExampleInstrumentedTest.kt create mode 100644 nativelib/src/main/AndroidManifest.xml create mode 100644 nativelib/src/main/cpp/CMakeLists.txt create mode 100644 nativelib/src/main/cpp/include/linux/freebsd/input-event-codes.h create mode 100644 nativelib/src/main/cpp/include/linux/freebsd/input.h create mode 100644 nativelib/src/main/cpp/include/linux/freebsd/uinput.h create mode 100644 nativelib/src/main/cpp/include/linux/input.h create mode 100644 nativelib/src/main/cpp/include/linux/linux/input-event-codes.h create mode 100644 nativelib/src/main/cpp/include/linux/linux/input.h create mode 100644 nativelib/src/main/cpp/include/linux/linux/uinput.h create mode 100644 nativelib/src/main/cpp/include/linux/uinput.h create mode 100644 nativelib/src/main/cpp/libevdev/.gitignore create mode 100644 nativelib/src/main/cpp/libevdev/Makefile.am create mode 100644 nativelib/src/main/cpp/libevdev/libevdev-int.h create mode 100644 nativelib/src/main/cpp/libevdev/libevdev-names.c create mode 100644 nativelib/src/main/cpp/libevdev/libevdev-uinput-int.h create mode 100644 nativelib/src/main/cpp/libevdev/libevdev-uinput.c create mode 100644 nativelib/src/main/cpp/libevdev/libevdev-uinput.h create mode 100644 nativelib/src/main/cpp/libevdev/libevdev-util.h create mode 100644 nativelib/src/main/cpp/libevdev/libevdev.c create mode 100644 nativelib/src/main/cpp/libevdev/libevdev.h create mode 100644 nativelib/src/main/cpp/libevdev/libevdev.sym create mode 100755 nativelib/src/main/cpp/libevdev/make-event-names.py create mode 100644 nativelib/src/main/cpp/nativelib.cpp create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/NativeLib.kt create mode 100644 nativelib/src/test/java/io/github/sds100/keymapper/nativelib/ExampleUnitTest.kt diff --git a/app/build.gradle b/app/build.gradle index 13f120a407..12ef959eb9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -162,6 +162,7 @@ android { dependencies { implementation fileTree(include: ["*.jar"], dir: "libs") + implementation project(':nativelib') compileOnly project(":systemstubs") diff --git a/nativelib/.gitignore b/nativelib/.gitignore new file mode 100644 index 0000000000..c591fdeb45 --- /dev/null +++ b/nativelib/.gitignore @@ -0,0 +1,2 @@ +/build +.cxx \ No newline at end of file diff --git a/nativelib/build.gradle.kts b/nativelib/build.gradle.kts new file mode 100644 index 0000000000..8200e8ba83 --- /dev/null +++ b/nativelib/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "io.github.sds100.keymapper.nativelib" + compileSdk = 35 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + externalNativeBuild { + cmake { + cppFlags("") + } + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + externalNativeBuild { + cmake { + path("src/main/cpp/CMakeLists.txt") + version = "3.22.1" + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.16.0") + implementation("androidx.appcompat:appcompat:1.7.0") + implementation("com.google.android.material:material:1.12.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.2.1") + androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") +} \ No newline at end of file diff --git a/nativelib/consumer-rules.pro b/nativelib/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/nativelib/proguard-rules.pro b/nativelib/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/nativelib/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/nativelib/src/androidTest/java/io/github/sds100/keymapper/nativelib/ExampleInstrumentedTest.kt b/nativelib/src/androidTest/java/io/github/sds100/keymapper/nativelib/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..9a8dddd089 --- /dev/null +++ b/nativelib/src/androidTest/java/io/github/sds100/keymapper/nativelib/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package io.github.sds100.keymapper.nativelib + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("io.github.sds100.keymapper.nativelib.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/nativelib/src/main/AndroidManifest.xml b/nativelib/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a5918e68ab --- /dev/null +++ b/nativelib/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/nativelib/src/main/cpp/CMakeLists.txt b/nativelib/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000..417847fe0c --- /dev/null +++ b/nativelib/src/main/cpp/CMakeLists.txt @@ -0,0 +1,43 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("nativelib") + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. +# +# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +# is preferred for the same purpose. +# +# In order to load a library into your app from Java/Kotlin, you must call +# System.loadLibrary() and pass the name of the library defined here; +# for GameActivity/NativeActivity derived applications, the same library name must be +# used in the AndroidManifest.xml file. +add_library(${CMAKE_PROJECT_NAME} SHARED + # List C/C++ source files with relative paths to this CMakeLists.txt. + nativelib.cpp + libevdev/libevdev.c + libevdev/libevdev-names.c + libevdev/libevdev-uinput.c +) + +include_directories(src/main/cpp/include/) + +# Specifies libraries CMake should link to your target library. You +# can link libraries from various origins, such as libraries defined in this +# build script, prebuilt third-party libraries, or Android system libraries. +target_link_libraries(${CMAKE_PROJECT_NAME} + # List libraries link to the target library + android + log) \ No newline at end of file diff --git a/nativelib/src/main/cpp/include/linux/freebsd/input-event-codes.h b/nativelib/src/main/cpp/include/linux/freebsd/input-event-codes.h new file mode 100644 index 0000000000..a4206723f5 --- /dev/null +++ b/nativelib/src/main/cpp/include/linux/freebsd/input-event-codes.h @@ -0,0 +1,980 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Input event codes + * + * *** IMPORTANT *** + * This file is not only included from C-code but also from devicetree source + * files. As such this file MUST only contain comments and defines. + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 2015 Hans de Goede + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _UAPI_INPUT_EVENT_CODES_H +#define _UAPI_INPUT_EVENT_CODES_H + +/* + * Device properties and quirks + */ + +#define INPUT_PROP_POINTER 0x00 /* needs a pointer */ +#define INPUT_PROP_DIRECT 0x01 /* direct input devices */ +#define INPUT_PROP_BUTTONPAD 0x02 /* has button(s) under pad */ +#define INPUT_PROP_SEMI_MT 0x03 /* touch rectangle only */ +#define INPUT_PROP_TOPBUTTONPAD 0x04 /* softbuttons at top of pad */ +#define INPUT_PROP_POINTING_STICK 0x05 /* is a pointing stick */ +#define INPUT_PROP_ACCELEROMETER 0x06 /* has accelerometer */ + +#define INPUT_PROP_MAX 0x1f +#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) + +/* + * Event types + */ + +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 +#define EV_SW 0x05 +#define EV_LED 0x11 +#define EV_SND 0x12 +#define EV_REP 0x14 +#define EV_FF 0x15 +#define EV_PWR 0x16 +#define EV_FF_STATUS 0x17 +#define EV_MAX 0x1f +#define EV_CNT (EV_MAX+1) + +/* + * Synchronization events. + */ + +#define SYN_REPORT 0 +#define SYN_CONFIG 1 +#define SYN_MT_REPORT 2 +#define SYN_DROPPED 3 +#define SYN_MAX 0xf +#define SYN_CNT (SYN_MAX+1) + +/* + * Keys and buttons + * + * Most of the keys/buttons are modeled after USB HUT 1.12 + * (see http://www.usb.org/developers/hidpage). + * Abbreviations in the comments: + * AC - Application Control + * AL - Application Launch Button + * SC - System Control + */ + +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 + +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 /* SC System Power Down */ +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 +#define KEY_SCALE 120 /* AL Compiz Scale (Expose) */ + +#define KEY_KPCOMMA 121 +#define KEY_HANGEUL 122 +#define KEY_HANGUEL KEY_HANGEUL +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 + +#define KEY_STOP 128 /* AC Stop */ +#define KEY_AGAIN 129 +#define KEY_PROPS 130 /* AC Properties */ +#define KEY_UNDO 131 /* AC Undo */ +#define KEY_FRONT 132 +#define KEY_COPY 133 /* AC Copy */ +#define KEY_OPEN 134 /* AC Open */ +#define KEY_PASTE 135 /* AC Paste */ +#define KEY_FIND 136 /* AC Search */ +#define KEY_CUT 137 /* AC Cut */ +#define KEY_HELP 138 /* AL Integrated Help Center */ +#define KEY_MENU 139 /* Menu (show menu) */ +#define KEY_CALC 140 /* AL Calculator */ +#define KEY_SETUP 141 +#define KEY_SLEEP 142 /* SC System Sleep */ +#define KEY_WAKEUP 143 /* System Wake Up */ +#define KEY_FILE 144 /* AL Local Machine Browser */ +#define KEY_SENDFILE 145 +#define KEY_DELETEFILE 146 +#define KEY_XFER 147 +#define KEY_PROG1 148 +#define KEY_PROG2 149 +#define KEY_WWW 150 /* AL Internet Browser */ +#define KEY_MSDOS 151 +#define KEY_COFFEE 152 /* AL Terminal Lock/Screensaver */ +#define KEY_SCREENLOCK KEY_COFFEE +#define KEY_ROTATE_DISPLAY 153 /* Display orientation for e.g. tablets */ +#define KEY_DIRECTION KEY_ROTATE_DISPLAY +#define KEY_CYCLEWINDOWS 154 +#define KEY_MAIL 155 +#define KEY_BOOKMARKS 156 /* AC Bookmarks */ +#define KEY_COMPUTER 157 +#define KEY_BACK 158 /* AC Back */ +#define KEY_FORWARD 159 /* AC Forward */ +#define KEY_CLOSECD 160 +#define KEY_EJECTCD 161 +#define KEY_EJECTCLOSECD 162 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_RECORD 167 +#define KEY_REWIND 168 +#define KEY_PHONE 169 /* Media Select Telephone */ +#define KEY_ISO 170 +#define KEY_CONFIG 171 /* AL Consumer Control Configuration */ +#define KEY_HOMEPAGE 172 /* AC Home */ +#define KEY_REFRESH 173 /* AC Refresh */ +#define KEY_EXIT 174 /* AC Exit */ +#define KEY_MOVE 175 +#define KEY_EDIT 176 +#define KEY_SCROLLUP 177 +#define KEY_SCROLLDOWN 178 +#define KEY_KPLEFTPAREN 179 +#define KEY_KPRIGHTPAREN 180 +#define KEY_NEW 181 /* AC New */ +#define KEY_REDO 182 /* AC Redo/Repeat */ + +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 + +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_PROG3 202 +#define KEY_PROG4 203 +#define KEY_ALL_APPLICATIONS 204 /* AC Desktop Show All Applications */ +#define KEY_DASHBOARD KEY_ALL_APPLICATIONS +#define KEY_SUSPEND 205 +#define KEY_CLOSE 206 /* AC Close */ +#define KEY_PLAY 207 +#define KEY_FASTFORWARD 208 +#define KEY_BASSBOOST 209 +#define KEY_PRINT 210 /* AC Print */ +#define KEY_HP 211 +#define KEY_CAMERA 212 +#define KEY_SOUND 213 +#define KEY_QUESTION 214 +#define KEY_EMAIL 215 +#define KEY_CHAT 216 +#define KEY_SEARCH 217 +#define KEY_CONNECT 218 +#define KEY_FINANCE 219 /* AL Checkbook/Finance */ +#define KEY_SPORT 220 +#define KEY_SHOP 221 +#define KEY_ALTERASE 222 +#define KEY_CANCEL 223 /* AC Cancel */ +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MEDIA 226 + +#define KEY_SWITCHVIDEOMODE 227 /* Cycle between available video + outputs (Monitor/LCD/TV-out/etc) */ +#define KEY_KBDILLUMTOGGLE 228 +#define KEY_KBDILLUMDOWN 229 +#define KEY_KBDILLUMUP 230 + +#define KEY_SEND 231 /* AC Send */ +#define KEY_REPLY 232 /* AC Reply */ +#define KEY_FORWARDMAIL 233 /* AC Forward Msg */ +#define KEY_SAVE 234 /* AC Save */ +#define KEY_DOCUMENTS 235 + +#define KEY_BATTERY 236 + +#define KEY_BLUETOOTH 237 +#define KEY_WLAN 238 +#define KEY_UWB 239 + +#define KEY_UNKNOWN 240 + +#define KEY_VIDEO_NEXT 241 /* drive next video source */ +#define KEY_VIDEO_PREV 242 /* drive previous video source */ +#define KEY_BRIGHTNESS_CYCLE 243 /* brightness up, after max is min */ +#define KEY_BRIGHTNESS_AUTO 244 /* Set Auto Brightness: manual + brightness control is off, + rely on ambient */ +#define KEY_BRIGHTNESS_ZERO KEY_BRIGHTNESS_AUTO +#define KEY_DISPLAY_OFF 245 /* display device to off state */ + +#define KEY_WWAN 246 /* Wireless WAN (LTE, UMTS, GSM, etc.) */ +#define KEY_WIMAX KEY_WWAN +#define KEY_RFKILL 247 /* Key that controls all radios */ + +#define KEY_MICMUTE 248 /* Mute / unmute the microphone */ + +/* Code 255 is reserved for special needs of AT keyboard driver */ + +#define BTN_MISC 0x100 +#define BTN_0 0x100 +#define BTN_1 0x101 +#define BTN_2 0x102 +#define BTN_3 0x103 +#define BTN_4 0x104 +#define BTN_5 0x105 +#define BTN_6 0x106 +#define BTN_7 0x107 +#define BTN_8 0x108 +#define BTN_9 0x109 + +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 + +#define BTN_JOYSTICK 0x120 +#define BTN_TRIGGER 0x120 +#define BTN_THUMB 0x121 +#define BTN_THUMB2 0x122 +#define BTN_TOP 0x123 +#define BTN_TOP2 0x124 +#define BTN_PINKIE 0x125 +#define BTN_BASE 0x126 +#define BTN_BASE2 0x127 +#define BTN_BASE3 0x128 +#define BTN_BASE4 0x129 +#define BTN_BASE5 0x12a +#define BTN_BASE6 0x12b +#define BTN_DEAD 0x12f + +#define BTN_GAMEPAD 0x130 +#define BTN_SOUTH 0x130 +#define BTN_A BTN_SOUTH +#define BTN_EAST 0x131 +#define BTN_B BTN_EAST +#define BTN_C 0x132 +#define BTN_NORTH 0x133 +#define BTN_X BTN_NORTH +#define BTN_WEST 0x134 +#define BTN_Y BTN_WEST +#define BTN_Z 0x135 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c +#define BTN_THUMBL 0x13d +#define BTN_THUMBR 0x13e + +#define BTN_DIGI 0x140 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_RUBBER 0x141 +#define BTN_TOOL_BRUSH 0x142 +#define BTN_TOOL_PENCIL 0x143 +#define BTN_TOOL_AIRBRUSH 0x144 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOOL_MOUSE 0x146 +#define BTN_TOOL_LENS 0x147 +#define BTN_TOOL_QUINTTAP 0x148 /* Five fingers on trackpad */ +#define BTN_STYLUS3 0x149 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_STYLUS2 0x14c +#define BTN_TOOL_DOUBLETAP 0x14d +#define BTN_TOOL_TRIPLETAP 0x14e +#define BTN_TOOL_QUADTAP 0x14f /* Four fingers on trackpad */ + +#define BTN_WHEEL 0x150 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 + +#define KEY_OK 0x160 +#define KEY_SELECT 0x161 +#define KEY_GOTO 0x162 +#define KEY_CLEAR 0x163 +#define KEY_POWER2 0x164 +#define KEY_OPTION 0x165 +#define KEY_INFO 0x166 /* AL OEM Features/Tips/Tutorial */ +#define KEY_TIME 0x167 +#define KEY_VENDOR 0x168 +#define KEY_ARCHIVE 0x169 +#define KEY_PROGRAM 0x16a /* Media Select Program Guide */ +#define KEY_CHANNEL 0x16b +#define KEY_FAVORITES 0x16c +#define KEY_EPG 0x16d +#define KEY_PVR 0x16e /* Media Select Home */ +#define KEY_MHP 0x16f +#define KEY_LANGUAGE 0x170 +#define KEY_TITLE 0x171 +#define KEY_SUBTITLE 0x172 +#define KEY_ANGLE 0x173 +#define KEY_FULL_SCREEN 0x174 /* AC View Toggle */ +#define KEY_ZOOM KEY_FULL_SCREEN +#define KEY_MODE 0x175 +#define KEY_KEYBOARD 0x176 +#define KEY_ASPECT_RATIO 0x177 /* HUTRR37: Aspect */ +#define KEY_SCREEN KEY_ASPECT_RATIO +#define KEY_PC 0x178 /* Media Select Computer */ +#define KEY_TV 0x179 /* Media Select TV */ +#define KEY_TV2 0x17a /* Media Select Cable */ +#define KEY_VCR 0x17b /* Media Select VCR */ +#define KEY_VCR2 0x17c /* VCR Plus */ +#define KEY_SAT 0x17d /* Media Select Satellite */ +#define KEY_SAT2 0x17e +#define KEY_CD 0x17f /* Media Select CD */ +#define KEY_TAPE 0x180 /* Media Select Tape */ +#define KEY_RADIO 0x181 +#define KEY_TUNER 0x182 /* Media Select Tuner */ +#define KEY_PLAYER 0x183 +#define KEY_TEXT 0x184 +#define KEY_DVD 0x185 /* Media Select DVD */ +#define KEY_AUX 0x186 +#define KEY_MP3 0x187 +#define KEY_AUDIO 0x188 /* AL Audio Browser */ +#define KEY_VIDEO 0x189 /* AL Movie Browser */ +#define KEY_DIRECTORY 0x18a +#define KEY_LIST 0x18b +#define KEY_MEMO 0x18c /* Media Select Messages */ +#define KEY_CALENDAR 0x18d +#define KEY_RED 0x18e +#define KEY_GREEN 0x18f +#define KEY_YELLOW 0x190 +#define KEY_BLUE 0x191 +#define KEY_CHANNELUP 0x192 /* Channel Increment */ +#define KEY_CHANNELDOWN 0x193 /* Channel Decrement */ +#define KEY_FIRST 0x194 +#define KEY_LAST 0x195 /* Recall Last */ +#define KEY_AB 0x196 +#define KEY_NEXT 0x197 +#define KEY_RESTART 0x198 +#define KEY_SLOW 0x199 +#define KEY_SHUFFLE 0x19a +#define KEY_BREAK 0x19b +#define KEY_PREVIOUS 0x19c +#define KEY_DIGITS 0x19d +#define KEY_TEEN 0x19e +#define KEY_TWEN 0x19f +#define KEY_VIDEOPHONE 0x1a0 /* Media Select Video Phone */ +#define KEY_GAMES 0x1a1 /* Media Select Games */ +#define KEY_ZOOMIN 0x1a2 /* AC Zoom In */ +#define KEY_ZOOMOUT 0x1a3 /* AC Zoom Out */ +#define KEY_ZOOMRESET 0x1a4 /* AC Zoom */ +#define KEY_WORDPROCESSOR 0x1a5 /* AL Word Processor */ +#define KEY_EDITOR 0x1a6 /* AL Text Editor */ +#define KEY_SPREADSHEET 0x1a7 /* AL Spreadsheet */ +#define KEY_GRAPHICSEDITOR 0x1a8 /* AL Graphics Editor */ +#define KEY_PRESENTATION 0x1a9 /* AL Presentation App */ +#define KEY_DATABASE 0x1aa /* AL Database App */ +#define KEY_NEWS 0x1ab /* AL Newsreader */ +#define KEY_VOICEMAIL 0x1ac /* AL Voicemail */ +#define KEY_ADDRESSBOOK 0x1ad /* AL Contacts/Address Book */ +#define KEY_MESSENGER 0x1ae /* AL Instant Messaging */ +#define KEY_DISPLAYTOGGLE 0x1af /* Turn display (LCD) on and off */ +#define KEY_BRIGHTNESS_TOGGLE KEY_DISPLAYTOGGLE +#define KEY_SPELLCHECK 0x1b0 /* AL Spell Check */ +#define KEY_LOGOFF 0x1b1 /* AL Logoff */ + +#define KEY_DOLLAR 0x1b2 +#define KEY_EURO 0x1b3 + +#define KEY_FRAMEBACK 0x1b4 /* Consumer - transport controls */ +#define KEY_FRAMEFORWARD 0x1b5 +#define KEY_CONTEXT_MENU 0x1b6 /* GenDesc - system context menu */ +#define KEY_MEDIA_REPEAT 0x1b7 /* Consumer - transport control */ +#define KEY_10CHANNELSUP 0x1b8 /* 10 channels up (10+) */ +#define KEY_10CHANNELSDOWN 0x1b9 /* 10 channels down (10-) */ +#define KEY_IMAGES 0x1ba /* AL Image Browser */ +#define KEY_NOTIFICATION_CENTER 0x1bc /* Show/hide the notification center */ +#define KEY_PICKUP_PHONE 0x1bd /* Answer incoming call */ +#define KEY_HANGUP_PHONE 0x1be /* Decline incoming call */ + +#define KEY_DEL_EOL 0x1c0 +#define KEY_DEL_EOS 0x1c1 +#define KEY_INS_LINE 0x1c2 +#define KEY_DEL_LINE 0x1c3 + +#define KEY_FN 0x1d0 +#define KEY_FN_ESC 0x1d1 +#define KEY_FN_F1 0x1d2 +#define KEY_FN_F2 0x1d3 +#define KEY_FN_F3 0x1d4 +#define KEY_FN_F4 0x1d5 +#define KEY_FN_F5 0x1d6 +#define KEY_FN_F6 0x1d7 +#define KEY_FN_F7 0x1d8 +#define KEY_FN_F8 0x1d9 +#define KEY_FN_F9 0x1da +#define KEY_FN_F10 0x1db +#define KEY_FN_F11 0x1dc +#define KEY_FN_F12 0x1dd +#define KEY_FN_1 0x1de +#define KEY_FN_2 0x1df +#define KEY_FN_D 0x1e0 +#define KEY_FN_E 0x1e1 +#define KEY_FN_F 0x1e2 +#define KEY_FN_S 0x1e3 +#define KEY_FN_B 0x1e4 +#define KEY_FN_RIGHT_SHIFT 0x1e5 + +#define KEY_BRL_DOT1 0x1f1 +#define KEY_BRL_DOT2 0x1f2 +#define KEY_BRL_DOT3 0x1f3 +#define KEY_BRL_DOT4 0x1f4 +#define KEY_BRL_DOT5 0x1f5 +#define KEY_BRL_DOT6 0x1f6 +#define KEY_BRL_DOT7 0x1f7 +#define KEY_BRL_DOT8 0x1f8 +#define KEY_BRL_DOT9 0x1f9 +#define KEY_BRL_DOT10 0x1fa + +#define KEY_NUMERIC_0 0x200 /* used by phones, remote controls, */ +#define KEY_NUMERIC_1 0x201 /* and other keypads */ +#define KEY_NUMERIC_2 0x202 +#define KEY_NUMERIC_3 0x203 +#define KEY_NUMERIC_4 0x204 +#define KEY_NUMERIC_5 0x205 +#define KEY_NUMERIC_6 0x206 +#define KEY_NUMERIC_7 0x207 +#define KEY_NUMERIC_8 0x208 +#define KEY_NUMERIC_9 0x209 +#define KEY_NUMERIC_STAR 0x20a +#define KEY_NUMERIC_POUND 0x20b +#define KEY_NUMERIC_A 0x20c /* Phone key A - HUT Telephony 0xb9 */ +#define KEY_NUMERIC_B 0x20d +#define KEY_NUMERIC_C 0x20e +#define KEY_NUMERIC_D 0x20f + +#define KEY_CAMERA_FOCUS 0x210 +#define KEY_WPS_BUTTON 0x211 /* WiFi Protected Setup key */ + +#define KEY_TOUCHPAD_TOGGLE 0x212 /* Request switch touchpad on or off */ +#define KEY_TOUCHPAD_ON 0x213 +#define KEY_TOUCHPAD_OFF 0x214 + +#define KEY_CAMERA_ZOOMIN 0x215 +#define KEY_CAMERA_ZOOMOUT 0x216 +#define KEY_CAMERA_UP 0x217 +#define KEY_CAMERA_DOWN 0x218 +#define KEY_CAMERA_LEFT 0x219 +#define KEY_CAMERA_RIGHT 0x21a + +#define KEY_ATTENDANT_ON 0x21b +#define KEY_ATTENDANT_OFF 0x21c +#define KEY_ATTENDANT_TOGGLE 0x21d /* Attendant call on or off */ +#define KEY_LIGHTS_TOGGLE 0x21e /* Reading light on or off */ + +#define BTN_DPAD_UP 0x220 +#define BTN_DPAD_DOWN 0x221 +#define BTN_DPAD_LEFT 0x222 +#define BTN_DPAD_RIGHT 0x223 + +#define KEY_ALS_TOGGLE 0x230 /* Ambient light sensor */ +#define KEY_ROTATE_LOCK_TOGGLE 0x231 /* Display rotation lock */ +#define KEY_REFRESH_RATE_TOGGLE 0x232 /* Display refresh rate toggle */ + +#define KEY_BUTTONCONFIG 0x240 /* AL Button Configuration */ +#define KEY_TASKMANAGER 0x241 /* AL Task/Project Manager */ +#define KEY_JOURNAL 0x242 /* AL Log/Journal/Timecard */ +#define KEY_CONTROLPANEL 0x243 /* AL Control Panel */ +#define KEY_APPSELECT 0x244 /* AL Select Task/Application */ +#define KEY_SCREENSAVER 0x245 /* AL Screen Saver */ +#define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */ +#define KEY_ASSISTANT 0x247 /* AL Context-aware desktop assistant */ +#define KEY_KBD_LAYOUT_NEXT 0x248 /* AC Next Keyboard Layout Select */ +#define KEY_EMOJI_PICKER 0x249 /* Show/hide emoji picker (HUTRR101) */ +#define KEY_DICTATE 0x24a /* Start or Stop Voice Dictation Session (HUTRR99) */ +#define KEY_CAMERA_ACCESS_ENABLE 0x24b /* Enables programmatic access to camera devices. (HUTRR72) */ +#define KEY_CAMERA_ACCESS_DISABLE 0x24c /* Disables programmatic access to camera devices. (HUTRR72) */ +#define KEY_CAMERA_ACCESS_TOGGLE 0x24d /* Toggles the current state of the camera access control. (HUTRR72) */ +#define KEY_ACCESSIBILITY 0x24e /* Toggles the system bound accessibility UI/command (HUTRR116) */ +#define KEY_DO_NOT_DISTURB 0x24f /* Toggles the system-wide "Do Not Disturb" control (HUTRR94)*/ + +#define KEY_BRIGHTNESS_MIN 0x250 /* Set Brightness to Minimum */ +#define KEY_BRIGHTNESS_MAX 0x251 /* Set Brightness to Maximum */ + +#define KEY_KBDINPUTASSIST_PREV 0x260 +#define KEY_KBDINPUTASSIST_NEXT 0x261 +#define KEY_KBDINPUTASSIST_PREVGROUP 0x262 +#define KEY_KBDINPUTASSIST_NEXTGROUP 0x263 +#define KEY_KBDINPUTASSIST_ACCEPT 0x264 +#define KEY_KBDINPUTASSIST_CANCEL 0x265 + +/* Diagonal movement keys */ +#define KEY_RIGHT_UP 0x266 +#define KEY_RIGHT_DOWN 0x267 +#define KEY_LEFT_UP 0x268 +#define KEY_LEFT_DOWN 0x269 + +#define KEY_ROOT_MENU 0x26a /* Show Device's Root Menu */ +/* Show Top Menu of the Media (e.g. DVD) */ +#define KEY_MEDIA_TOP_MENU 0x26b +#define KEY_NUMERIC_11 0x26c +#define KEY_NUMERIC_12 0x26d +/* + * Toggle Audio Description: refers to an audio service that helps blind and + * visually impaired consumers understand the action in a program. Note: in + * some countries this is referred to as "Video Description". + */ +#define KEY_AUDIO_DESC 0x26e +#define KEY_3D_MODE 0x26f +#define KEY_NEXT_FAVORITE 0x270 +#define KEY_STOP_RECORD 0x271 +#define KEY_PAUSE_RECORD 0x272 +#define KEY_VOD 0x273 /* Video on Demand */ +#define KEY_UNMUTE 0x274 +#define KEY_FASTREVERSE 0x275 +#define KEY_SLOWREVERSE 0x276 +/* + * Control a data application associated with the currently viewed channel, + * e.g. teletext or data broadcast application (MHEG, MHP, HbbTV, etc.) + */ +#define KEY_DATA 0x277 +#define KEY_ONSCREEN_KEYBOARD 0x278 +/* Electronic privacy screen control */ +#define KEY_PRIVACY_SCREEN_TOGGLE 0x279 + +/* Select an area of screen to be copied */ +#define KEY_SELECTIVE_SCREENSHOT 0x27a + +/* Move the focus to the next or previous user controllable element within a UI container */ +#define KEY_NEXT_ELEMENT 0x27b +#define KEY_PREVIOUS_ELEMENT 0x27c + +/* Toggle Autopilot engagement */ +#define KEY_AUTOPILOT_ENGAGE_TOGGLE 0x27d + +/* Shortcut Keys */ +#define KEY_MARK_WAYPOINT 0x27e +#define KEY_SOS 0x27f +#define KEY_NAV_CHART 0x280 +#define KEY_FISHING_CHART 0x281 +#define KEY_SINGLE_RANGE_RADAR 0x282 +#define KEY_DUAL_RANGE_RADAR 0x283 +#define KEY_RADAR_OVERLAY 0x284 +#define KEY_TRADITIONAL_SONAR 0x285 +#define KEY_CLEARVU_SONAR 0x286 +#define KEY_SIDEVU_SONAR 0x287 +#define KEY_NAV_INFO 0x288 +#define KEY_BRIGHTNESS_MENU 0x289 + +/* + * Some keyboards have keys which do not have a defined meaning, these keys + * are intended to be programmed / bound to macros by the user. For most + * keyboards with these macro-keys the key-sequence to inject, or action to + * take, is all handled by software on the host side. So from the kernel's + * point of view these are just normal keys. + * + * The KEY_MACRO# codes below are intended for such keys, which may be labeled + * e.g. G1-G18, or S1 - S30. The KEY_MACRO# codes MUST NOT be used for keys + * where the marking on the key does indicate a defined meaning / purpose. + * + * The KEY_MACRO# codes MUST also NOT be used as fallback for when no existing + * KEY_FOO define matches the marking / purpose. In this case a new KEY_FOO + * define MUST be added. + */ +#define KEY_MACRO1 0x290 +#define KEY_MACRO2 0x291 +#define KEY_MACRO3 0x292 +#define KEY_MACRO4 0x293 +#define KEY_MACRO5 0x294 +#define KEY_MACRO6 0x295 +#define KEY_MACRO7 0x296 +#define KEY_MACRO8 0x297 +#define KEY_MACRO9 0x298 +#define KEY_MACRO10 0x299 +#define KEY_MACRO11 0x29a +#define KEY_MACRO12 0x29b +#define KEY_MACRO13 0x29c +#define KEY_MACRO14 0x29d +#define KEY_MACRO15 0x29e +#define KEY_MACRO16 0x29f +#define KEY_MACRO17 0x2a0 +#define KEY_MACRO18 0x2a1 +#define KEY_MACRO19 0x2a2 +#define KEY_MACRO20 0x2a3 +#define KEY_MACRO21 0x2a4 +#define KEY_MACRO22 0x2a5 +#define KEY_MACRO23 0x2a6 +#define KEY_MACRO24 0x2a7 +#define KEY_MACRO25 0x2a8 +#define KEY_MACRO26 0x2a9 +#define KEY_MACRO27 0x2aa +#define KEY_MACRO28 0x2ab +#define KEY_MACRO29 0x2ac +#define KEY_MACRO30 0x2ad + +/* + * Some keyboards with the macro-keys described above have some extra keys + * for controlling the host-side software responsible for the macro handling: + * -A macro recording start/stop key. Note that not all keyboards which emit + * KEY_MACRO_RECORD_START will also emit KEY_MACRO_RECORD_STOP if + * KEY_MACRO_RECORD_STOP is not advertised, then KEY_MACRO_RECORD_START + * should be interpreted as a recording start/stop toggle; + * -Keys for switching between different macro (pre)sets, either a key for + * cycling through the configured presets or keys to directly select a preset. + */ +#define KEY_MACRO_RECORD_START 0x2b0 +#define KEY_MACRO_RECORD_STOP 0x2b1 +#define KEY_MACRO_PRESET_CYCLE 0x2b2 +#define KEY_MACRO_PRESET1 0x2b3 +#define KEY_MACRO_PRESET2 0x2b4 +#define KEY_MACRO_PRESET3 0x2b5 + +/* + * Some keyboards have a buildin LCD panel where the contents are controlled + * by the host. Often these have a number of keys directly below the LCD + * intended for controlling a menu shown on the LCD. These keys often don't + * have any labeling so we just name them KEY_KBD_LCD_MENU# + */ +#define KEY_KBD_LCD_MENU1 0x2b8 +#define KEY_KBD_LCD_MENU2 0x2b9 +#define KEY_KBD_LCD_MENU3 0x2ba +#define KEY_KBD_LCD_MENU4 0x2bb +#define KEY_KBD_LCD_MENU5 0x2bc + +#define BTN_TRIGGER_HAPPY 0x2c0 +#define BTN_TRIGGER_HAPPY1 0x2c0 +#define BTN_TRIGGER_HAPPY2 0x2c1 +#define BTN_TRIGGER_HAPPY3 0x2c2 +#define BTN_TRIGGER_HAPPY4 0x2c3 +#define BTN_TRIGGER_HAPPY5 0x2c4 +#define BTN_TRIGGER_HAPPY6 0x2c5 +#define BTN_TRIGGER_HAPPY7 0x2c6 +#define BTN_TRIGGER_HAPPY8 0x2c7 +#define BTN_TRIGGER_HAPPY9 0x2c8 +#define BTN_TRIGGER_HAPPY10 0x2c9 +#define BTN_TRIGGER_HAPPY11 0x2ca +#define BTN_TRIGGER_HAPPY12 0x2cb +#define BTN_TRIGGER_HAPPY13 0x2cc +#define BTN_TRIGGER_HAPPY14 0x2cd +#define BTN_TRIGGER_HAPPY15 0x2ce +#define BTN_TRIGGER_HAPPY16 0x2cf +#define BTN_TRIGGER_HAPPY17 0x2d0 +#define BTN_TRIGGER_HAPPY18 0x2d1 +#define BTN_TRIGGER_HAPPY19 0x2d2 +#define BTN_TRIGGER_HAPPY20 0x2d3 +#define BTN_TRIGGER_HAPPY21 0x2d4 +#define BTN_TRIGGER_HAPPY22 0x2d5 +#define BTN_TRIGGER_HAPPY23 0x2d6 +#define BTN_TRIGGER_HAPPY24 0x2d7 +#define BTN_TRIGGER_HAPPY25 0x2d8 +#define BTN_TRIGGER_HAPPY26 0x2d9 +#define BTN_TRIGGER_HAPPY27 0x2da +#define BTN_TRIGGER_HAPPY28 0x2db +#define BTN_TRIGGER_HAPPY29 0x2dc +#define BTN_TRIGGER_HAPPY30 0x2dd +#define BTN_TRIGGER_HAPPY31 0x2de +#define BTN_TRIGGER_HAPPY32 0x2df +#define BTN_TRIGGER_HAPPY33 0x2e0 +#define BTN_TRIGGER_HAPPY34 0x2e1 +#define BTN_TRIGGER_HAPPY35 0x2e2 +#define BTN_TRIGGER_HAPPY36 0x2e3 +#define BTN_TRIGGER_HAPPY37 0x2e4 +#define BTN_TRIGGER_HAPPY38 0x2e5 +#define BTN_TRIGGER_HAPPY39 0x2e6 +#define BTN_TRIGGER_HAPPY40 0x2e7 + +/* We avoid low common keys in module aliases so they don't get huge. */ +#define KEY_MIN_INTERESTING KEY_MUTE +#define KEY_MAX 0x2ff +#define KEY_CNT (KEY_MAX+1) + +/* + * Relative axes + */ + +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 +#define REL_RX 0x03 +#define REL_RY 0x04 +#define REL_RZ 0x05 +#define REL_HWHEEL 0x06 +#define REL_DIAL 0x07 +#define REL_WHEEL 0x08 +#define REL_MISC 0x09 +/* + * 0x0a is reserved and should not be used in input drivers. + * It was used by HID as REL_MISC+1 and userspace needs to detect if + * the next REL_* event is correct or is just REL_MISC + n. + * We define here REL_RESERVED so userspace can rely on it and detect + * the situation described above. + */ +#define REL_RESERVED 0x0a +#define REL_WHEEL_HI_RES 0x0b +#define REL_HWHEEL_HI_RES 0x0c +#define REL_MAX 0x0f +#define REL_CNT (REL_MAX+1) + +/* + * Absolute axes + */ + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_HAT1X 0x12 +#define ABS_HAT1Y 0x13 +#define ABS_HAT2X 0x14 +#define ABS_HAT2Y 0x15 +#define ABS_HAT3X 0x16 +#define ABS_HAT3Y 0x17 +#define ABS_PRESSURE 0x18 +#define ABS_DISTANCE 0x19 +#define ABS_TILT_X 0x1a +#define ABS_TILT_Y 0x1b +#define ABS_TOOL_WIDTH 0x1c + +#define ABS_VOLUME 0x20 +#define ABS_PROFILE 0x21 + +#define ABS_MISC 0x28 + +/* + * 0x2e is reserved and should not be used in input drivers. + * It was used by HID as ABS_MISC+6 and userspace needs to detect if + * the next ABS_* event is correct or is just ABS_MISC + n. + * We define here ABS_RESERVED so userspace can rely on it and detect + * the situation described above. + */ +#define ABS_RESERVED 0x2e + +#define ABS_MT_SLOT 0x2f /* MT slot being modified */ +#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ +#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ +#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ +#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ +#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ +#define ABS_MT_POSITION_X 0x35 /* Center X touch position */ +#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */ +#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */ +#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */ +#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ +#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */ +#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */ +#define ABS_MT_TOOL_X 0x3c /* Center X tool position */ +#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */ + + +#define ABS_MAX 0x3f +#define ABS_CNT (ABS_MAX+1) + +/* + * Switch events + */ + +#define SW_LID 0x00 /* set = lid shut */ +#define SW_TABLET_MODE 0x01 /* set = tablet mode */ +#define SW_HEADPHONE_INSERT 0x02 /* set = inserted */ +#define SW_RFKILL_ALL 0x03 /* rfkill master switch, type "any" + set = radio enabled */ +#define SW_RADIO SW_RFKILL_ALL /* deprecated */ +#define SW_MICROPHONE_INSERT 0x04 /* set = inserted */ +#define SW_DOCK 0x05 /* set = plugged into dock */ +#define SW_LINEOUT_INSERT 0x06 /* set = inserted */ +#define SW_JACK_PHYSICAL_INSERT 0x07 /* set = mechanical switch set */ +#define SW_VIDEOOUT_INSERT 0x08 /* set = inserted */ +#define SW_CAMERA_LENS_COVER 0x09 /* set = lens covered */ +#define SW_KEYPAD_SLIDE 0x0a /* set = keypad slide out */ +#define SW_FRONT_PROXIMITY 0x0b /* set = front proximity sensor active */ +#define SW_ROTATE_LOCK 0x0c /* set = rotate locked/disabled */ +#define SW_LINEIN_INSERT 0x0d /* set = inserted */ +#define SW_MUTE_DEVICE 0x0e /* set = device disabled */ +#define SW_PEN_INSERTED 0x0f /* set = pen inserted */ +#define SW_MACHINE_COVER 0x10 /* set = cover closed */ +#define SW_MAX 0x10 +#define SW_CNT (SW_MAX+1) + +/* + * Misc events + */ + +#define MSC_SERIAL 0x00 +#define MSC_PULSELED 0x01 +#define MSC_GESTURE 0x02 +#define MSC_RAW 0x03 +#define MSC_SCAN 0x04 +#define MSC_TIMESTAMP 0x05 +#define MSC_MAX 0x07 +#define MSC_CNT (MSC_MAX+1) + +/* + * LEDs + */ + +#define LED_NUML 0x00 +#define LED_CAPSL 0x01 +#define LED_SCROLLL 0x02 +#define LED_COMPOSE 0x03 +#define LED_KANA 0x04 +#define LED_SLEEP 0x05 +#define LED_SUSPEND 0x06 +#define LED_MUTE 0x07 +#define LED_MISC 0x08 +#define LED_MAIL 0x09 +#define LED_CHARGING 0x0a +#define LED_MAX 0x0f +#define LED_CNT (LED_MAX+1) + +/* + * Autorepeat values + */ + +#define REP_DELAY 0x00 +#define REP_PERIOD 0x01 +#define REP_MAX 0x01 +#define REP_CNT (REP_MAX+1) + +/* + * Sounds + */ + +#define SND_CLICK 0x00 +#define SND_BELL 0x01 +#define SND_TONE 0x02 +#define SND_MAX 0x07 +#define SND_CNT (SND_MAX+1) + +#endif diff --git a/nativelib/src/main/cpp/include/linux/freebsd/input.h b/nativelib/src/main/cpp/include/linux/freebsd/input.h new file mode 100644 index 0000000000..2f4d401568 --- /dev/null +++ b/nativelib/src/main/cpp/include/linux/freebsd/input.h @@ -0,0 +1,513 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (c) 1999-2002 Vojtech Pavlik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _UAPI_INPUT_H +#define _UAPI_INPUT_H + + +#ifndef __KERNEL__ +#include +#include +#include +#endif + +#include "input-event-codes.h" + +/* + * The event structure itself + * Note that __USE_TIME_BITS64 is defined by libc based on + * application's request to use 64 bit time_t. + */ + +struct input_event { +#if 1 /* (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__) */ + struct timeval time; +#define input_event_sec time.tv_sec +#define input_event_usec time.tv_usec +#else + __kernel_ulong_t __sec; +#if defined(__sparc__) && defined(__arch64__) + unsigned int __usec; + unsigned int __pad; +#else + __kernel_ulong_t __usec; +#endif +#define input_event_sec __sec +#define input_event_usec __usec +#endif + uint16_t type; + uint16_t code; + int32_t value; +}; + +/* + * Protocol version. + */ + +#define EV_VERSION 0x010001 + +/* + * IOCTLs (0x00 - 0x7f) + */ + +struct input_id { + uint16_t bustype; + uint16_t vendor; + uint16_t product; + uint16_t version; +}; + +/** + * struct input_absinfo - used by EVIOCGABS/EVIOCSABS ioctls + * @value: latest reported value for the axis. + * @minimum: specifies minimum value for the axis. + * @maximum: specifies maximum value for the axis. + * @fuzz: specifies fuzz value that is used to filter noise from + * the event stream. + * @flat: values that are within this value will be discarded by + * joydev interface and reported as 0 instead. + * @resolution: specifies resolution for the values reported for + * the axis. + * + * Note that input core does not clamp reported values to the + * [minimum, maximum] limits, such task is left to userspace. + * + * The default resolution for main axes (ABS_X, ABS_Y, ABS_Z) + * is reported in units per millimeter (units/mm), resolution + * for rotational axes (ABS_RX, ABS_RY, ABS_RZ) is reported + * in units per radian. + * When INPUT_PROP_ACCELEROMETER is set the resolution changes. + * The main axes (ABS_X, ABS_Y, ABS_Z) are then reported in + * in units per g (units/g) and in units per degree per second + * (units/deg/s) for rotational axes (ABS_RX, ABS_RY, ABS_RZ). + */ +struct input_absinfo { + int32_t value; + int32_t minimum; + int32_t maximum; + int32_t fuzz; + int32_t flat; + int32_t resolution; +}; + +/** + * struct input_keymap_entry - used by EVIOCGKEYCODE/EVIOCSKEYCODE ioctls + * @scancode: scancode represented in machine-endian form. + * @len: length of the scancode that resides in @scancode buffer. + * @index: index in the keymap, may be used instead of scancode + * @flags: allows to specify how kernel should handle the request. For + * example, setting INPUT_KEYMAP_BY_INDEX flag indicates that kernel + * should perform lookup in keymap by @index instead of @scancode + * @keycode: key code assigned to this scancode + * + * The structure is used to retrieve and modify keymap data. Users have + * option of performing lookup either by @scancode itself or by @index + * in keymap entry. EVIOCGKEYCODE will also return scancode or index + * (depending on which element was used to perform lookup). + */ +struct input_keymap_entry { +#define INPUT_KEYMAP_BY_INDEX (1 << 0) + uint8_t flags; + uint8_t len; + uint16_t index; + uint32_t keycode; + uint8_t scancode[32]; +}; + +struct input_mask { + uint32_t type; + uint32_t codes_size; + uint64_t codes_ptr; +}; + +#define EVIOCGVERSION _IOR('E', 0x01, int) /* get driver version */ +#define EVIOCGID _IOR('E', 0x02, struct input_id) /* get device ID */ +#define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) /* get repeat settings */ +#define EVIOCSREP _IOW('E', 0x03, unsigned int[2]) /* set repeat settings */ + +#define EVIOCGKEYCODE _IOWR('E', 0x04, unsigned int[2]) /* get keycode */ +#define EVIOCGKEYCODE_V2 _IOWR('E', 0x04, struct input_keymap_entry) +#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2]) /* set keycode */ +#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry) + +#define EVIOCGNAME(len) _IOC(IOC_OUT, 'E', 0x06, len) /* get device name */ +#define EVIOCGPHYS(len) _IOC(IOC_OUT, 'E', 0x07, len) /* get physical location */ +#define EVIOCGUNIQ(len) _IOC(IOC_OUT, 'E', 0x08, len) /* get unique identifier */ +#define EVIOCGPROP(len) _IOC(IOC_OUT, 'E', 0x09, len) /* get device properties */ + +/** + * EVIOCGMTSLOTS(len) - get MT slot values + * @len: size of the data buffer in bytes + * + * The ioctl buffer argument should be binary equivalent to + * + * struct input_mt_request_layout { + * uint32_t code; + * int32_t values[num_slots]; + * }; + * + * where num_slots is the (arbitrary) number of MT slots to extract. + * + * The ioctl size argument (len) is the size of the buffer, which + * should satisfy len = (num_slots + 1) * sizeof(int32_t). If len is + * too small to fit all available slots, the first num_slots are + * returned. + * + * Before the call, code is set to the wanted ABS_MT event type. On + * return, values[] is filled with the slot values for the specified + * ABS_MT code. + * + * If the request code is not an ABS_MT value, -EINVAL is returned. + */ +#define EVIOCGMTSLOTS(len) _IOC(IOC_INOUT, 'E', 0x0a, len) + +#define EVIOCGKEY(len) _IOC(IOC_OUT, 'E', 0x18, len) /* get global key state */ +#define EVIOCGLED(len) _IOC(IOC_OUT, 'E', 0x19, len) /* get all LEDs */ +#define EVIOCGSND(len) _IOC(IOC_OUT, 'E', 0x1a, len) /* get all sounds status */ +#define EVIOCGSW(len) _IOC(IOC_OUT, 'E', 0x1b, len) /* get all switch states */ + +#define EVIOCGBIT(ev,len) _IOC(IOC_OUT, 'E', 0x20 + (ev), len) /* get event bits */ +#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) /* get abs value/limits */ +#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo) /* set abs value/limits */ + +#define EVIOCSFF _IOW('E', 0x80, struct ff_effect) /* send a force effect to a force feedback device */ +#define EVIOCRMFF _IOWINT('E', 0x81) /* Erase a force effect */ +#define EVIOCGEFFECTS _IOR('E', 0x84, int) /* Report number of effects playable at the same time */ + +#define EVIOCGRAB _IOWINT('E', 0x90) /* Grab/Release device */ +#define EVIOCREVOKE _IOWINT('E', 0x91) /* Revoke device access */ + +/** + * EVIOCGMASK - Retrieve current event mask + * + * This ioctl allows user to retrieve the current event mask for specific + * event type. The argument must be of type "struct input_mask" and + * specifies the event type to query, the address of the receive buffer and + * the size of the receive buffer. + * + * The event mask is a per-client mask that specifies which events are + * forwarded to the client. Each event code is represented by a single bit + * in the event mask. If the bit is set, the event is passed to the client + * normally. Otherwise, the event is filtered and will never be queued on + * the client's receive buffer. + * + * Event masks do not affect global state of the input device. They only + * affect the file descriptor they are applied to. + * + * The default event mask for a client has all bits set, i.e. all events + * are forwarded to the client. If the kernel is queried for an unknown + * event type or if the receive buffer is larger than the number of + * event codes known to the kernel, the kernel returns all zeroes for those + * codes. + * + * At maximum, codes_size bytes are copied. + * + * This ioctl may fail with ENODEV in case the file is revoked, EFAULT + * if the receive-buffer points to invalid memory, or EINVAL if the kernel + * does not implement the ioctl. + */ +#define EVIOCGMASK _IOW('E', 0x92, struct input_mask) /* Get event-masks */ + +/** + * EVIOCSMASK - Set event mask + * + * This ioctl is the counterpart to EVIOCGMASK. Instead of receiving the + * current event mask, this changes the client's event mask for a specific + * type. See EVIOCGMASK for a description of event-masks and the + * argument-type. + * + * This ioctl provides full forward compatibility. If the passed event type + * is unknown to the kernel, or if the number of event codes specified in + * the mask is bigger than what is known to the kernel, the ioctl is still + * accepted and applied. However, any unknown codes are left untouched and + * stay cleared. That means, the kernel always filters unknown codes + * regardless of what the client requests. If the new mask doesn't cover + * all known event-codes, all remaining codes are automatically cleared and + * thus filtered. + * + * This ioctl may fail with ENODEV in case the file is revoked. EFAULT is + * returned if the receive-buffer points to invalid memory. EINVAL is returned + * if the kernel does not implement the ioctl. + */ +#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) /* Set event-masks */ + +#define EVIOCSCLOCKID _IOW('E', 0xa0, int) /* Set clockid to be used for timestamps */ + +/* + * IDs. + */ + +#define ID_BUS 0 +#define ID_VENDOR 1 +#define ID_PRODUCT 2 +#define ID_VERSION 3 + +#define BUS_PCI 0x01 +#define BUS_ISAPNP 0x02 +#define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 +#define BUS_VIRTUAL 0x06 + +#define BUS_ISA 0x10 +#define BUS_I8042 0x11 +#define BUS_XTKBD 0x12 +#define BUS_RS232 0x13 +#define BUS_GAMEPORT 0x14 +#define BUS_PARPORT 0x15 +#define BUS_AMIGA 0x16 +#define BUS_ADB 0x17 +#define BUS_I2C 0x18 +#define BUS_HOST 0x19 +#define BUS_GSC 0x1A +#define BUS_ATARI 0x1B +#define BUS_SPI 0x1C +#define BUS_RMI 0x1D +#define BUS_CEC 0x1E +#define BUS_INTEL_ISHTP 0x1F + +/* + * MT_TOOL types + */ +#define MT_TOOL_FINGER 0x00 +#define MT_TOOL_PEN 0x01 +#define MT_TOOL_PALM 0x02 +#define MT_TOOL_DIAL 0x0a +#define MT_TOOL_MAX 0x0f + +/* + * Values describing the status of a force-feedback effect + */ +#define FF_STATUS_STOPPED 0x00 +#define FF_STATUS_PLAYING 0x01 +#define FF_STATUS_MAX 0x01 + +/* + * Structures used in ioctls to upload effects to a device + * They are pieces of a bigger structure (called ff_effect) + */ + +/* + * All duration values are expressed in ms. Values above 32767 ms (0x7fff) + * should not be used and have unspecified results. + */ + +/** + * struct ff_replay - defines scheduling of the force-feedback effect + * @length: duration of the effect + * @delay: delay before effect should start playing + */ +struct ff_replay { + uint16_t length; + uint16_t delay; +}; + +/** + * struct ff_trigger - defines what triggers the force-feedback effect + * @button: number of the button triggering the effect + * @interval: controls how soon the effect can be re-triggered + */ +struct ff_trigger { + uint16_t button; + uint16_t interval; +}; + +/** + * struct ff_envelope - generic force-feedback effect envelope + * @attack_length: duration of the attack (ms) + * @attack_level: level at the beginning of the attack + * @fade_length: duration of fade (ms) + * @fade_level: level at the end of fade + * + * The @attack_level and @fade_level are absolute values; when applying + * envelope force-feedback core will convert to positive/negative + * value based on polarity of the default level of the effect. + * Valid range for the attack and fade levels is 0x0000 - 0x7fff + */ +struct ff_envelope { + uint16_t attack_length; + uint16_t attack_level; + uint16_t fade_length; + uint16_t fade_level; +}; + +/** + * struct ff_constant_effect - defines parameters of a constant force-feedback effect + * @level: strength of the effect; may be negative + * @envelope: envelope data + */ +struct ff_constant_effect { + int16_t level; + struct ff_envelope envelope; +}; + +/** + * struct ff_ramp_effect - defines parameters of a ramp force-feedback effect + * @start_level: beginning strength of the effect; may be negative + * @end_level: final strength of the effect; may be negative + * @envelope: envelope data + */ +struct ff_ramp_effect { + int16_t start_level; + int16_t end_level; + struct ff_envelope envelope; +}; + +/** + * struct ff_condition_effect - defines a spring or friction force-feedback effect + * @right_saturation: maximum level when joystick moved all way to the right + * @left_saturation: same for the left side + * @right_coeff: controls how fast the force grows when the joystick moves + * to the right + * @left_coeff: same for the left side + * @deadband: size of the dead zone, where no force is produced + * @center: position of the dead zone + */ +struct ff_condition_effect { + uint16_t right_saturation; + uint16_t left_saturation; + + int16_t right_coeff; + int16_t left_coeff; + + uint16_t deadband; + int16_t center; +}; + +/** + * struct ff_periodic_effect - defines parameters of a periodic force-feedback effect + * @waveform: kind of the effect (wave) + * @period: period of the wave (ms) + * @magnitude: peak value + * @offset: mean value of the wave (roughly) + * @phase: 'horizontal' shift + * @envelope: envelope data + * @custom_len: number of samples (FF_CUSTOM only) + * @custom_data: buffer of samples (FF_CUSTOM only) + * + * Known waveforms - FF_SQUARE, FF_TRIANGLE, FF_SINE, FF_SAW_UP, + * FF_SAW_DOWN, FF_CUSTOM. The exact syntax FF_CUSTOM is undefined + * for the time being as no driver supports it yet. + * + * Note: the data pointed by custom_data is copied by the driver. + * You can therefore dispose of the memory after the upload/update. + */ +struct ff_periodic_effect { + uint16_t waveform; + uint16_t period; + int16_t magnitude; + int16_t offset; + uint16_t phase; + + struct ff_envelope envelope; + + uint32_t custom_len; + int16_t *custom_data; +}; + +/** + * struct ff_rumble_effect - defines parameters of a periodic force-feedback effect + * @strong_magnitude: magnitude of the heavy motor + * @weak_magnitude: magnitude of the light one + * + * Some rumble pads have two motors of different weight. Strong_magnitude + * represents the magnitude of the vibration generated by the heavy one. + */ +struct ff_rumble_effect { + uint16_t strong_magnitude; + uint16_t weak_magnitude; +}; + +/** + * struct ff_effect - defines force feedback effect + * @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING, + * FF_FRICTION, FF_DAMPER, FF_RUMBLE, FF_INERTIA, or FF_CUSTOM) + * @id: an unique id assigned to an effect + * @direction: direction of the effect + * @trigger: trigger conditions (struct ff_trigger) + * @replay: scheduling of the effect (struct ff_replay) + * @u: effect-specific structure (one of ff_constant_effect, ff_ramp_effect, + * ff_periodic_effect, ff_condition_effect, ff_rumble_effect) further + * defining effect parameters + * + * This structure is sent through ioctl from the application to the driver. + * To create a new effect application should set its @id to -1; the kernel + * will return assigned @id which can later be used to update or delete + * this effect. + * + * Direction of the effect is encoded as follows: + * 0 deg -> 0x0000 (down) + * 90 deg -> 0x4000 (left) + * 180 deg -> 0x8000 (up) + * 270 deg -> 0xC000 (right) + */ +struct ff_effect { + uint16_t type; + int16_t id; + uint16_t direction; + struct ff_trigger trigger; + struct ff_replay replay; + + union { + struct ff_constant_effect constant; + struct ff_ramp_effect ramp; + struct ff_periodic_effect periodic; + struct ff_condition_effect condition[2]; /* One for each axis */ + struct ff_rumble_effect rumble; + } u; +}; + +/* + * Force feedback effect types + */ + +#define FF_RUMBLE 0x50 +#define FF_PERIODIC 0x51 +#define FF_CONSTANT 0x52 +#define FF_SPRING 0x53 +#define FF_FRICTION 0x54 +#define FF_DAMPER 0x55 +#define FF_INERTIA 0x56 +#define FF_RAMP 0x57 + +#define FF_EFFECT_MIN FF_RUMBLE +#define FF_EFFECT_MAX FF_RAMP + +/* + * Force feedback periodic effect types + */ + +#define FF_SQUARE 0x58 +#define FF_TRIANGLE 0x59 +#define FF_SINE 0x5a +#define FF_SAW_UP 0x5b +#define FF_SAW_DOWN 0x5c +#define FF_CUSTOM 0x5d + +#define FF_WAVEFORM_MIN FF_SQUARE +#define FF_WAVEFORM_MAX FF_CUSTOM + +/* + * Set ff device properties + */ + +#define FF_GAIN 0x60 +#define FF_AUTOCENTER 0x61 + +/* + * ff->playback(effect_id = FF_GAIN) is the first effect_id to + * cause a collision with another ff method, in this case ff->set_gain(). + * Therefore the greatest safe value for effect_id is FF_GAIN - 1, + * and thus the total number of effects should never exceed FF_GAIN. + */ +#define FF_MAX_EFFECTS FF_GAIN + +#define FF_MAX 0x7f +#define FF_CNT (FF_MAX+1) + +#endif /* _UAPI_INPUT_H */ diff --git a/nativelib/src/main/cpp/include/linux/freebsd/uinput.h b/nativelib/src/main/cpp/include/linux/freebsd/uinput.h new file mode 100644 index 0000000000..fc6253c7fc --- /dev/null +++ b/nativelib/src/main/cpp/include/linux/freebsd/uinput.h @@ -0,0 +1,232 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * User level driver support for input subsystem + * + * Heavily based on evdev.c by Vojtech Pavlik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Aristeu Sergio Rozanski Filho + * + * Changes/Revisions: + * 0.5 08/13/2015 (David Herrmann & + * Benjamin Tissoires ) + * - add UI_DEV_SETUP ioctl + * - add UI_ABS_SETUP ioctl + * - add UI_GET_VERSION ioctl + * 0.4 01/09/2014 (Benjamin Tissoires ) + * - add UI_GET_SYSNAME ioctl + * 0.3 24/05/2006 (Anssi Hannula ) + * - update ff support for the changes in kernel interface + * - add UINPUT_VERSION + * 0.2 16/10/2004 (Micah Dowty ) + * - added force feedback support + * - added UI_SET_PHYS + * 0.1 20/06/2002 + * - first public version + */ +#ifndef _UAPI__UINPUT_H_ +#define _UAPI__UINPUT_H_ + +#include +#include + +#define UINPUT_VERSION 5 +#define UINPUT_MAX_NAME_SIZE 80 + +struct uinput_ff_upload { + uint32_t request_id; + int32_t retval; + struct ff_effect effect; + struct ff_effect old; +}; + +struct uinput_ff_erase { + uint32_t request_id; + int32_t retval; + uint32_t effect_id; +}; + +/* ioctl */ +#define UINPUT_IOCTL_BASE 'U' +#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1) +#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2) + +struct uinput_setup { + struct input_id id; + char name[UINPUT_MAX_NAME_SIZE]; + uint32_t ff_effects_max; +}; + +/** + * UI_DEV_SETUP - Set device parameters for setup + * + * This ioctl sets parameters for the input device to be created. It + * supersedes the old "struct uinput_user_dev" method, which wrote this data + * via write(). To actually set the absolute axes UI_ABS_SETUP should be + * used. + * + * The ioctl takes a "struct uinput_setup" object as argument. The fields of + * this object are as follows: + * id: See the description of "struct input_id". This field is + * copied unchanged into the new device. + * name: This is used unchanged as name for the new device. + * ff_effects_max: This limits the maximum numbers of force-feedback effects. + * See below for a description of FF with uinput. + * + * This ioctl can be called multiple times and will overwrite previous values. + * If this ioctl fails with -EINVAL, it is recommended to use the old + * "uinput_user_dev" method via write() as a fallback, in case you run on an + * old kernel that does not support this ioctl. + * + * This ioctl may fail with -EINVAL if it is not supported or if you passed + * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the + * passed uinput_setup object cannot be read/written. + * If this call fails, partial data may have already been applied to the + * internal device. + */ +#define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup) + +struct uinput_abs_setup { + uint16_t code; /* axis code */ + /* uint16_t filler; */ + struct input_absinfo absinfo; +}; + +/** + * UI_ABS_SETUP - Set absolute axis information for the device to setup + * + * This ioctl sets one absolute axis information for the input device to be + * created. It supersedes the old "struct uinput_user_dev" method, which wrote + * part of this data and the content of UI_DEV_SETUP via write(). + * + * The ioctl takes a "struct uinput_abs_setup" object as argument. The fields + * of this object are as follows: + * code: The corresponding input code associated with this axis + * (ABS_X, ABS_Y, etc...) + * absinfo: See "struct input_absinfo" for a description of this field. + * This field is copied unchanged into the kernel for the + * specified axis. If the axis is not enabled via + * UI_SET_ABSBIT, this ioctl will enable it. + * + * This ioctl can be called multiple times and will overwrite previous values. + * If this ioctl fails with -EINVAL, it is recommended to use the old + * "uinput_user_dev" method via write() as a fallback, in case you run on an + * old kernel that does not support this ioctl. + * + * This ioctl may fail with -EINVAL if it is not supported or if you passed + * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the + * passed uinput_setup object cannot be read/written. + * If this call fails, partial data may have already been applied to the + * internal device. + */ +#define UI_ABS_SETUP _IOW(UINPUT_IOCTL_BASE, 4, struct uinput_abs_setup) + +#define UI_SET_EVBIT _IOWINT(UINPUT_IOCTL_BASE, 100) +#define UI_SET_KEYBIT _IOWINT(UINPUT_IOCTL_BASE, 101) +#define UI_SET_RELBIT _IOWINT(UINPUT_IOCTL_BASE, 102) +#define UI_SET_ABSBIT _IOWINT(UINPUT_IOCTL_BASE, 103) +#define UI_SET_MSCBIT _IOWINT(UINPUT_IOCTL_BASE, 104) +#define UI_SET_LEDBIT _IOWINT(UINPUT_IOCTL_BASE, 105) +#define UI_SET_SNDBIT _IOWINT(UINPUT_IOCTL_BASE, 106) +#define UI_SET_FFBIT _IOWINT(UINPUT_IOCTL_BASE, 107) +#define UI_SET_PHYS _IO(UINPUT_IOCTL_BASE, 108) +#define UI_SET_SWBIT _IOWINT(UINPUT_IOCTL_BASE, 109) +#define UI_SET_PROPBIT _IOWINT(UINPUT_IOCTL_BASE, 110) + +#define UI_BEGIN_FF_UPLOAD _IOWR(UINPUT_IOCTL_BASE, 200, struct uinput_ff_upload) +#define UI_END_FF_UPLOAD _IOW(UINPUT_IOCTL_BASE, 201, struct uinput_ff_upload) +#define UI_BEGIN_FF_ERASE _IOWR(UINPUT_IOCTL_BASE, 202, struct uinput_ff_erase) +#define UI_END_FF_ERASE _IOW(UINPUT_IOCTL_BASE, 203, struct uinput_ff_erase) + +/** + * UI_GET_SYSNAME - get the sysfs name of the created uinput device + * + * @return the sysfs name of the created virtual input device. + * The complete sysfs path is then /sys/devices/virtual/input/--NAME-- + * Usually, it is in the form "inputN" + */ +#define UI_GET_SYSNAME(len) _IOC(IOC_OUT, UINPUT_IOCTL_BASE, 44, len) + +/** + * UI_GET_VERSION - Return version of uinput protocol + * + * This writes uinput protocol version implemented by the kernel into + * the integer pointed to by the ioctl argument. The protocol version + * is hard-coded in the kernel and is independent of the uinput device. + */ +#define UI_GET_VERSION _IOR(UINPUT_IOCTL_BASE, 45, unsigned int) + +/* + * To write a force-feedback-capable driver, the upload_effect + * and erase_effect callbacks in input_dev must be implemented. + * The uinput driver will generate a fake input event when one of + * these callbacks are invoked. The userspace code then uses + * ioctls to retrieve additional parameters and send the return code. + * The callback blocks until this return code is sent. + * + * The described callback mechanism is only used if ff_effects_max + * is set. + * + * To implement upload_effect(): + * 1. Wait for an event with type == EV_UINPUT and code == UI_FF_UPLOAD. + * A request ID will be given in 'value'. + * 2. Allocate a uinput_ff_upload struct, fill in request_id with + * the 'value' from the EV_UINPUT event. + * 3. Issue a UI_BEGIN_FF_UPLOAD ioctl, giving it the + * uinput_ff_upload struct. It will be filled in with the + * ff_effects passed to upload_effect(). + * 4. Perform the effect upload, and place a return code back into + the uinput_ff_upload struct. + * 5. Issue a UI_END_FF_UPLOAD ioctl, also giving it the + * uinput_ff_upload_effect struct. This will complete execution + * of our upload_effect() handler. + * + * To implement erase_effect(): + * 1. Wait for an event with type == EV_UINPUT and code == UI_FF_ERASE. + * A request ID will be given in 'value'. + * 2. Allocate a uinput_ff_erase struct, fill in request_id with + * the 'value' from the EV_UINPUT event. + * 3. Issue a UI_BEGIN_FF_ERASE ioctl, giving it the + * uinput_ff_erase struct. It will be filled in with the + * effect ID passed to erase_effect(). + * 4. Perform the effect erasure, and place a return code back + * into the uinput_ff_erase struct. + * 5. Issue a UI_END_FF_ERASE ioctl, also giving it the + * uinput_ff_erase_effect struct. This will complete execution + * of our erase_effect() handler. + */ + +/* + * This is the new event type, used only by uinput. + * 'code' is UI_FF_UPLOAD or UI_FF_ERASE, and 'value' + * is the unique request ID. This number was picked + * arbitrarily, above EV_MAX (since the input system + * never sees it) but in the range of a 16-bit int. + */ +#define EV_UINPUT 0x0101 +#define UI_FF_UPLOAD 1 +#define UI_FF_ERASE 2 + +struct uinput_user_dev { + char name[UINPUT_MAX_NAME_SIZE]; + struct input_id id; + uint32_t ff_effects_max; + int32_t absmax[ABS_CNT]; + int32_t absmin[ABS_CNT]; + int32_t absfuzz[ABS_CNT]; + int32_t absflat[ABS_CNT]; +}; +#endif /* _UAPI__UINPUT_H_ */ diff --git a/nativelib/src/main/cpp/include/linux/input.h b/nativelib/src/main/cpp/include/linux/input.h new file mode 100644 index 0000000000..03c512efb0 --- /dev/null +++ b/nativelib/src/main/cpp/include/linux/input.h @@ -0,0 +1,5 @@ +#ifdef __linux__ +#include "linux/input.h" +#elif __FreeBSD__ +#include "freebsd/input.h" +#endif diff --git a/nativelib/src/main/cpp/include/linux/linux/input-event-codes.h b/nativelib/src/main/cpp/include/linux/linux/input-event-codes.h new file mode 100644 index 0000000000..4f93d5ecea --- /dev/null +++ b/nativelib/src/main/cpp/include/linux/linux/input-event-codes.h @@ -0,0 +1,787 @@ +/* + * This file is auto-generated. Modifications will be lost. + * + * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/ + * for more information. + */ +#ifndef _UAPI_INPUT_EVENT_CODES_H +#define _UAPI_INPUT_EVENT_CODES_H +#define INPUT_PROP_POINTER 0x00 +#define INPUT_PROP_DIRECT 0x01 +#define INPUT_PROP_BUTTONPAD 0x02 +#define INPUT_PROP_SEMI_MT 0x03 +#define INPUT_PROP_TOPBUTTONPAD 0x04 +#define INPUT_PROP_POINTING_STICK 0x05 +#define INPUT_PROP_ACCELEROMETER 0x06 +#define INPUT_PROP_MAX 0x1f +#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 +#define EV_SW 0x05 +#define EV_LED 0x11 +#define EV_SND 0x12 +#define EV_REP 0x14 +#define EV_FF 0x15 +#define EV_PWR 0x16 +#define EV_FF_STATUS 0x17 +#define EV_MAX 0x1f +#define EV_CNT (EV_MAX + 1) +#define SYN_REPORT 0 +#define SYN_CONFIG 1 +#define SYN_MT_REPORT 2 +#define SYN_DROPPED 3 +#define SYN_MAX 0xf +#define SYN_CNT (SYN_MAX + 1) +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 +#define KEY_SCALE 120 +#define KEY_KPCOMMA 121 +#define KEY_HANGEUL 122 +#define KEY_HANGUEL KEY_HANGEUL +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 +#define KEY_STOP 128 +#define KEY_AGAIN 129 +#define KEY_PROPS 130 +#define KEY_UNDO 131 +#define KEY_FRONT 132 +#define KEY_COPY 133 +#define KEY_OPEN 134 +#define KEY_PASTE 135 +#define KEY_FIND 136 +#define KEY_CUT 137 +#define KEY_HELP 138 +#define KEY_MENU 139 +#define KEY_CALC 140 +#define KEY_SETUP 141 +#define KEY_SLEEP 142 +#define KEY_WAKEUP 143 +#define KEY_FILE 144 +#define KEY_SENDFILE 145 +#define KEY_DELETEFILE 146 +#define KEY_XFER 147 +#define KEY_PROG1 148 +#define KEY_PROG2 149 +#define KEY_WWW 150 +#define KEY_MSDOS 151 +#define KEY_COFFEE 152 +#define KEY_SCREENLOCK KEY_COFFEE +#define KEY_ROTATE_DISPLAY 153 +#define KEY_DIRECTION KEY_ROTATE_DISPLAY +#define KEY_CYCLEWINDOWS 154 +#define KEY_MAIL 155 +#define KEY_BOOKMARKS 156 +#define KEY_COMPUTER 157 +#define KEY_BACK 158 +#define KEY_FORWARD 159 +#define KEY_CLOSECD 160 +#define KEY_EJECTCD 161 +#define KEY_EJECTCLOSECD 162 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_RECORD 167 +#define KEY_REWIND 168 +#define KEY_PHONE 169 +#define KEY_ISO 170 +#define KEY_CONFIG 171 +#define KEY_HOMEPAGE 172 +#define KEY_REFRESH 173 +#define KEY_EXIT 174 +#define KEY_MOVE 175 +#define KEY_EDIT 176 +#define KEY_SCROLLUP 177 +#define KEY_SCROLLDOWN 178 +#define KEY_KPLEFTPAREN 179 +#define KEY_KPRIGHTPAREN 180 +#define KEY_NEW 181 +#define KEY_REDO 182 +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_PROG3 202 +#define KEY_PROG4 203 +#define KEY_ALL_APPLICATIONS 204 +#define KEY_DASHBOARD KEY_ALL_APPLICATIONS +#define KEY_SUSPEND 205 +#define KEY_CLOSE 206 +#define KEY_PLAY 207 +#define KEY_FASTFORWARD 208 +#define KEY_BASSBOOST 209 +#define KEY_PRINT 210 +#define KEY_HP 211 +#define KEY_CAMERA 212 +#define KEY_SOUND 213 +#define KEY_QUESTION 214 +#define KEY_EMAIL 215 +#define KEY_CHAT 216 +#define KEY_SEARCH 217 +#define KEY_CONNECT 218 +#define KEY_FINANCE 219 +#define KEY_SPORT 220 +#define KEY_SHOP 221 +#define KEY_ALTERASE 222 +#define KEY_CANCEL 223 +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MEDIA 226 +#define KEY_SWITCHVIDEOMODE 227 +#define KEY_KBDILLUMTOGGLE 228 +#define KEY_KBDILLUMDOWN 229 +#define KEY_KBDILLUMUP 230 +#define KEY_SEND 231 +#define KEY_REPLY 232 +#define KEY_FORWARDMAIL 233 +#define KEY_SAVE 234 +#define KEY_DOCUMENTS 235 +#define KEY_BATTERY 236 +#define KEY_BLUETOOTH 237 +#define KEY_WLAN 238 +#define KEY_UWB 239 +#define KEY_UNKNOWN 240 +#define KEY_VIDEO_NEXT 241 +#define KEY_VIDEO_PREV 242 +#define KEY_BRIGHTNESS_CYCLE 243 +#define KEY_BRIGHTNESS_AUTO 244 +#define KEY_BRIGHTNESS_ZERO KEY_BRIGHTNESS_AUTO +#define KEY_DISPLAY_OFF 245 +#define KEY_WWAN 246 +#define KEY_WIMAX KEY_WWAN +#define KEY_RFKILL 247 +#define KEY_MICMUTE 248 +#define BTN_MISC 0x100 +#define BTN_0 0x100 +#define BTN_1 0x101 +#define BTN_2 0x102 +#define BTN_3 0x103 +#define BTN_4 0x104 +#define BTN_5 0x105 +#define BTN_6 0x106 +#define BTN_7 0x107 +#define BTN_8 0x108 +#define BTN_9 0x109 +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 +#define BTN_JOYSTICK 0x120 +#define BTN_TRIGGER 0x120 +#define BTN_THUMB 0x121 +#define BTN_THUMB2 0x122 +#define BTN_TOP 0x123 +#define BTN_TOP2 0x124 +#define BTN_PINKIE 0x125 +#define BTN_BASE 0x126 +#define BTN_BASE2 0x127 +#define BTN_BASE3 0x128 +#define BTN_BASE4 0x129 +#define BTN_BASE5 0x12a +#define BTN_BASE6 0x12b +#define BTN_DEAD 0x12f +#define BTN_GAMEPAD 0x130 +#define BTN_SOUTH 0x130 +#define BTN_A BTN_SOUTH +#define BTN_EAST 0x131 +#define BTN_B BTN_EAST +#define BTN_C 0x132 +#define BTN_NORTH 0x133 +#define BTN_X BTN_NORTH +#define BTN_WEST 0x134 +#define BTN_Y BTN_WEST +#define BTN_Z 0x135 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c +#define BTN_THUMBL 0x13d +#define BTN_THUMBR 0x13e +#define BTN_DIGI 0x140 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_RUBBER 0x141 +#define BTN_TOOL_BRUSH 0x142 +#define BTN_TOOL_PENCIL 0x143 +#define BTN_TOOL_AIRBRUSH 0x144 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOOL_MOUSE 0x146 +#define BTN_TOOL_LENS 0x147 +#define BTN_TOOL_QUINTTAP 0x148 +#define BTN_STYLUS3 0x149 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_STYLUS2 0x14c +#define BTN_TOOL_DOUBLETAP 0x14d +#define BTN_TOOL_TRIPLETAP 0x14e +#define BTN_TOOL_QUADTAP 0x14f +#define BTN_WHEEL 0x150 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 +#define KEY_OK 0x160 +#define KEY_SELECT 0x161 +#define KEY_GOTO 0x162 +#define KEY_CLEAR 0x163 +#define KEY_POWER2 0x164 +#define KEY_OPTION 0x165 +#define KEY_INFO 0x166 +#define KEY_TIME 0x167 +#define KEY_VENDOR 0x168 +#define KEY_ARCHIVE 0x169 +#define KEY_PROGRAM 0x16a +#define KEY_CHANNEL 0x16b +#define KEY_FAVORITES 0x16c +#define KEY_EPG 0x16d +#define KEY_PVR 0x16e +#define KEY_MHP 0x16f +#define KEY_LANGUAGE 0x170 +#define KEY_TITLE 0x171 +#define KEY_SUBTITLE 0x172 +#define KEY_ANGLE 0x173 +#define KEY_FULL_SCREEN 0x174 +#define KEY_ZOOM KEY_FULL_SCREEN +#define KEY_MODE 0x175 +#define KEY_KEYBOARD 0x176 +#define KEY_ASPECT_RATIO 0x177 +#define KEY_SCREEN KEY_ASPECT_RATIO +#define KEY_PC 0x178 +#define KEY_TV 0x179 +#define KEY_TV2 0x17a +#define KEY_VCR 0x17b +#define KEY_VCR2 0x17c +#define KEY_SAT 0x17d +#define KEY_SAT2 0x17e +#define KEY_CD 0x17f +#define KEY_TAPE 0x180 +#define KEY_RADIO 0x181 +#define KEY_TUNER 0x182 +#define KEY_PLAYER 0x183 +#define KEY_TEXT 0x184 +#define KEY_DVD 0x185 +#define KEY_AUX 0x186 +#define KEY_MP3 0x187 +#define KEY_AUDIO 0x188 +#define KEY_VIDEO 0x189 +#define KEY_DIRECTORY 0x18a +#define KEY_LIST 0x18b +#define KEY_MEMO 0x18c +#define KEY_CALENDAR 0x18d +#define KEY_RED 0x18e +#define KEY_GREEN 0x18f +#define KEY_YELLOW 0x190 +#define KEY_BLUE 0x191 +#define KEY_CHANNELUP 0x192 +#define KEY_CHANNELDOWN 0x193 +#define KEY_FIRST 0x194 +#define KEY_LAST 0x195 +#define KEY_AB 0x196 +#define KEY_NEXT 0x197 +#define KEY_RESTART 0x198 +#define KEY_SLOW 0x199 +#define KEY_SHUFFLE 0x19a +#define KEY_BREAK 0x19b +#define KEY_PREVIOUS 0x19c +#define KEY_DIGITS 0x19d +#define KEY_TEEN 0x19e +#define KEY_TWEN 0x19f +#define KEY_VIDEOPHONE 0x1a0 +#define KEY_GAMES 0x1a1 +#define KEY_ZOOMIN 0x1a2 +#define KEY_ZOOMOUT 0x1a3 +#define KEY_ZOOMRESET 0x1a4 +#define KEY_WORDPROCESSOR 0x1a5 +#define KEY_EDITOR 0x1a6 +#define KEY_SPREADSHEET 0x1a7 +#define KEY_GRAPHICSEDITOR 0x1a8 +#define KEY_PRESENTATION 0x1a9 +#define KEY_DATABASE 0x1aa +#define KEY_NEWS 0x1ab +#define KEY_VOICEMAIL 0x1ac +#define KEY_ADDRESSBOOK 0x1ad +#define KEY_MESSENGER 0x1ae +#define KEY_DISPLAYTOGGLE 0x1af +#define KEY_BRIGHTNESS_TOGGLE KEY_DISPLAYTOGGLE +#define KEY_SPELLCHECK 0x1b0 +#define KEY_LOGOFF 0x1b1 +#define KEY_DOLLAR 0x1b2 +#define KEY_EURO 0x1b3 +#define KEY_FRAMEBACK 0x1b4 +#define KEY_FRAMEFORWARD 0x1b5 +#define KEY_CONTEXT_MENU 0x1b6 +#define KEY_MEDIA_REPEAT 0x1b7 +#define KEY_10CHANNELSUP 0x1b8 +#define KEY_10CHANNELSDOWN 0x1b9 +#define KEY_IMAGES 0x1ba +#define KEY_NOTIFICATION_CENTER 0x1bc +#define KEY_PICKUP_PHONE 0x1bd +#define KEY_HANGUP_PHONE 0x1be +#define KEY_DEL_EOL 0x1c0 +#define KEY_DEL_EOS 0x1c1 +#define KEY_INS_LINE 0x1c2 +#define KEY_DEL_LINE 0x1c3 +#define KEY_FN 0x1d0 +#define KEY_FN_ESC 0x1d1 +#define KEY_FN_F1 0x1d2 +#define KEY_FN_F2 0x1d3 +#define KEY_FN_F3 0x1d4 +#define KEY_FN_F4 0x1d5 +#define KEY_FN_F5 0x1d6 +#define KEY_FN_F6 0x1d7 +#define KEY_FN_F7 0x1d8 +#define KEY_FN_F8 0x1d9 +#define KEY_FN_F9 0x1da +#define KEY_FN_F10 0x1db +#define KEY_FN_F11 0x1dc +#define KEY_FN_F12 0x1dd +#define KEY_FN_1 0x1de +#define KEY_FN_2 0x1df +#define KEY_FN_D 0x1e0 +#define KEY_FN_E 0x1e1 +#define KEY_FN_F 0x1e2 +#define KEY_FN_S 0x1e3 +#define KEY_FN_B 0x1e4 +#define KEY_FN_RIGHT_SHIFT 0x1e5 +#define KEY_BRL_DOT1 0x1f1 +#define KEY_BRL_DOT2 0x1f2 +#define KEY_BRL_DOT3 0x1f3 +#define KEY_BRL_DOT4 0x1f4 +#define KEY_BRL_DOT5 0x1f5 +#define KEY_BRL_DOT6 0x1f6 +#define KEY_BRL_DOT7 0x1f7 +#define KEY_BRL_DOT8 0x1f8 +#define KEY_BRL_DOT9 0x1f9 +#define KEY_BRL_DOT10 0x1fa +#define KEY_NUMERIC_0 0x200 +#define KEY_NUMERIC_1 0x201 +#define KEY_NUMERIC_2 0x202 +#define KEY_NUMERIC_3 0x203 +#define KEY_NUMERIC_4 0x204 +#define KEY_NUMERIC_5 0x205 +#define KEY_NUMERIC_6 0x206 +#define KEY_NUMERIC_7 0x207 +#define KEY_NUMERIC_8 0x208 +#define KEY_NUMERIC_9 0x209 +#define KEY_NUMERIC_STAR 0x20a +#define KEY_NUMERIC_POUND 0x20b +#define KEY_NUMERIC_A 0x20c +#define KEY_NUMERIC_B 0x20d +#define KEY_NUMERIC_C 0x20e +#define KEY_NUMERIC_D 0x20f +#define KEY_CAMERA_FOCUS 0x210 +#define KEY_WPS_BUTTON 0x211 +#define KEY_TOUCHPAD_TOGGLE 0x212 +#define KEY_TOUCHPAD_ON 0x213 +#define KEY_TOUCHPAD_OFF 0x214 +#define KEY_CAMERA_ZOOMIN 0x215 +#define KEY_CAMERA_ZOOMOUT 0x216 +#define KEY_CAMERA_UP 0x217 +#define KEY_CAMERA_DOWN 0x218 +#define KEY_CAMERA_LEFT 0x219 +#define KEY_CAMERA_RIGHT 0x21a +#define KEY_ATTENDANT_ON 0x21b +#define KEY_ATTENDANT_OFF 0x21c +#define KEY_ATTENDANT_TOGGLE 0x21d +#define KEY_LIGHTS_TOGGLE 0x21e +#define BTN_DPAD_UP 0x220 +#define BTN_DPAD_DOWN 0x221 +#define BTN_DPAD_LEFT 0x222 +#define BTN_DPAD_RIGHT 0x223 +#define KEY_ALS_TOGGLE 0x230 +#define KEY_ROTATE_LOCK_TOGGLE 0x231 +#define KEY_REFRESH_RATE_TOGGLE 0x232 +#define KEY_BUTTONCONFIG 0x240 +#define KEY_TASKMANAGER 0x241 +#define KEY_JOURNAL 0x242 +#define KEY_CONTROLPANEL 0x243 +#define KEY_APPSELECT 0x244 +#define KEY_SCREENSAVER 0x245 +#define KEY_VOICECOMMAND 0x246 +#define KEY_ASSISTANT 0x247 +#define KEY_KBD_LAYOUT_NEXT 0x248 +#define KEY_EMOJI_PICKER 0x249 +#define KEY_DICTATE 0x24a +#define KEY_CAMERA_ACCESS_ENABLE 0x24b +#define KEY_CAMERA_ACCESS_DISABLE 0x24c +#define KEY_CAMERA_ACCESS_TOGGLE 0x24d +#define KEY_ACCESSIBILITY 0x24e +#define KEY_DO_NOT_DISTURB 0x24f +#define KEY_BRIGHTNESS_MIN 0x250 +#define KEY_BRIGHTNESS_MAX 0x251 +#define KEY_KBDINPUTASSIST_PREV 0x260 +#define KEY_KBDINPUTASSIST_NEXT 0x261 +#define KEY_KBDINPUTASSIST_PREVGROUP 0x262 +#define KEY_KBDINPUTASSIST_NEXTGROUP 0x263 +#define KEY_KBDINPUTASSIST_ACCEPT 0x264 +#define KEY_KBDINPUTASSIST_CANCEL 0x265 +#define KEY_RIGHT_UP 0x266 +#define KEY_RIGHT_DOWN 0x267 +#define KEY_LEFT_UP 0x268 +#define KEY_LEFT_DOWN 0x269 +#define KEY_ROOT_MENU 0x26a +#define KEY_MEDIA_TOP_MENU 0x26b +#define KEY_NUMERIC_11 0x26c +#define KEY_NUMERIC_12 0x26d +#define KEY_AUDIO_DESC 0x26e +#define KEY_3D_MODE 0x26f +#define KEY_NEXT_FAVORITE 0x270 +#define KEY_STOP_RECORD 0x271 +#define KEY_PAUSE_RECORD 0x272 +#define KEY_VOD 0x273 +#define KEY_UNMUTE 0x274 +#define KEY_FASTREVERSE 0x275 +#define KEY_SLOWREVERSE 0x276 +#define KEY_DATA 0x277 +#define KEY_ONSCREEN_KEYBOARD 0x278 +#define KEY_PRIVACY_SCREEN_TOGGLE 0x279 +#define KEY_SELECTIVE_SCREENSHOT 0x27a +#define KEY_NEXT_ELEMENT 0x27b +#define KEY_PREVIOUS_ELEMENT 0x27c +#define KEY_AUTOPILOT_ENGAGE_TOGGLE 0x27d +#define KEY_MARK_WAYPOINT 0x27e +#define KEY_SOS 0x27f +#define KEY_NAV_CHART 0x280 +#define KEY_FISHING_CHART 0x281 +#define KEY_SINGLE_RANGE_RADAR 0x282 +#define KEY_DUAL_RANGE_RADAR 0x283 +#define KEY_RADAR_OVERLAY 0x284 +#define KEY_TRADITIONAL_SONAR 0x285 +#define KEY_CLEARVU_SONAR 0x286 +#define KEY_SIDEVU_SONAR 0x287 +#define KEY_NAV_INFO 0x288 +#define KEY_BRIGHTNESS_MENU 0x289 +#define KEY_MACRO1 0x290 +#define KEY_MACRO2 0x291 +#define KEY_MACRO3 0x292 +#define KEY_MACRO4 0x293 +#define KEY_MACRO5 0x294 +#define KEY_MACRO6 0x295 +#define KEY_MACRO7 0x296 +#define KEY_MACRO8 0x297 +#define KEY_MACRO9 0x298 +#define KEY_MACRO10 0x299 +#define KEY_MACRO11 0x29a +#define KEY_MACRO12 0x29b +#define KEY_MACRO13 0x29c +#define KEY_MACRO14 0x29d +#define KEY_MACRO15 0x29e +#define KEY_MACRO16 0x29f +#define KEY_MACRO17 0x2a0 +#define KEY_MACRO18 0x2a1 +#define KEY_MACRO19 0x2a2 +#define KEY_MACRO20 0x2a3 +#define KEY_MACRO21 0x2a4 +#define KEY_MACRO22 0x2a5 +#define KEY_MACRO23 0x2a6 +#define KEY_MACRO24 0x2a7 +#define KEY_MACRO25 0x2a8 +#define KEY_MACRO26 0x2a9 +#define KEY_MACRO27 0x2aa +#define KEY_MACRO28 0x2ab +#define KEY_MACRO29 0x2ac +#define KEY_MACRO30 0x2ad +#define KEY_MACRO_RECORD_START 0x2b0 +#define KEY_MACRO_RECORD_STOP 0x2b1 +#define KEY_MACRO_PRESET_CYCLE 0x2b2 +#define KEY_MACRO_PRESET1 0x2b3 +#define KEY_MACRO_PRESET2 0x2b4 +#define KEY_MACRO_PRESET3 0x2b5 +#define KEY_KBD_LCD_MENU1 0x2b8 +#define KEY_KBD_LCD_MENU2 0x2b9 +#define KEY_KBD_LCD_MENU3 0x2ba +#define KEY_KBD_LCD_MENU4 0x2bb +#define KEY_KBD_LCD_MENU5 0x2bc +#define BTN_TRIGGER_HAPPY 0x2c0 +#define BTN_TRIGGER_HAPPY1 0x2c0 +#define BTN_TRIGGER_HAPPY2 0x2c1 +#define BTN_TRIGGER_HAPPY3 0x2c2 +#define BTN_TRIGGER_HAPPY4 0x2c3 +#define BTN_TRIGGER_HAPPY5 0x2c4 +#define BTN_TRIGGER_HAPPY6 0x2c5 +#define BTN_TRIGGER_HAPPY7 0x2c6 +#define BTN_TRIGGER_HAPPY8 0x2c7 +#define BTN_TRIGGER_HAPPY9 0x2c8 +#define BTN_TRIGGER_HAPPY10 0x2c9 +#define BTN_TRIGGER_HAPPY11 0x2ca +#define BTN_TRIGGER_HAPPY12 0x2cb +#define BTN_TRIGGER_HAPPY13 0x2cc +#define BTN_TRIGGER_HAPPY14 0x2cd +#define BTN_TRIGGER_HAPPY15 0x2ce +#define BTN_TRIGGER_HAPPY16 0x2cf +#define BTN_TRIGGER_HAPPY17 0x2d0 +#define BTN_TRIGGER_HAPPY18 0x2d1 +#define BTN_TRIGGER_HAPPY19 0x2d2 +#define BTN_TRIGGER_HAPPY20 0x2d3 +#define BTN_TRIGGER_HAPPY21 0x2d4 +#define BTN_TRIGGER_HAPPY22 0x2d5 +#define BTN_TRIGGER_HAPPY23 0x2d6 +#define BTN_TRIGGER_HAPPY24 0x2d7 +#define BTN_TRIGGER_HAPPY25 0x2d8 +#define BTN_TRIGGER_HAPPY26 0x2d9 +#define BTN_TRIGGER_HAPPY27 0x2da +#define BTN_TRIGGER_HAPPY28 0x2db +#define BTN_TRIGGER_HAPPY29 0x2dc +#define BTN_TRIGGER_HAPPY30 0x2dd +#define BTN_TRIGGER_HAPPY31 0x2de +#define BTN_TRIGGER_HAPPY32 0x2df +#define BTN_TRIGGER_HAPPY33 0x2e0 +#define BTN_TRIGGER_HAPPY34 0x2e1 +#define BTN_TRIGGER_HAPPY35 0x2e2 +#define BTN_TRIGGER_HAPPY36 0x2e3 +#define BTN_TRIGGER_HAPPY37 0x2e4 +#define BTN_TRIGGER_HAPPY38 0x2e5 +#define BTN_TRIGGER_HAPPY39 0x2e6 +#define BTN_TRIGGER_HAPPY40 0x2e7 +#define KEY_MIN_INTERESTING KEY_MUTE +#define KEY_MAX 0x2ff +#define KEY_CNT (KEY_MAX + 1) +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 +#define REL_RX 0x03 +#define REL_RY 0x04 +#define REL_RZ 0x05 +#define REL_HWHEEL 0x06 +#define REL_DIAL 0x07 +#define REL_WHEEL 0x08 +#define REL_MISC 0x09 +#define REL_RESERVED 0x0a +#define REL_WHEEL_HI_RES 0x0b +#define REL_HWHEEL_HI_RES 0x0c +#define REL_MAX 0x0f +#define REL_CNT (REL_MAX + 1) +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_HAT1X 0x12 +#define ABS_HAT1Y 0x13 +#define ABS_HAT2X 0x14 +#define ABS_HAT2Y 0x15 +#define ABS_HAT3X 0x16 +#define ABS_HAT3Y 0x17 +#define ABS_PRESSURE 0x18 +#define ABS_DISTANCE 0x19 +#define ABS_TILT_X 0x1a +#define ABS_TILT_Y 0x1b +#define ABS_TOOL_WIDTH 0x1c +#define ABS_VOLUME 0x20 +#define ABS_PROFILE 0x21 +#define ABS_MISC 0x28 +#define ABS_RESERVED 0x2e +#define ABS_MT_SLOT 0x2f +#define ABS_MT_TOUCH_MAJOR 0x30 +#define ABS_MT_TOUCH_MINOR 0x31 +#define ABS_MT_WIDTH_MAJOR 0x32 +#define ABS_MT_WIDTH_MINOR 0x33 +#define ABS_MT_ORIENTATION 0x34 +#define ABS_MT_POSITION_X 0x35 +#define ABS_MT_POSITION_Y 0x36 +#define ABS_MT_TOOL_TYPE 0x37 +#define ABS_MT_BLOB_ID 0x38 +#define ABS_MT_TRACKING_ID 0x39 +#define ABS_MT_PRESSURE 0x3a +#define ABS_MT_DISTANCE 0x3b +#define ABS_MT_TOOL_X 0x3c +#define ABS_MT_TOOL_Y 0x3d +#define ABS_MAX 0x3f +#define ABS_CNT (ABS_MAX + 1) +#define SW_LID 0x00 +#define SW_TABLET_MODE 0x01 +#define SW_HEADPHONE_INSERT 0x02 +#define SW_RFKILL_ALL 0x03 +#define SW_RADIO SW_RFKILL_ALL +#define SW_MICROPHONE_INSERT 0x04 +#define SW_DOCK 0x05 +#define SW_LINEOUT_INSERT 0x06 +#define SW_JACK_PHYSICAL_INSERT 0x07 +#define SW_VIDEOOUT_INSERT 0x08 +#define SW_CAMERA_LENS_COVER 0x09 +#define SW_KEYPAD_SLIDE 0x0a +#define SW_FRONT_PROXIMITY 0x0b +#define SW_ROTATE_LOCK 0x0c +#define SW_LINEIN_INSERT 0x0d +#define SW_MUTE_DEVICE 0x0e +#define SW_PEN_INSERTED 0x0f +#define SW_MACHINE_COVER 0x10 +#define SW_MAX 0x10 +#define SW_CNT (SW_MAX + 1) +#define MSC_SERIAL 0x00 +#define MSC_PULSELED 0x01 +#define MSC_GESTURE 0x02 +#define MSC_RAW 0x03 +#define MSC_SCAN 0x04 +#define MSC_TIMESTAMP 0x05 +#define MSC_MAX 0x07 +#define MSC_CNT (MSC_MAX + 1) +#define LED_NUML 0x00 +#define LED_CAPSL 0x01 +#define LED_SCROLLL 0x02 +#define LED_COMPOSE 0x03 +#define LED_KANA 0x04 +#define LED_SLEEP 0x05 +#define LED_SUSPEND 0x06 +#define LED_MUTE 0x07 +#define LED_MISC 0x08 +#define LED_MAIL 0x09 +#define LED_CHARGING 0x0a +#define LED_MAX 0x0f +#define LED_CNT (LED_MAX + 1) +#define REP_DELAY 0x00 +#define REP_PERIOD 0x01 +#define REP_MAX 0x01 +#define REP_CNT (REP_MAX + 1) +#define SND_CLICK 0x00 +#define SND_BELL 0x01 +#define SND_TONE 0x02 +#define SND_MAX 0x07 +#define SND_CNT (SND_MAX + 1) +#endif diff --git a/nativelib/src/main/cpp/include/linux/linux/input.h b/nativelib/src/main/cpp/include/linux/linux/input.h new file mode 100644 index 0000000000..df687f6044 --- /dev/null +++ b/nativelib/src/main/cpp/include/linux/linux/input.h @@ -0,0 +1,207 @@ +/* + * This file is auto-generated. Modifications will be lost. + * + * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/ + * for more information. + */ +#ifndef _UAPI_INPUT_H +#define _UAPI_INPUT_H +#include +#include +#include +#include +#include "input-event-codes.h" +struct input_event { +#if __BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64) + struct timeval time; +#define input_event_sec time.tv_sec +#define input_event_usec time.tv_usec +#else + __kernel_ulong_t __sec; +#if defined(__sparc__) && defined(__arch64__) + unsigned int __usec; + unsigned int __pad; +#else + __kernel_ulong_t __usec; +#endif +#define input_event_sec __sec +#define input_event_usec __usec +#endif + __u16 type; + __u16 code; + __s32 value; +}; +#define EV_VERSION 0x010001 +struct input_id { + __u16 bustype; + __u16 vendor; + __u16 product; + __u16 version; +}; +struct input_absinfo { + __s32 value; + __s32 minimum; + __s32 maximum; + __s32 fuzz; + __s32 flat; + __s32 resolution; +}; +struct input_keymap_entry { +#define INPUT_KEYMAP_BY_INDEX (1 << 0) + __u8 flags; + __u8 len; + __u16 index; + __u32 keycode; + __u8 scancode[32]; +}; +struct input_mask { + __u32 type; + __u32 codes_size; + __u64 codes_ptr; +}; +#define EVIOCGVERSION _IOR('E', 0x01, int) +#define EVIOCGID _IOR('E', 0x02, struct input_id) +#define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) +#define EVIOCSREP _IOW('E', 0x03, unsigned int[2]) +#define EVIOCGKEYCODE _IOR('E', 0x04, unsigned int[2]) +#define EVIOCGKEYCODE_V2 _IOR('E', 0x04, struct input_keymap_entry) +#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2]) +#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry) +#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) +#define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len) +#define EVIOCGUNIQ(len) _IOC(_IOC_READ, 'E', 0x08, len) +#define EVIOCGPROP(len) _IOC(_IOC_READ, 'E', 0x09, len) +#define EVIOCGMTSLOTS(len) _IOC(_IOC_READ, 'E', 0x0a, len) +#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len) +#define EVIOCGLED(len) _IOC(_IOC_READ, 'E', 0x19, len) +#define EVIOCGSND(len) _IOC(_IOC_READ, 'E', 0x1a, len) +#define EVIOCGSW(len) _IOC(_IOC_READ, 'E', 0x1b, len) +#define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len) +#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) +#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo) +#define EVIOCSFF _IOW('E', 0x80, struct ff_effect) +#define EVIOCRMFF _IOW('E', 0x81, int) +#define EVIOCGEFFECTS _IOR('E', 0x84, int) +#define EVIOCGRAB _IOW('E', 0x90, int) +#define EVIOCREVOKE _IOW('E', 0x91, int) +#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) +#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) +#define EVIOCSCLOCKID _IOW('E', 0xa0, int) +#define ID_BUS 0 +#define ID_VENDOR 1 +#define ID_PRODUCT 2 +#define ID_VERSION 3 +#define BUS_PCI 0x01 +#define BUS_ISAPNP 0x02 +#define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 +#define BUS_VIRTUAL 0x06 +#define BUS_ISA 0x10 +#define BUS_I8042 0x11 +#define BUS_XTKBD 0x12 +#define BUS_RS232 0x13 +#define BUS_GAMEPORT 0x14 +#define BUS_PARPORT 0x15 +#define BUS_AMIGA 0x16 +#define BUS_ADB 0x17 +#define BUS_I2C 0x18 +#define BUS_HOST 0x19 +#define BUS_GSC 0x1A +#define BUS_ATARI 0x1B +#define BUS_SPI 0x1C +#define BUS_RMI 0x1D +#define BUS_CEC 0x1E +#define BUS_INTEL_ISHTP 0x1F +#define BUS_AMD_SFH 0x20 +#define MT_TOOL_FINGER 0x00 +#define MT_TOOL_PEN 0x01 +#define MT_TOOL_PALM 0x02 +#define MT_TOOL_DIAL 0x0a +#define MT_TOOL_MAX 0x0f +#define FF_STATUS_STOPPED 0x00 +#define FF_STATUS_PLAYING 0x01 +#define FF_STATUS_MAX 0x01 +struct ff_replay { + __u16 length; + __u16 delay; +}; +struct ff_trigger { + __u16 button; + __u16 interval; +}; +struct ff_envelope { + __u16 attack_length; + __u16 attack_level; + __u16 fade_length; + __u16 fade_level; +}; +struct ff_constant_effect { + __s16 level; + struct ff_envelope envelope; +}; +struct ff_ramp_effect { + __s16 start_level; + __s16 end_level; + struct ff_envelope envelope; +}; +struct ff_condition_effect { + __u16 right_saturation; + __u16 left_saturation; + __s16 right_coeff; + __s16 left_coeff; + __u16 deadband; + __s16 center; +}; +struct ff_periodic_effect { + __u16 waveform; + __u16 period; + __s16 magnitude; + __s16 offset; + __u16 phase; + struct ff_envelope envelope; + __u32 custom_len; + __s16 * custom_data; +}; +struct ff_rumble_effect { + __u16 strong_magnitude; + __u16 weak_magnitude; +}; +struct ff_effect { + __u16 type; + __s16 id; + __u16 direction; + struct ff_trigger trigger; + struct ff_replay replay; + union { + struct ff_constant_effect constant; + struct ff_ramp_effect ramp; + struct ff_periodic_effect periodic; + struct ff_condition_effect condition[2]; + struct ff_rumble_effect rumble; + } u; +}; +#define FF_RUMBLE 0x50 +#define FF_PERIODIC 0x51 +#define FF_CONSTANT 0x52 +#define FF_SPRING 0x53 +#define FF_FRICTION 0x54 +#define FF_DAMPER 0x55 +#define FF_INERTIA 0x56 +#define FF_RAMP 0x57 +#define FF_EFFECT_MIN FF_RUMBLE +#define FF_EFFECT_MAX FF_RAMP +#define FF_SQUARE 0x58 +#define FF_TRIANGLE 0x59 +#define FF_SINE 0x5a +#define FF_SAW_UP 0x5b +#define FF_SAW_DOWN 0x5c +#define FF_CUSTOM 0x5d +#define FF_WAVEFORM_MIN FF_SQUARE +#define FF_WAVEFORM_MAX FF_CUSTOM +#define FF_GAIN 0x60 +#define FF_AUTOCENTER 0x61 +#define FF_MAX_EFFECTS FF_GAIN +#define FF_MAX 0x7f +#define FF_CNT (FF_MAX + 1) +#endif diff --git a/nativelib/src/main/cpp/include/linux/linux/uinput.h b/nativelib/src/main/cpp/include/linux/linux/uinput.h new file mode 100644 index 0000000000..434f02db66 --- /dev/null +++ b/nativelib/src/main/cpp/include/linux/linux/uinput.h @@ -0,0 +1,231 @@ +/* + * User level driver support for input subsystem + * + * Heavily based on evdev.c by Vojtech Pavlik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Aristeu Sergio Rozanski Filho + * + * Changes/Revisions: + * 0.5 08/13/2015 (David Herrmann & + * Benjamin Tissoires ) + * - add UI_DEV_SETUP ioctl + * - add UI_ABS_SETUP ioctl + * - add UI_GET_VERSION ioctl + * 0.4 01/09/2014 (Benjamin Tissoires ) + * - add UI_GET_SYSNAME ioctl + * 0.3 24/05/2006 (Anssi Hannula ) + * - update ff support for the changes in kernel interface + * - add UINPUT_VERSION + * 0.2 16/10/2004 (Micah Dowty ) + * - added force feedback support + * - added UI_SET_PHYS + * 0.1 20/06/2002 + * - first public version + */ +#ifndef __UINPUT_H_ +#define __UINPUT_H_ + +#include +#include + +#define UINPUT_VERSION 5 +#define UINPUT_MAX_NAME_SIZE 80 + +struct uinput_ff_upload { + __u32 request_id; + __s32 retval; + struct ff_effect effect; + struct ff_effect old; +}; + +struct uinput_ff_erase { + __u32 request_id; + __s32 retval; + __u32 effect_id; +}; + +/* ioctl */ +#define UINPUT_IOCTL_BASE 'U' +#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1) +#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2) + +struct uinput_setup { + struct input_id id; + char name[UINPUT_MAX_NAME_SIZE]; + __u32 ff_effects_max; +}; + +/** + * UI_DEV_SETUP - Set device parameters for setup + * + * This ioctl sets parameters for the input device to be created. It + * supersedes the old "struct uinput_user_dev" method, which wrote this data + * via write(). To actually set the absolute axes UI_ABS_SETUP should be + * used. + * + * The ioctl takes a "struct uinput_setup" object as argument. The fields of + * this object are as follows: + * id: See the description of "struct input_id". This field is + * copied unchanged into the new device. + * name: This is used unchanged as name for the new device. + * ff_effects_max: This limits the maximum numbers of force-feedback effects. + * See below for a description of FF with uinput. + * + * This ioctl can be called multiple times and will overwrite previous values. + * If this ioctl fails with -EINVAL, it is recommended to use the old + * "uinput_user_dev" method via write() as a fallback, in case you run on an + * old kernel that does not support this ioctl. + * + * This ioctl may fail with -EINVAL if it is not supported or if you passed + * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the + * passed uinput_setup object cannot be read/written. + * If this call fails, partial data may have already been applied to the + * internal device. + */ +#define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup) + +struct uinput_abs_setup { + __u16 code; /* axis code */ + /* __u16 filler; */ + struct input_absinfo absinfo; +}; + +/** + * UI_ABS_SETUP - Set absolute axis information for the device to setup + * + * This ioctl sets one absolute axis information for the input device to be + * created. It supersedes the old "struct uinput_user_dev" method, which wrote + * part of this data and the content of UI_DEV_SETUP via write(). + * + * The ioctl takes a "struct uinput_abs_setup" object as argument. The fields + * of this object are as follows: + * code: The corresponding input code associated with this axis + * (ABS_X, ABS_Y, etc...) + * absinfo: See "struct input_absinfo" for a description of this field. + * This field is copied unchanged into the kernel for the + * specified axis. If the axis is not enabled via + * UI_SET_ABSBIT, this ioctl will enable it. + * + * This ioctl can be called multiple times and will overwrite previous values. + * If this ioctl fails with -EINVAL, it is recommended to use the old + * "uinput_user_dev" method via write() as a fallback, in case you run on an + * old kernel that does not support this ioctl. + * + * This ioctl may fail with -EINVAL if it is not supported or if you passed + * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the + * passed uinput_setup object cannot be read/written. + * If this call fails, partial data may have already been applied to the + * internal device. + */ +#define UI_ABS_SETUP _IOW(UINPUT_IOCTL_BASE, 4, struct uinput_abs_setup) + +#define UI_SET_EVBIT _IOW(UINPUT_IOCTL_BASE, 100, int) +#define UI_SET_KEYBIT _IOW(UINPUT_IOCTL_BASE, 101, int) +#define UI_SET_RELBIT _IOW(UINPUT_IOCTL_BASE, 102, int) +#define UI_SET_ABSBIT _IOW(UINPUT_IOCTL_BASE, 103, int) +#define UI_SET_MSCBIT _IOW(UINPUT_IOCTL_BASE, 104, int) +#define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int) +#define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int) +#define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int) +#define UI_SET_PHYS _IOW(UINPUT_IOCTL_BASE, 108, char*) +#define UI_SET_SWBIT _IOW(UINPUT_IOCTL_BASE, 109, int) +#define UI_SET_PROPBIT _IOW(UINPUT_IOCTL_BASE, 110, int) + +#define UI_BEGIN_FF_UPLOAD _IOWR(UINPUT_IOCTL_BASE, 200, struct uinput_ff_upload) +#define UI_END_FF_UPLOAD _IOW(UINPUT_IOCTL_BASE, 201, struct uinput_ff_upload) +#define UI_BEGIN_FF_ERASE _IOWR(UINPUT_IOCTL_BASE, 202, struct uinput_ff_erase) +#define UI_END_FF_ERASE _IOW(UINPUT_IOCTL_BASE, 203, struct uinput_ff_erase) + +/** + * UI_GET_SYSNAME - get the sysfs name of the created uinput device + * + * @return the sysfs name of the created virtual input device. + * The complete sysfs path is then /sys/devices/virtual/input/--NAME-- + * Usually, it is in the form "inputN" + */ +#define UI_GET_SYSNAME(len) _IOC(_IOC_READ, UINPUT_IOCTL_BASE, 44, len) + +/** + * UI_GET_VERSION - Return version of uinput protocol + * + * This writes uinput protocol version implemented by the kernel into + * the integer pointed to by the ioctl argument. The protocol version + * is hard-coded in the kernel and is independent of the uinput device. + */ +#define UI_GET_VERSION _IOR(UINPUT_IOCTL_BASE, 45, unsigned int) + +/* + * To write a force-feedback-capable driver, the upload_effect + * and erase_effect callbacks in input_dev must be implemented. + * The uinput driver will generate a fake input event when one of + * these callbacks are invoked. The userspace code then uses + * ioctls to retrieve additional parameters and send the return code. + * The callback blocks until this return code is sent. + * + * The described callback mechanism is only used if ff_effects_max + * is set. + * + * To implement upload_effect(): + * 1. Wait for an event with type == EV_UINPUT and code == UI_FF_UPLOAD. + * A request ID will be given in 'value'. + * 2. Allocate a uinput_ff_upload struct, fill in request_id with + * the 'value' from the EV_UINPUT event. + * 3. Issue a UI_BEGIN_FF_UPLOAD ioctl, giving it the + * uinput_ff_upload struct. It will be filled in with the + * ff_effects passed to upload_effect(). + * 4. Perform the effect upload, and place a return code back into + the uinput_ff_upload struct. + * 5. Issue a UI_END_FF_UPLOAD ioctl, also giving it the + * uinput_ff_upload_effect struct. This will complete execution + * of our upload_effect() handler. + * + * To implement erase_effect(): + * 1. Wait for an event with type == EV_UINPUT and code == UI_FF_ERASE. + * A request ID will be given in 'value'. + * 2. Allocate a uinput_ff_erase struct, fill in request_id with + * the 'value' from the EV_UINPUT event. + * 3. Issue a UI_BEGIN_FF_ERASE ioctl, giving it the + * uinput_ff_erase struct. It will be filled in with the + * effect ID passed to erase_effect(). + * 4. Perform the effect erasure, and place a return code back + * into the uinput_ff_erase struct. + * 5. Issue a UI_END_FF_ERASE ioctl, also giving it the + * uinput_ff_erase_effect struct. This will complete execution + * of our erase_effect() handler. + */ + +/* + * This is the new event type, used only by uinput. + * 'code' is UI_FF_UPLOAD or UI_FF_ERASE, and 'value' + * is the unique request ID. This number was picked + * arbitrarily, above EV_MAX (since the input system + * never sees it) but in the range of a 16-bit int. + */ +#define EV_UINPUT 0x0101 +#define UI_FF_UPLOAD 1 +#define UI_FF_ERASE 2 + +struct uinput_user_dev { + char name[UINPUT_MAX_NAME_SIZE]; + struct input_id id; + __u32 ff_effects_max; + __s32 absmax[ABS_CNT]; + __s32 absmin[ABS_CNT]; + __s32 absfuzz[ABS_CNT]; + __s32 absflat[ABS_CNT]; +}; +#endif /* __UINPUT_H_ */ diff --git a/nativelib/src/main/cpp/include/linux/uinput.h b/nativelib/src/main/cpp/include/linux/uinput.h new file mode 100644 index 0000000000..1ef4e3ba91 --- /dev/null +++ b/nativelib/src/main/cpp/include/linux/uinput.h @@ -0,0 +1,5 @@ +#ifdef __linux__ +#include "linux/uinput.h" +#elif __FreeBSD__ +#include "freebsd/uinput.h" +#endif diff --git a/nativelib/src/main/cpp/libevdev/.gitignore b/nativelib/src/main/cpp/libevdev/.gitignore new file mode 100644 index 0000000000..339d72d3b8 --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/.gitignore @@ -0,0 +1 @@ +event-names.h diff --git a/nativelib/src/main/cpp/libevdev/Makefile.am b/nativelib/src/main/cpp/libevdev/Makefile.am new file mode 100644 index 0000000000..f577900827 --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/Makefile.am @@ -0,0 +1,42 @@ +lib_LTLIBRARIES=libevdev.la + +AM_CPPFLAGS = $(GCC_CFLAGS) $(GCOV_CFLAGS) -I$(top_srcdir)/include -I$(top_srcdir) +AM_LDFLAGS = $(GCOV_LDFLAGS) + +libevdev_la_SOURCES = \ + libevdev.h \ + libevdev-int.h \ + libevdev-util.h \ + libevdev-uinput.c \ + libevdev-uinput.h \ + libevdev-uinput-int.h \ + libevdev.c \ + libevdev-names.c \ + ../include/linux/input.h \ + ../include/linux/uinput.h \ + ../include/linux/@OS@/input-event-codes.h \ + ../include/linux/@OS@/input.h \ + ../include/linux/@OS@/uinput.h + +libevdev_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBEVDEV_LT_VERSION) \ + -Wl,--version-script="$(srcdir)/libevdev.sym" \ + $(GNU_LD_FLAGS) + +EXTRA_libevdev_la_DEPENDENCIES = $(srcdir)/libevdev.sym + +libevdevincludedir = $(includedir)/libevdev-1.0/libevdev +libevdevinclude_HEADERS = libevdev.h libevdev-uinput.h + +event-names.h: Makefile make-event-names.py + $(PYTHON) $(srcdir)/make-event-names.py $(top_srcdir)/include/linux/@OS@/input.h $(top_srcdir)/include/linux/@OS@/input-event-codes.h > $@ + + +EXTRA_DIST = make-event-names.py libevdev.sym ../include +CLEANFILES = event-names.h +BUILT_SOURCES = event-names.h + +if GCOV_ENABLED +CLEANFILES += *.gcno +endif diff --git a/nativelib/src/main/cpp/libevdev/libevdev-int.h b/nativelib/src/main/cpp/libevdev/libevdev-int.h new file mode 100644 index 0000000000..672fd2f48f --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev-int.h @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2013 Red Hat, Inc. + */ + +#ifndef LIBEVDEV_INT_H +#define LIBEVDEV_INT_H + +#include +#include +#include +#include +#include "libevdev.h" +#include "libevdev-util.h" + +#define MAX_NAME 256 +#define ABS_MT_MIN ABS_MT_SLOT +#define ABS_MT_MAX ABS_MT_TOOL_Y +#define ABS_MT_CNT (ABS_MT_MAX - ABS_MT_MIN + 1) +#define LIBEVDEV_EXPORT __attribute__((visibility("default"))) +#define ALIAS(_to) __attribute__((alias(#_to))) + +/** + * Sync state machine: + * default state: SYNC_NONE + * + * SYNC_NONE → SYN_DROPPED or forced sync → SYNC_NEEDED + * SYNC_NEEDED → libevdev_next_event(LIBEVDEV_READ_FLAG_SYNC) → SYNC_IN_PROGRESS + * SYNC_NEEDED → libevdev_next_event(LIBEVDEV_READ_FLAG_SYNC_NONE) → SYNC_NONE + * SYNC_IN_PROGRESS → libevdev_next_event(LIBEVDEV_READ_FLAG_SYNC_NONE) → SYNC_NONE + * SYNC_IN_PROGRESS → no sync events left → SYNC_NONE + * + */ +enum SyncState { + SYNC_NONE, + SYNC_NEEDED, + SYNC_IN_PROGRESS, +}; + +/** + * Internal only: log data used to send messages to the respective log + * handler. We re-use the same struct for a global and inside + * struct libevdev. + * For the global, device_handler is NULL, for per-device instance + * global_handler is NULL. + */ +struct logdata { + enum libevdev_log_priority priority; /** minimum logging priority */ + libevdev_log_func_t global_handler; /** global handler function */ + libevdev_device_log_func_t device_handler; /** per-device handler function */ + void *userdata; /** user-defined data pointer */ +}; + +struct libevdev { + int fd; + bool initialized; + char *name; + char *phys; + char *uniq; + struct input_id ids; + int driver_version; + unsigned long bits[NLONGS(EV_CNT)]; + unsigned long props[NLONGS(INPUT_PROP_CNT)]; + unsigned long key_bits[NLONGS(KEY_CNT)]; + unsigned long rel_bits[NLONGS(REL_CNT)]; + unsigned long abs_bits[NLONGS(ABS_CNT)]; + unsigned long led_bits[NLONGS(LED_CNT)]; + unsigned long msc_bits[NLONGS(MSC_CNT)]; + unsigned long sw_bits[NLONGS(SW_CNT)]; + unsigned long rep_bits[NLONGS(REP_CNT)]; /* convenience, always 1 */ + unsigned long ff_bits[NLONGS(FF_CNT)]; + unsigned long snd_bits[NLONGS(SND_CNT)]; + unsigned long key_values[NLONGS(KEY_CNT)]; + unsigned long led_values[NLONGS(LED_CNT)]; + unsigned long sw_values[NLONGS(SW_CNT)]; + struct input_absinfo abs_info[ABS_CNT]; + int *mt_slot_vals; /* [num_slots * ABS_MT_CNT] */ + int num_slots; /**< valid slots in mt_slot_vals */ + int current_slot; + int rep_values[REP_CNT]; + + enum SyncState sync_state; + enum libevdev_grab_mode grabbed; + + struct input_event *queue; + size_t queue_size; /**< size of queue in elements */ + size_t queue_next; /**< next event index */ + size_t queue_nsync; /**< number of sync events */ + + struct timeval last_event_time; + + struct logdata log; +}; + +#define log_msg_cond(dev, priority, ...) \ + do { \ + if (_libevdev_log_priority(dev) >= priority) \ + _libevdev_log_msg(dev, priority, __FILE__, __LINE__, __func__, __VA_ARGS__); \ + } while(0) + +#define log_error(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_ERROR, __VA_ARGS__) +#define log_info(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_INFO, __VA_ARGS__) +#define log_dbg(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_DEBUG, __VA_ARGS__) +#define log_bug(dev, ...) log_msg_cond(dev, LIBEVDEV_LOG_ERROR, "BUG: "__VA_ARGS__) + +extern void +_libevdev_log_msg(const struct libevdev *dev, + enum libevdev_log_priority priority, + const char *file, int line, const char *func, + const char *format, ...) LIBEVDEV_ATTRIBUTE_PRINTF(6, 7); +extern enum libevdev_log_priority +_libevdev_log_priority(const struct libevdev *dev); + +static inline void +init_event(struct libevdev *dev, struct input_event *ev, int type, int code, int value) +{ + ev->input_event_sec = dev->last_event_time.tv_sec; + ev->input_event_usec = dev->last_event_time.tv_usec; + ev->type = type; + ev->code = code; + ev->value = value; +} + +/** + * @return a pointer to the next element in the queue, or NULL if the queue + * is full. + */ +static inline struct input_event* +queue_push(struct libevdev *dev) +{ + if (dev->queue_next >= dev->queue_size) + return NULL; + + return &dev->queue[dev->queue_next++]; +} + +static inline bool +queue_push_event(struct libevdev *dev, unsigned int type, + unsigned int code, int value) +{ + struct input_event *ev = queue_push(dev); + + if (ev) + init_event(dev, ev, type, code, value); + + return ev != NULL; +} + +/** + * Set ev to the last element in the queue, removing it from the queue. + * + * @return 0 on success, 1 if the queue is empty. + */ +static inline int +queue_pop(struct libevdev *dev, struct input_event *ev) +{ + if (dev->queue_next == 0) + return 1; + + *ev = dev->queue[--dev->queue_next]; + + return 0; +} + +static inline int +queue_peek(struct libevdev *dev, size_t idx, struct input_event *ev) +{ + if (dev->queue_next == 0 || idx > dev->queue_next) + return 1; + *ev = dev->queue[idx]; + return 0; +} + +/** + * Shift the first n elements into ev and return the number of elements + * shifted. + * ev must be large enough to store n elements. + * + * @param ev The buffer to copy into, or NULL + * @return The number of elements in ev. + */ +static inline int +queue_shift_multiple(struct libevdev *dev, size_t n, struct input_event *ev) +{ + size_t remaining; + + if (dev->queue_next == 0) + return 0; + + remaining = dev->queue_next; + n = min(n, remaining); + remaining -= n; + + if (ev) + memcpy(ev, dev->queue, n * sizeof(*ev)); + + memmove(dev->queue, &dev->queue[n], remaining * sizeof(*dev->queue)); + + dev->queue_next = remaining; + return n; +} + +/** + * Set ev to the first element in the queue, shifting everything else + * forward by one. + * + * @return 0 on success, 1 if the queue is empty. + */ +static inline int +queue_shift(struct libevdev *dev, struct input_event *ev) +{ + return queue_shift_multiple(dev, 1, ev) == 1 ? 0 : 1; +} + +static inline int +queue_alloc(struct libevdev *dev, size_t size) +{ + if (size == 0) + return -ENOMEM; + + dev->queue = calloc(size, sizeof(struct input_event)); + if (!dev->queue) + return -ENOMEM; + + dev->queue_size = size; + dev->queue_next = 0; + return 0; +} + +static inline void +queue_free(struct libevdev *dev) +{ + free(dev->queue); + dev->queue_size = 0; + dev->queue_next = 0; +} + +static inline size_t +queue_num_elements(struct libevdev *dev) +{ + return dev->queue_next; +} + +static inline size_t +queue_size(struct libevdev *dev) +{ + return dev->queue_size; +} + +static inline size_t +queue_num_free_elements(struct libevdev *dev) +{ + if (dev->queue_size == 0) + return 0; + + return dev->queue_size - dev->queue_next; +} + +static inline struct input_event * +queue_next_element(struct libevdev *dev) +{ + if (dev->queue_next == dev->queue_size) + return NULL; + + return &dev->queue[dev->queue_next]; +} + +static inline int +queue_set_num_elements(struct libevdev *dev, size_t nelem) +{ + if (nelem > dev->queue_size) + return 1; + + dev->queue_next = nelem; + + return 0; +} + +#define max_mask(uc, lc) \ + case EV_##uc: \ + *mask = dev->lc##_bits; \ + max = libevdev_event_type_get_max(type); \ + break; + +static inline int +type_to_mask_const(const struct libevdev *dev, unsigned int type, const unsigned long **mask) +{ + int max; + + switch(type) { + max_mask(ABS, abs); + max_mask(REL, rel); + max_mask(KEY, key); + max_mask(LED, led); + max_mask(MSC, msc); + max_mask(SW, sw); + max_mask(FF, ff); + max_mask(REP, rep); + max_mask(SND, snd); + default: + max = -1; + break; + } + + return max; +} + +static inline int +type_to_mask(struct libevdev *dev, unsigned int type, unsigned long **mask) +{ + int max; + + switch(type) { + max_mask(ABS, abs); + max_mask(REL, rel); + max_mask(KEY, key); + max_mask(LED, led); + max_mask(MSC, msc); + max_mask(SW, sw); + max_mask(FF, ff); + max_mask(REP, rep); + max_mask(SND, snd); + default: + max = -1; + break; + } + + return max; +} + +#undef max_mask +#endif diff --git a/nativelib/src/main/cpp/libevdev/libevdev-names.c b/nativelib/src/main/cpp/libevdev/libevdev-names.c new file mode 100644 index 0000000000..f8c991b84a --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev-names.c @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2013 David Herrmann + */ + +#include +#include +#include +#include + +#include "libevdev-int.h" +#include "libevdev-util.h" +#include "libevdev.h" + +#include "event-names.h" + +struct name_lookup { + const char *name; + size_t len; +}; + +static int cmp_entry(const void *vlookup, const void *ventry) +{ + const struct name_lookup *lookup = vlookup; + const struct name_entry *entry = ventry; + int r; + + r = strncmp(lookup->name, entry->name, lookup->len); + if (!r) { + if (entry->name[lookup->len]) + r = -1; + else + r = 0; + } + + return r; +} + +static const struct name_entry* +lookup_name(const struct name_entry *array, size_t asize, + struct name_lookup *lookup) +{ + const struct name_entry *entry; + + entry = bsearch(lookup, array, asize, sizeof(*array), cmp_entry); + if (!entry) + return NULL; + + return entry; +} + +LIBEVDEV_EXPORT int +libevdev_event_type_from_name(const char *name) +{ + return libevdev_event_type_from_name_n(name, strlen(name)); +} + +LIBEVDEV_EXPORT int +libevdev_event_type_from_name_n(const char *name, size_t len) +{ + struct name_lookup lookup; + const struct name_entry *entry; + + lookup.name = name; + lookup.len = len; + + entry = lookup_name(ev_names, ARRAY_LENGTH(ev_names), &lookup); + + return entry ? (int)entry->value : -1; +} + +static int type_from_prefix(const char *name, ssize_t len) +{ + const char *e; + size_t i; + ssize_t l; + + /* MAX_ is not allowed, even though EV_MAX exists */ + if (startswith(name, len, "MAX_", 4)) + return -1; + /* BTN_ is special as there is no EV_BTN type */ + if (startswith(name, len, "BTN_", 4)) + return EV_KEY; + /* FF_STATUS_ is special as FF_ is a prefix of it, so test it first */ + if (startswith(name, len, "FF_STATUS_", 10)) + return EV_FF_STATUS; + + for (i = 0; i < ARRAY_LENGTH(ev_names); ++i) { + /* skip EV_ prefix so @e is suffix of [EV_]XYZ */ + e = &ev_names[i].name[3]; + l = strlen(e); + + /* compare prefix and test for trailing _ */ + if (len > l && startswith(name, len, e, l) && name[l] == '_') + return ev_names[i].value; + } + + return -1; +} + +LIBEVDEV_EXPORT int +libevdev_event_code_from_name(unsigned int type, const char *name) +{ + return libevdev_event_code_from_name_n(type, name, strlen(name)); +} + +LIBEVDEV_EXPORT int +libevdev_event_code_from_name_n(unsigned int type, const char *name, size_t len) +{ + struct name_lookup lookup; + const struct name_entry *entry; + int real_type; + + /* verify that @name is really of type @type */ + real_type = type_from_prefix(name, len); + if (real_type < 0 || (unsigned int)real_type != type) + return -1; + + /* now look up the name @name and return the constant */ + lookup.name = name; + lookup.len = len; + + entry = lookup_name(code_names, ARRAY_LENGTH(code_names), &lookup); + + return entry ? (int)entry->value : -1; +} + +LIBEVDEV_EXPORT int +libevdev_event_value_from_name(unsigned int type, unsigned int code, const char *name) +{ + return libevdev_event_value_from_name_n(type, code, name, strlen(name)); +} + +LIBEVDEV_EXPORT int +libevdev_event_value_from_name_n(unsigned int type, unsigned int code, const char *name, size_t len) +{ + struct name_lookup lookup; + const struct name_entry *entry; + + if (type != EV_ABS || code != ABS_MT_TOOL_TYPE) + return -1; + + lookup.name = name; + lookup.len = len; + + entry = lookup_name(tool_type_names, ARRAY_LENGTH(tool_type_names), &lookup); + + return entry ? (int)entry->value : -1; +} + +LIBEVDEV_EXPORT int +libevdev_property_from_name(const char *name) +{ + return libevdev_property_from_name_n(name, strlen(name)); +} + +LIBEVDEV_EXPORT int +libevdev_property_from_name_n(const char *name, size_t len) +{ + struct name_lookup lookup; + const struct name_entry *entry; + + lookup.name = name; + lookup.len = len; + + entry = lookup_name(prop_names, ARRAY_LENGTH(prop_names), &lookup); + + return entry ? (int)entry->value : -1; +} + +LIBEVDEV_EXPORT int +libevdev_event_code_from_code_name(const char *name) +{ + return libevdev_event_code_from_code_name_n(name, strlen(name)); +} + +LIBEVDEV_EXPORT int +libevdev_event_code_from_code_name_n(const char *name, size_t len) +{ + const struct name_entry *entry; + struct name_lookup lookup; + + /* now look up the name @name and return the constant */ + lookup.name = name; + lookup.len = len; + + entry = lookup_name(code_names, ARRAY_LENGTH(code_names), &lookup); + + return entry ? (int)entry->value : -1; +} + +LIBEVDEV_EXPORT int +libevdev_event_type_from_code_name(const char *name) +{ + return libevdev_event_type_from_code_name_n(name, strlen(name)); +} + +LIBEVDEV_EXPORT int +libevdev_event_type_from_code_name_n(const char *name, size_t len) +{ + const struct name_entry *entry; + struct name_lookup lookup; + + /* First look up if the name exists, we dont' want to return a valid + * type for an invalid code name */ + lookup.name = name; + lookup.len = len; + + entry = lookup_name(code_names, ARRAY_LENGTH(code_names), &lookup); + + return entry ? type_from_prefix(name, len) : -1; +} diff --git a/nativelib/src/main/cpp/libevdev/libevdev-uinput-int.h b/nativelib/src/main/cpp/libevdev/libevdev-uinput-int.h new file mode 100644 index 0000000000..d7e674a93d --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev-uinput-int.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2013 Red Hat, Inc. + */ + +struct libevdev_uinput { + int fd; /**< file descriptor to uinput */ + int fd_is_managed; /**< do we need to close it? */ + char *name; /**< device name */ + char *syspath; /**< /sys path */ + char *devnode; /**< device node */ + time_t ctime[2]; /**< before/after UI_DEV_CREATE */ +}; diff --git a/nativelib/src/main/cpp/libevdev/libevdev-uinput.c b/nativelib/src/main/cpp/libevdev/libevdev-uinput.c new file mode 100644 index 0000000000..b2f6144c6b --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev-uinput.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2013 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libevdev-int.h" +#include "libevdev-uinput-int.h" +#include "libevdev-uinput.h" +#include "libevdev-util.h" +#include "libevdev.h" + +#ifndef UINPUT_IOCTL_BASE +#define UINPUT_IOCTL_BASE 'U' +#endif + +#ifndef UI_SET_PROPBIT +#define UI_SET_PROPBIT _IOW(UINPUT_IOCTL_BASE, 110, int) +#endif + +static struct libevdev_uinput * +alloc_uinput_device(const char *name) +{ + struct libevdev_uinput *uinput_dev; + + uinput_dev = calloc(1, sizeof(struct libevdev_uinput)); + if (uinput_dev) { + uinput_dev->name = strdup(name); + uinput_dev->fd = -1; + } + + return uinput_dev; +} + +static inline int +set_abs(const struct libevdev *dev, int fd, unsigned int code) +{ + const struct input_absinfo *abs = libevdev_get_abs_info(dev, code); + struct uinput_abs_setup abs_setup = {0}; + int rc; + + abs_setup.code = code; + abs_setup.absinfo = *abs; + rc = ioctl(fd, UI_ABS_SETUP, &abs_setup); + return rc; +} + +static int +set_evbits(const struct libevdev *dev, int fd, struct uinput_user_dev *uidev) +{ + int rc = 0; + unsigned int type; + + for (type = 0; type < EV_CNT; type++) { + unsigned int code; + int max; + int uinput_bit; + const unsigned long *mask; + + if (!libevdev_has_event_type(dev, type)) + continue; + + rc = ioctl(fd, UI_SET_EVBIT, type); + if (rc == -1) + break; + + /* uinput can't set EV_REP */ + if (type == EV_REP) + continue; + + max = type_to_mask_const(dev, type, &mask); + if (max == -1) + continue; + + switch(type) { + case EV_KEY: uinput_bit = UI_SET_KEYBIT; break; + case EV_REL: uinput_bit = UI_SET_RELBIT; break; + case EV_ABS: uinput_bit = UI_SET_ABSBIT; break; + case EV_MSC: uinput_bit = UI_SET_MSCBIT; break; + case EV_LED: uinput_bit = UI_SET_LEDBIT; break; + case EV_SND: uinput_bit = UI_SET_SNDBIT; break; + case EV_FF: uinput_bit = UI_SET_FFBIT; break; + case EV_SW: uinput_bit = UI_SET_SWBIT; break; + default: + rc = -1; + errno = EINVAL; + goto out; + } + + for (code = 0; code <= (unsigned int)max; code++) { + if (!libevdev_has_event_code(dev, type, code)) + continue; + + rc = ioctl(fd, uinput_bit, code); + if (rc == -1) + goto out; + + if (type == EV_ABS) { + if (uidev == NULL) { + rc = set_abs(dev, fd, code); + if (rc != 0) + goto out; + } else { + const struct input_absinfo *abs = + libevdev_get_abs_info(dev, code); + + uidev->absmin[code] = abs->minimum; + uidev->absmax[code] = abs->maximum; + uidev->absfuzz[code] = abs->fuzz; + uidev->absflat[code] = abs->flat; + /* uinput has no resolution in the + * device struct */ + } + } + } + + } + +out: + return rc; +} + +static int +set_props(const struct libevdev *dev, int fd) +{ + unsigned int prop; + int rc = 0; + + for (prop = 0; prop <= INPUT_PROP_MAX; prop++) { + if (!libevdev_has_property(dev, prop)) + continue; + + rc = ioctl(fd, UI_SET_PROPBIT, prop); + if (rc == -1) { + /* If UI_SET_PROPBIT is not supported, treat -EINVAL + * as success. The kernel only sends -EINVAL for an + * invalid ioctl, invalid INPUT_PROP_MAX or if the + * ioctl is called on an already created device. The + * last two can't happen here. + */ + if (errno == EINVAL) + rc = 0; + break; + } + } + return rc; +} + +LIBEVDEV_EXPORT int +libevdev_uinput_get_fd(const struct libevdev_uinput *uinput_dev) +{ + return uinput_dev->fd; +} + +#ifdef __FreeBSD__ +/* + * FreeBSD does not have anything similar to sysfs. + * Set libevdev_uinput->syspath to NULL unconditionally. + * Look up the device nodes directly instead of via sysfs, as this matches what + * is returned by the UI_GET_SYSNAME ioctl() on FreeBSD. + */ +static int +fetch_syspath_and_devnode(struct libevdev_uinput *uinput_dev) +{ +#define DEV_INPUT_DIR "/dev/input/" + int rc; + char buf[sizeof(DEV_INPUT_DIR) + 64] = DEV_INPUT_DIR; + + rc = ioctl(uinput_dev->fd, + UI_GET_SYSNAME(sizeof(buf) - strlen(DEV_INPUT_DIR)), + &buf[strlen(DEV_INPUT_DIR)]); + if (rc == -1) + return -1; + + uinput_dev->syspath = NULL; + uinput_dev->devnode = strdup(buf); + + return 0; +#undef DEV_INPUT_DIR +} + +#else /* !__FreeBSD__ */ + +static int is_event_device(const struct dirent *dent) { + return strncmp("event", dent->d_name, 5) == 0; +} + +static char * +fetch_device_node(const char *path) +{ + char *devnode = NULL; + struct dirent **namelist; + int ndev, i; + + ndev = scandir(path, &namelist, is_event_device, alphasort); + if (ndev <= 0) + return NULL; + + /* ndev should only ever be 1 */ + + for (i = 0; i < ndev; i++) { + if (!devnode && asprintf(&devnode, "/dev/input/%s", namelist[i]->d_name) == -1) + devnode = NULL; + free(namelist[i]); + } + + free(namelist); + + return devnode; +} + +static int is_input_device(const struct dirent *dent) { + return strncmp("input", dent->d_name, 5) == 0; +} + +static int +fetch_syspath_and_devnode(struct libevdev_uinput *uinput_dev) +{ +#define SYS_INPUT_DIR "/sys/devices/virtual/input/" + struct dirent **namelist; + int ndev, i; + int rc; + char buf[sizeof(SYS_INPUT_DIR) + 64] = SYS_INPUT_DIR; + + rc = ioctl(uinput_dev->fd, + UI_GET_SYSNAME(sizeof(buf) - strlen(SYS_INPUT_DIR)), + &buf[strlen(SYS_INPUT_DIR)]); + if (rc != -1) { + uinput_dev->syspath = strdup(buf); + uinput_dev->devnode = fetch_device_node(buf); + return 0; + } + + ndev = scandir(SYS_INPUT_DIR, &namelist, is_input_device, alphasort); + if (ndev <= 0) + return -1; + + for (i = 0; i < ndev; i++) { + int fd, len; + struct stat st; + + rc = snprintf(buf, sizeof(buf), "%s%s/name", + SYS_INPUT_DIR, + namelist[i]->d_name); + if (rc < 0 || (size_t)rc >= sizeof(buf)) { + continue; + } + + /* created within time frame */ + fd = open(buf, O_RDONLY); + if (fd < 0) + continue; + + /* created before UI_DEV_CREATE, or after it finished */ + if (fstat(fd, &st) == -1 || + st.st_ctime < uinput_dev->ctime[0] || + st.st_ctime > uinput_dev->ctime[1]) { + close(fd); + continue; + } + + len = read(fd, buf, sizeof(buf)); + close(fd); + if (len <= 0) + continue; + + buf[len - 1] = '\0'; /* file contains \n */ + if (strcmp(buf, uinput_dev->name) == 0) { + if (uinput_dev->syspath) { + /* FIXME: could descend into bit comparison here */ + log_info(NULL, "multiple identical devices found. syspath is unreliable\n"); + break; + } + + rc = snprintf(buf, sizeof(buf), "%s%s", + SYS_INPUT_DIR, + namelist[i]->d_name); + + if (rc < 0 || (size_t)rc >= sizeof(buf)) { + log_error(NULL, "Invalid syspath, syspath is unreliable\n"); + break; + } + + uinput_dev->syspath = strdup(buf); + uinput_dev->devnode = fetch_device_node(buf); + } + } + + for (i = 0; i < ndev; i++) + free(namelist[i]); + free(namelist); + + return uinput_dev->devnode ? 0 : -1; +#undef SYS_INPUT_DIR +} +#endif /* __FreeBSD__*/ + +static int +uinput_create_write(const struct libevdev *dev, int fd) +{ + int rc; + struct uinput_user_dev uidev; + + memset(&uidev, 0, sizeof(uidev)); + + strncpy(uidev.name, libevdev_get_name(dev), UINPUT_MAX_NAME_SIZE - 1); + uidev.id.vendor = libevdev_get_id_vendor(dev); + uidev.id.product = libevdev_get_id_product(dev); + uidev.id.bustype = libevdev_get_id_bustype(dev); + uidev.id.version = libevdev_get_id_version(dev); + + if (set_evbits(dev, fd, &uidev) != 0) + goto error; + if (set_props(dev, fd) != 0) + goto error; + + rc = write(fd, &uidev, sizeof(uidev)); + if (rc < 0) { + goto error; + } else if ((size_t)rc < sizeof(uidev)) { + errno = EINVAL; + goto error; + } + + errno = 0; + +error: + return -errno; +} + +static int +uinput_create_DEV_SETUP(const struct libevdev *dev, int fd, + struct libevdev_uinput *new_device) +{ + int rc; + struct uinput_setup setup; + + if (set_evbits(dev, fd, NULL) != 0) + goto error; + if (set_props(dev, fd) != 0) + goto error; + + memset(&setup, 0, sizeof(setup)); + strncpy(setup.name, libevdev_get_name(dev), UINPUT_MAX_NAME_SIZE - 1); + setup.id.vendor = libevdev_get_id_vendor(dev); + setup.id.product = libevdev_get_id_product(dev); + setup.id.bustype = libevdev_get_id_bustype(dev); + setup.id.version = libevdev_get_id_version(dev); + setup.ff_effects_max = libevdev_has_event_type(dev, EV_FF) ? 10 : 0; + + rc = ioctl(fd, UI_DEV_SETUP, &setup); + if (rc == 0) + errno = 0; +error: + return -errno; +} + +LIBEVDEV_EXPORT int +libevdev_uinput_create_from_device(const struct libevdev *dev, int fd, struct libevdev_uinput** uinput_dev) +{ + int rc; + struct libevdev_uinput *new_device; + int close_fd_on_error = (fd == LIBEVDEV_UINPUT_OPEN_MANAGED); + unsigned int uinput_version = 0; + + new_device = alloc_uinput_device(libevdev_get_name(dev)); + if (!new_device) + return -ENOMEM; + + if (fd == LIBEVDEV_UINPUT_OPEN_MANAGED) { + fd = open("/dev/uinput", O_RDWR|O_CLOEXEC); + if (fd < 0) + goto error; + + new_device->fd_is_managed = 1; + } else if (fd < 0) { + log_bug(NULL, "Invalid fd %d\n", fd); + errno = EBADF; + goto error; + } + + if (ioctl(fd, UI_GET_VERSION, &uinput_version) == 0 && + uinput_version >= 5) + rc = uinput_create_DEV_SETUP(dev, fd, new_device); + else + rc = uinput_create_write(dev, fd); + + if (rc != 0) + goto error; + + /* ctime notes time before/after ioctl to help us filter out devices + when traversing /sys/devices/virtual/input to find the device + node. + + this is in seconds, so ctime[0]/[1] will almost always be + identical but /sys doesn't give us sub-second ctime so... + */ + new_device->ctime[0] = time(NULL); + + rc = ioctl(fd, UI_DEV_CREATE, NULL); + if (rc == -1) + goto error; + + new_device->ctime[1] = time(NULL); + new_device->fd = fd; + + if (fetch_syspath_and_devnode(new_device) == -1) { + log_error(NULL, "unable to fetch syspath or device node.\n"); + errno = ENODEV; + goto error; + } + + *uinput_dev = new_device; + + return 0; + +error: + rc = -errno; + libevdev_uinput_destroy(new_device); + if (fd != -1 && close_fd_on_error) + close(fd); + return rc; +} + +LIBEVDEV_EXPORT void +libevdev_uinput_destroy(struct libevdev_uinput *uinput_dev) +{ + if (!uinput_dev) + return; + + if (uinput_dev->fd >= 0) { + (void)ioctl(uinput_dev->fd, UI_DEV_DESTROY, NULL); + if (uinput_dev->fd_is_managed) + close(uinput_dev->fd); + } + free(uinput_dev->syspath); + free(uinput_dev->devnode); + free(uinput_dev->name); + free(uinput_dev); +} + +LIBEVDEV_EXPORT const char* +libevdev_uinput_get_syspath(struct libevdev_uinput *uinput_dev) +{ + return uinput_dev->syspath; +} + +LIBEVDEV_EXPORT const char* +libevdev_uinput_get_devnode(struct libevdev_uinput *uinput_dev) +{ + return uinput_dev->devnode; +} + +LIBEVDEV_EXPORT int +libevdev_uinput_write_event(const struct libevdev_uinput *uinput_dev, + unsigned int type, + unsigned int code, + int value) +{ + struct input_event ev = { + .input_event_sec = 0, + .input_event_usec = 0, + .type = type, + .code = code, + .value = value + }; + int fd = libevdev_uinput_get_fd(uinput_dev); + int rc, max; + + if (type > EV_MAX) + return -EINVAL; + + max = libevdev_event_type_get_max(type); + if (max == -1 || code > (unsigned int)max) + return -EINVAL; + + rc = write(fd, &ev, sizeof(ev)); + + return rc < 0 ? -errno : 0; +} diff --git a/nativelib/src/main/cpp/libevdev/libevdev-uinput.h b/nativelib/src/main/cpp/libevdev/libevdev-uinput.h new file mode 100644 index 0000000000..b317fe7024 --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev-uinput.h @@ -0,0 +1,254 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef LIBEVDEV_UINPUT_H +#define LIBEVDEV_UINPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct libevdev_uinput; + +/** + * @defgroup uinput uinput device creation + * + * Creation of uinput devices based on existing libevdev devices. These functions + * help to create uinput devices that emulate libevdev devices. In the simplest + * form it serves to duplicate an existing device: + * + * @code + * int err; + * int fd, uifd; + * struct libevdev *dev; + * struct libevdev_uinput *uidev; + * + * fd = open("/dev/input/event0", O_RDONLY); + * if (fd < 0) + * return -errno; + * + * err = libevdev_new_from_fd(fd, &dev); + * if (err != 0) + * return err; + * + * uifd = open("/dev/uinput", O_RDWR); + * if (uifd < 0) + * return -errno; + * + * err = libevdev_uinput_create_from_device(dev, uifd, &uidev); + * if (err != 0) + * return err; + * + * // post a REL_X event + * err = libevdev_uinput_write_event(uidev, EV_REL, REL_X, -1); + * if (err != 0) + * return err; + * err = libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); + * if (err != 0) + * return err; + * + * libevdev_uinput_destroy(uidev); + * libevdev_free(dev); + * close(uifd); + * close(fd); + * + * @endcode + * + * Alternatively, a device can be constructed from scratch: + * + * @code + * int err; + * struct libevdev *dev; + * struct libevdev_uinput *uidev; + * + * dev = libevdev_new(); + * libevdev_set_name(dev, "test device"); + * libevdev_enable_event_type(dev, EV_REL); + * libevdev_enable_event_code(dev, EV_REL, REL_X, NULL); + * libevdev_enable_event_code(dev, EV_REL, REL_Y, NULL); + * libevdev_enable_event_type(dev, EV_KEY); + * libevdev_enable_event_code(dev, EV_KEY, BTN_LEFT, NULL); + * libevdev_enable_event_code(dev, EV_KEY, BTN_MIDDLE, NULL); + * libevdev_enable_event_code(dev, EV_KEY, BTN_RIGHT, NULL); + * + * err = libevdev_uinput_create_from_device(dev, + * LIBEVDEV_UINPUT_OPEN_MANAGED, + * &uidev); + * if (err != 0) + * return err; + * + * // ... do something ... + * + * libevdev_uinput_destroy(uidev); + * + * @endcode + */ + +enum libevdev_uinput_open_mode { + /* intentionally -2 to avoid code like below from accidentally working: + fd = open("/dev/uinput", O_RDWR); // fails, fd is -1 + libevdev_uinput_create_from_device(dev, fd, &uidev); // may hide the error */ + LIBEVDEV_UINPUT_OPEN_MANAGED = -2 /**< let libevdev open and close @c /dev/uinput */ +}; + +/** + * @ingroup uinput + * + * Create a uinput device based on the given libevdev device. The uinput device + * will be an exact copy of the libevdev device, minus the bits that uinput doesn't + * allow to be set. + * + * If uinput_fd is @ref LIBEVDEV_UINPUT_OPEN_MANAGED, libevdev_uinput_create_from_device() + * will open @c /dev/uinput in read/write mode and manage the file descriptor. + * Otherwise, uinput_fd must be opened by the caller and opened with the + * appropriate permissions. + * + * The device's lifetime is tied to the uinput file descriptor, closing it will + * destroy the uinput device. You should call libevdev_uinput_destroy() before + * closing the file descriptor to free allocated resources. + * A file descriptor can only create one uinput device at a time; the second device + * will fail with -EINVAL. + * + * You don't need to keep the file descriptor variable around, + * libevdev_uinput_get_fd() will return it when needed. + * + * @note Due to limitations in the uinput kernel module, REP_DELAY and + * REP_PERIOD will default to the kernel defaults, not to the ones set in the + * source device. + * + * @note On FreeBSD, if the UI_GET_SYSNAME ioctl() fails, there is no other way + * to get a device, and the function call will fail. + * + * @param dev The device to duplicate + * @param uinput_fd @ref LIBEVDEV_UINPUT_OPEN_MANAGED or a file descriptor to @c /dev/uinput, + * @param[out] uinput_dev The newly created libevdev device. + * + * @return 0 on success or a negative errno on failure. On failure, the value of + * uinput_dev is unmodified. + * + * @see libevdev_uinput_destroy + */ +int libevdev_uinput_create_from_device(const struct libevdev *dev, + int uinput_fd, + struct libevdev_uinput **uinput_dev); + +/** + * @ingroup uinput + * + * Destroy a previously created uinput device and free associated memory. + * + * If the device was opened with @ref LIBEVDEV_UINPUT_OPEN_MANAGED, + * libevdev_uinput_destroy() also closes the file descriptor. Otherwise, the + * fd is left as-is and must be closed by the caller. + * + * @param uinput_dev A previously created uinput device. + */ +void libevdev_uinput_destroy(struct libevdev_uinput *uinput_dev); + +/** + * @ingroup uinput + * + * Return the file descriptor used to create this uinput device. This is the + * fd pointing to /dev/uinput. This file descriptor may be used to write + * events that are emitted by the uinput device. + * Closing this file descriptor will destroy the uinput device, you should + * call libevdev_uinput_destroy() first to free allocated resources. + * + * @param uinput_dev A previously created uinput device. + * + * @return The file descriptor used to create this device + */ +int libevdev_uinput_get_fd(const struct libevdev_uinput *uinput_dev); + +/** + * @ingroup uinput + * + * Return the syspath representing this uinput device. If the UI_GET_SYSNAME + * ioctl is not available, libevdev makes an educated guess. + * The UI_GET_SYSNAME ioctl is available since Linux 3.15. + * + * The syspath returned is the one of the input node itself + * (e.g. /sys/devices/virtual/input/input123), not the syspath of the device + * node returned with libevdev_uinput_get_devnode(). + * + * @note This function may return NULL if UI_GET_SYSNAME is not available. + * In that case, libevdev uses ctime and the device name to guess devices. + * To avoid false positives, wait at least 1.5s between creating devices that + * have the same name. + * + * @note FreeBSD does not have sysfs, on FreeBSD this function always returns + * NULL. + * + * @param uinput_dev A previously created uinput device. + * @return The syspath for this device, including the preceding /sys + * + * @see libevdev_uinput_get_devnode + */ +const char* libevdev_uinput_get_syspath(struct libevdev_uinput *uinput_dev); + +/** + * @ingroup uinput + * + * Return the device node representing this uinput device. + * + * This relies on libevdev_uinput_get_syspath() to provide a valid syspath. + * See libevdev_uinput_get_syspath() for more details. + * + * @note This function may return NULL. libevdev may have to guess the + * syspath and the device node. See libevdev_uinput_get_syspath() for details. + * + * @note On FreeBSD, this function can not return NULL. libudev uses the + * UI_GET_SYSNAME ioctl to get the device node on this platform and if that + * fails, the call to libevdev_uinput_create_from_device() fails. + * + * @param uinput_dev A previously created uinput device. + * @return The device node for this device, in the form of /dev/input/eventN + * + * @see libevdev_uinput_get_syspath + */ +const char* libevdev_uinput_get_devnode(struct libevdev_uinput *uinput_dev); + +/** + * @ingroup uinput + * + * Post an event through the uinput device. It is the caller's responsibility + * that any event sequence is terminated with an EV_SYN/SYN_REPORT/0 event. + * Otherwise, listeners on the device node will not see the events until the + * next EV_SYN event is posted. + * + * @param uinput_dev A previously created uinput device. + * @param type Event type (EV_ABS, EV_REL, etc.) + * @param code Event code (ABS_X, REL_Y, etc.) + * @param value The event value + * @return 0 on success or a negative errno on error + */ +int libevdev_uinput_write_event(const struct libevdev_uinput *uinput_dev, + unsigned int type, + unsigned int code, + int value); +#ifdef __cplusplus +} +#endif + +#endif /* LIBEVDEV_UINPUT_H */ diff --git a/nativelib/src/main/cpp/libevdev/libevdev-util.h b/nativelib/src/main/cpp/libevdev/libevdev-util.h new file mode 100644 index 0000000000..f2ad9a2d43 --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev-util.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2013 Red Hat, Inc. + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#include +#include + +#define LONG_BITS (sizeof(long) * 8) +#define NLONGS(x) (((x) + LONG_BITS - 1) / LONG_BITS) +#define ARRAY_LENGTH(a) (sizeof(a) / (sizeof((a)[0]))) +#define unlikely(x) (__builtin_expect(!!(x),0)) + +#undef min +#undef max +#ifdef __GNUC__ +#define min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _b : _a; \ + }) +#define max(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; \ + }) +#else +#define min(a,b) ((a) > (b) ? (b) : (a)) +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +static inline bool +startswith(const char *str, size_t len, const char *prefix, size_t plen) +{ + return len >= plen && !strncmp(str, prefix, plen); +} + +static inline int +bit_is_set(const unsigned long *array, int bit) +{ + return !!(array[bit / LONG_BITS] & (1LL << (bit % LONG_BITS))); +} + +static inline void +set_bit(unsigned long *array, int bit) +{ + array[bit / LONG_BITS] |= (1LL << (bit % LONG_BITS)); +} + +static inline void +clear_bit(unsigned long *array, int bit) +{ + array[bit / LONG_BITS] &= ~(1LL << (bit % LONG_BITS)); +} + +static inline void +set_bit_state(unsigned long *array, int bit, int state) +{ + if (state) + set_bit(array, bit); + else + clear_bit(array, bit); +} + +#endif diff --git a/nativelib/src/main/cpp/libevdev/libevdev.c b/nativelib/src/main/cpp/libevdev/libevdev.c new file mode 100644 index 0000000000..743fc02a1d --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev.c @@ -0,0 +1,1882 @@ +// SPDX-License-Identifier: MIT +/* + * Copyright © 2013 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libevdev-int.h" +#include "libevdev-util.h" +#include "libevdev.h" + +#include "event-names.h" + +#define MAXEVENTS 64 + +enum event_filter_status { + EVENT_FILTER_NONE, /**< Event untouched by filters */ + EVENT_FILTER_MODIFIED, /**< Event was modified */ + EVENT_FILTER_DISCARD, /**< Discard current event */ +}; + +/* Keeps a record of touches during SYN_DROPPED */ +enum touch_state { + TOUCH_OFF, + TOUCH_STARTED, /* Started during SYN_DROPPED */ + TOUCH_STOPPED, /* Stopped during SYN_DROPPED */ + TOUCH_ONGOING, /* Existed before, still have same tracking ID */ + TOUCH_CHANGED, /* Existed before but have new tracking ID now, so + stopped + started in that slot */ +}; + +struct slot_change_state { + enum touch_state state; + unsigned long axes[NLONGS(ABS_CNT)]; /* bitmask for updated axes */ +}; + +static int sync_mt_state(struct libevdev *dev, + struct slot_change_state changes_out[dev->num_slots]); + +static int +update_key_state(struct libevdev *dev, const struct input_event *e); + +static inline int* +slot_value(const struct libevdev *dev, int slot, int axis) +{ + if (unlikely(slot > dev->num_slots)) { + log_bug(dev, "Slot %d exceeds number of slots (%d)\n", slot, dev->num_slots); + slot = 0; + } + if (unlikely(axis < ABS_MT_MIN || axis > ABS_MT_MAX)) { + log_bug(dev, "MT axis %d is outside the valid range [%d,%d]\n", + axis, ABS_MT_MIN, ABS_MT_MAX); + axis = ABS_MT_MIN; + } + return &dev->mt_slot_vals[slot * ABS_MT_CNT + axis - ABS_MT_MIN]; +} + +static int +init_event_queue(struct libevdev *dev) +{ + const int MIN_QUEUE_SIZE = 256; + int nevents = 1; /* terminating SYN_REPORT */ + int nslots; + unsigned int type, code; + + /* count the number of axes, keys, etc. to get a better idea at how + many events per EV_SYN we could possibly get. That's the max we + may get during SYN_DROPPED too. Use double that, just so we have + room for events while syncing a device. + */ + for (type = EV_KEY; type < EV_MAX; type++) { + int max = libevdev_event_type_get_max(type); + for (code = 0; max > 0 && code < (unsigned int) max; code++) { + if (libevdev_has_event_code(dev, type, code)) + nevents++; + } + } + + nslots = libevdev_get_num_slots(dev); + if (nslots > 1) { + int num_mt_axes = 0; + + for (code = ABS_MT_SLOT; code <= ABS_MAX; code++) { + if (libevdev_has_event_code(dev, EV_ABS, code)) + num_mt_axes++; + } + + /* We already counted the first slot in the initial count */ + nevents += num_mt_axes * (nslots - 1); + } + + return queue_alloc(dev, max(MIN_QUEUE_SIZE, nevents * 2)); +} + +static void +libevdev_dflt_log_func(enum libevdev_log_priority priority, + void *data, + const char *file, int line, const char *func, + const char *format, va_list args) +{ + const char *prefix; + switch(priority) { + case LIBEVDEV_LOG_ERROR: prefix = "libevdev error"; break; + case LIBEVDEV_LOG_INFO: prefix = "libevdev info"; break; + case LIBEVDEV_LOG_DEBUG: + prefix = "libevdev debug"; + break; + default: + prefix = "libevdev INVALID LOG PRIORITY"; + break; + } + /* default logging format: + libevev error in libevdev_some_func: blah blah + libevev info in libevdev_some_func: blah blah + libevev debug in file.c:123:libevdev_some_func: blah blah + */ + + fprintf(stderr, "%s in ", prefix); + if (priority == LIBEVDEV_LOG_DEBUG) + fprintf(stderr, "%s:%d:", file, line); + fprintf(stderr, "%s: ", func); + vfprintf(stderr, format, args); +} + +static void +fix_invalid_absinfo(const struct libevdev *dev, + int axis, + struct input_absinfo* abs_info) +{ + /* + * The reported absinfo for ABS_MT_TRACKING_ID is sometimes + * uninitialized for certain mtk-soc, due to init code mangling + * in the vendor kernel. + */ + if (axis == ABS_MT_TRACKING_ID && + abs_info->maximum == abs_info->minimum) { + abs_info->minimum = -1; + abs_info->maximum = 0xFFFF; + log_bug(dev, + "Device \"%s\" has invalid ABS_MT_TRACKING_ID range", + dev->name); + } +} + +/* + * Global logging settings. + */ +static struct logdata log_data = { + .priority = LIBEVDEV_LOG_INFO, + .global_handler = libevdev_dflt_log_func, + .userdata = NULL, +}; + +void +_libevdev_log_msg(const struct libevdev *dev, + enum libevdev_log_priority priority, + const char *file, int line, const char *func, + const char *format, ...) +{ + va_list args; + + if (dev && dev->log.device_handler) { + /** + * if both global handler and device handler are set + * we've set up the handlers wrong. And that means we'll + * likely get the printf args wrong and cause all sorts of + * mayhem. Seppuku is called for. + */ + if (unlikely(dev->log.global_handler)) + abort(); + + if (priority > dev->log.priority) + return; + } else if (!log_data.global_handler || priority > log_data.priority) { + return; + } else if (unlikely(log_data.device_handler)) { + abort(); /* Seppuku, see above */ + } + + va_start(args, format); + if (dev && dev->log.device_handler) + dev->log.device_handler(dev, priority, dev->log.userdata, file, line, func, format, args); + else + log_data.global_handler(priority, log_data.userdata, file, line, func, format, args); + va_end(args); +} + +static void +libevdev_reset(struct libevdev *dev) +{ + enum libevdev_log_priority pri = dev->log.priority; + libevdev_device_log_func_t handler = dev->log.device_handler; + + free(dev->name); + free(dev->phys); + free(dev->uniq); + free(dev->mt_slot_vals); + memset(dev, 0, sizeof(*dev)); + dev->fd = -1; + dev->initialized = false; + dev->num_slots = -1; + dev->current_slot = -1; + dev->grabbed = LIBEVDEV_UNGRAB; + dev->sync_state = SYNC_NONE; + dev->log.priority = pri; + dev->log.device_handler = handler; + libevdev_enable_event_type(dev, EV_SYN); +} + +LIBEVDEV_EXPORT struct libevdev* +libevdev_new(void) +{ + struct libevdev *dev; + + dev = calloc(1, sizeof(*dev)); + if (!dev) + return NULL; + + libevdev_reset(dev); + + return dev; +} + +LIBEVDEV_EXPORT int +libevdev_new_from_fd(int fd, struct libevdev **dev) +{ + struct libevdev *d; + int rc; + + d = libevdev_new(); + if (!d) + return -ENOMEM; + + rc = libevdev_set_fd(d, fd); + if (rc < 0) + libevdev_free(d); + else + *dev = d; + return rc; +} + +LIBEVDEV_EXPORT void +libevdev_free(struct libevdev *dev) +{ + if (!dev) + return; + + queue_free(dev); + libevdev_reset(dev); + free(dev); +} + +LIBEVDEV_EXPORT void +libevdev_set_log_function(libevdev_log_func_t logfunc, void *data) +{ + log_data.global_handler = logfunc; + log_data.userdata = data; +} + +LIBEVDEV_EXPORT void +libevdev_set_log_priority(enum libevdev_log_priority priority) +{ + if (priority > LIBEVDEV_LOG_DEBUG) + priority = LIBEVDEV_LOG_DEBUG; + log_data.priority = priority; +} + +LIBEVDEV_EXPORT enum libevdev_log_priority +libevdev_get_log_priority(void) +{ + return log_data.priority; +} + +LIBEVDEV_EXPORT void +libevdev_set_device_log_function(struct libevdev *dev, + libevdev_device_log_func_t logfunc, + enum libevdev_log_priority priority, + void *data) +{ + if (!dev) { + log_bug(NULL, "device must not be NULL\n"); + return; + } + + dev->log.priority = priority; + dev->log.device_handler = logfunc; + dev->log.userdata = data; +} + +enum libevdev_log_priority +_libevdev_log_priority(const struct libevdev *dev) +{ + if (dev && dev->log.device_handler) + return dev->log.priority; + return libevdev_get_log_priority(); +} + +LIBEVDEV_EXPORT int +libevdev_change_fd(struct libevdev *dev, int fd) +{ + if (!dev->initialized) { + log_bug(dev, "device not initialized. call libevdev_set_fd() first\n"); + return -1; + } + dev->fd = fd; + dev->grabbed = LIBEVDEV_UNGRAB; + return 0; +} + +static void +reset_tracking_ids(struct libevdev *dev) +{ + if (dev->num_slots == -1 || + !libevdev_has_event_code(dev, EV_ABS, ABS_MT_TRACKING_ID)) + return; + + for (int slot = 0; slot < dev->num_slots; slot++) + libevdev_set_slot_value(dev, slot, ABS_MT_TRACKING_ID, -1); +} + +static inline void +free_slots(struct libevdev *dev) +{ + dev->num_slots = -1; + free(dev->mt_slot_vals); + dev->mt_slot_vals = NULL; +} + +static int +init_slots(struct libevdev *dev) +{ + const struct input_absinfo *abs_info; + int rc = 0; + + free(dev->mt_slot_vals); + dev->mt_slot_vals = NULL; + + /* devices with ABS_RESERVED aren't MT devices, + see the documentation for multitouch-related + functions for more details */ + if (libevdev_has_event_code(dev, EV_ABS, ABS_RESERVED) || + !libevdev_has_event_code(dev, EV_ABS, ABS_MT_SLOT)) { + if (dev->num_slots != -1) { + free_slots(dev); + } + return rc; + } + + abs_info = libevdev_get_abs_info(dev, ABS_MT_SLOT); + + free_slots(dev); + dev->num_slots = abs_info->maximum + 1; + dev->mt_slot_vals = calloc(dev->num_slots * ABS_MT_CNT, sizeof(int)); + if (!dev->mt_slot_vals) { + rc = -ENOMEM; + goto out; + } + dev->current_slot = abs_info->value; + + reset_tracking_ids(dev); +out: + return rc; +} + +LIBEVDEV_EXPORT int +libevdev_set_fd(struct libevdev* dev, int fd) +{ + int rc; + int i; + char buf[256]; + + if (dev->initialized) { + log_bug(dev, "device already initialized.\n"); + return -EBADF; + } + + if (fd < 0) { + return -EBADF; + } + + libevdev_reset(dev); + + rc = ioctl(fd, EVIOCGBIT(0, sizeof(dev->bits)), dev->bits); + if (rc < 0) + goto out; + + memset(buf, 0, sizeof(buf)); + rc = ioctl(fd, EVIOCGNAME(sizeof(buf) - 1), buf); + if (rc < 0) + goto out; + + free(dev->name); + dev->name = strdup(buf); + if (!dev->name) { + errno = ENOMEM; + goto out; + } + + free(dev->phys); + dev->phys = NULL; + memset(buf, 0, sizeof(buf)); + rc = ioctl(fd, EVIOCGPHYS(sizeof(buf) - 1), buf); + if (rc < 0) { + /* uinput has no phys */ + if (errno != ENOENT) + goto out; + } else { + dev->phys = strdup(buf); + if (!dev->phys) { + errno = ENOMEM; + goto out; + } + } + + free(dev->uniq); + dev->uniq = NULL; + memset(buf, 0, sizeof(buf)); + rc = ioctl(fd, EVIOCGUNIQ(sizeof(buf) - 1), buf); + if (rc < 0) { + if (errno != ENOENT) + goto out; + } else { + dev->uniq = strdup(buf); + if (!dev->uniq) { + errno = ENOMEM; + goto out; + } + } + + rc = ioctl(fd, EVIOCGID, &dev->ids); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGVERSION, &dev->driver_version); + if (rc < 0) + goto out; + + /* Built on a kernel with props, running against a kernel without property + support. This should not be a fatal case, we'll be missing properties but other + than that everything is as expected. + */ + rc = ioctl(fd, EVIOCGPROP(sizeof(dev->props)), dev->props); + if (rc < 0 && errno != EINVAL) + goto out; + + rc = ioctl(fd, EVIOCGBIT(EV_REL, sizeof(dev->rel_bits)), dev->rel_bits); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(dev->abs_bits)), dev->abs_bits); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGBIT(EV_LED, sizeof(dev->led_bits)), dev->led_bits); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(dev->key_bits)), dev->key_bits); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGBIT(EV_SW, sizeof(dev->sw_bits)), dev->sw_bits); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGBIT(EV_MSC, sizeof(dev->msc_bits)), dev->msc_bits); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGBIT(EV_FF, sizeof(dev->ff_bits)), dev->ff_bits); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGBIT(EV_SND, sizeof(dev->snd_bits)), dev->snd_bits); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGKEY(sizeof(dev->key_values)), dev->key_values); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGLED(sizeof(dev->led_values)), dev->led_values); + if (rc < 0) + goto out; + + rc = ioctl(fd, EVIOCGSW(sizeof(dev->sw_values)), dev->sw_values); + if (rc < 0) + goto out; + + /* rep is a special case, always set it to 1 for both values if EV_REP is set */ + if (bit_is_set(dev->bits, EV_REP)) { + for (i = 0; i < REP_CNT; i++) + set_bit(dev->rep_bits, i); + rc = ioctl(fd, EVIOCGREP, dev->rep_values); + if (rc < 0) + goto out; + } + + for (i = ABS_X; i <= ABS_MAX; i++) { + if (bit_is_set(dev->abs_bits, i)) { + struct input_absinfo abs_info; + rc = ioctl(fd, EVIOCGABS(i), &abs_info); + if (rc < 0) + goto out; + + fix_invalid_absinfo(dev, i, &abs_info); + + dev->abs_info[i] = abs_info; + } + } + + dev->fd = fd; + + rc = init_slots(dev); + if (rc != 0) + goto out; + + if (dev->num_slots != -1) { + struct slot_change_state unused[dev->num_slots]; + sync_mt_state(dev, unused); + } + + rc = init_event_queue(dev); + if (rc < 0) { + dev->fd = -1; + return -rc; + } + + /* not copying key state because we won't know when we'll start to + * use this fd and key's are likely to change state by then. + * Same with the valuators, really, but they may not change. + */ + + dev->initialized = true; +out: + if (rc) + libevdev_reset(dev); + return rc ? -errno : 0; +} + +LIBEVDEV_EXPORT int +libevdev_get_fd(const struct libevdev* dev) +{ + return dev->fd; +} + +static int +sync_key_state(struct libevdev *dev) +{ + int rc; + int i; + unsigned long keystate[NLONGS(KEY_CNT)] = {0}; + + rc = ioctl(dev->fd, EVIOCGKEY(sizeof(keystate)), keystate); + if (rc < 0) + goto out; + + for (i = 0; i < KEY_CNT; i++) { + int old, new; + old = bit_is_set(dev->key_values, i); + new = bit_is_set(keystate, i); + if (old ^ new) + queue_push_event(dev, EV_KEY, i, new ? 1 : 0); + } + + memcpy(dev->key_values, keystate, rc); + + rc = 0; +out: + return rc ? -errno : 0; +} + +static int +sync_sw_state(struct libevdev *dev) +{ + int rc; + int i; + unsigned long swstate[NLONGS(SW_CNT)] = {0}; + + rc = ioctl(dev->fd, EVIOCGSW(sizeof(swstate)), swstate); + if (rc < 0) + goto out; + + for (i = 0; i < SW_CNT; i++) { + int old, new; + old = bit_is_set(dev->sw_values, i); + new = bit_is_set(swstate, i); + if (old ^ new) + queue_push_event(dev, EV_SW, i, new ? 1 : 0); + } + + memcpy(dev->sw_values, swstate, rc); + + rc = 0; +out: + return rc ? -errno : 0; +} + +static int +sync_led_state(struct libevdev *dev) +{ + int rc; + int i; + unsigned long ledstate[NLONGS(LED_CNT)] = {0}; + + rc = ioctl(dev->fd, EVIOCGLED(sizeof(ledstate)), ledstate); + if (rc < 0) + goto out; + + for (i = 0; i < LED_CNT; i++) { + int old, new; + old = bit_is_set(dev->led_values, i); + new = bit_is_set(ledstate, i); + if (old ^ new) { + queue_push_event(dev, EV_LED, i, new ? 1 : 0); + } + } + + memcpy(dev->led_values, ledstate, rc); + + rc = 0; +out: + return rc ? -errno : 0; +} +static int +sync_abs_state(struct libevdev *dev) +{ + int rc; + int i; + + for (i = ABS_X; i < ABS_CNT; i++) { + struct input_absinfo abs_info; + + if (i >= ABS_MT_MIN && i <= ABS_MT_MAX) + continue; + + if (!bit_is_set(dev->abs_bits, i)) + continue; + + rc = ioctl(dev->fd, EVIOCGABS(i), &abs_info); + if (rc < 0) + goto out; + + if (dev->abs_info[i].value != abs_info.value) { + queue_push_event(dev, EV_ABS, i, abs_info.value); + dev->abs_info[i].value = abs_info.value; + } + } + + rc = 0; +out: + return rc ? -errno : 0; +} + +static int +sync_mt_state(struct libevdev *dev, + struct slot_change_state changes_out[dev->num_slots]) +{ +#define MAX_SLOTS 256 + int rc = 0; + struct slot_change_state changes[MAX_SLOTS] = {0}; + unsigned int nslots = min(MAX_SLOTS, dev->num_slots); + + for (int axis = ABS_MT_MIN; axis <= ABS_MT_MAX; axis++) { + /* EVIOCGMTSLOTS required format */ + struct mt_sync_state { + uint32_t code; + int32_t val[MAX_SLOTS]; + } mt_state; + + if (axis == ABS_MT_SLOT || + !libevdev_has_event_code(dev, EV_ABS, axis)) + continue; + + mt_state.code = axis; + rc = ioctl(dev->fd, EVIOCGMTSLOTS(sizeof(mt_state)), &mt_state); + if (rc < 0) + goto out; + + for (unsigned int slot = 0; slot < nslots; slot++) { + int val_before = *slot_value(dev, slot, axis), + val_after = mt_state.val[slot]; + + if (axis == ABS_MT_TRACKING_ID) { + if (val_before == -1 && val_after != -1) { + changes[slot].state = TOUCH_STARTED; + } else if (val_before != -1 && val_after == -1) { + changes[slot].state = TOUCH_STOPPED; + } else if (val_before != -1 && val_after != -1 && + val_before == val_after) { + changes[slot].state = TOUCH_ONGOING; + } else if (val_before != -1 && val_after != -1 && + val_before != val_after) { + changes[slot].state = TOUCH_CHANGED; + } else { + changes[slot].state = TOUCH_OFF; + } + } + + if (val_before == val_after) + continue; + + *slot_value(dev, slot, axis) = val_after; + + set_bit(changes[slot].axes, axis); + /* note that this slot has updates */ + set_bit(changes[slot].axes, ABS_MT_SLOT); + } + } + + if (dev->num_slots > MAX_SLOTS) + memset(changes_out, 0, sizeof(*changes) * dev->num_slots); + + memcpy(changes_out, changes, sizeof(*changes) * nslots); +out: + return rc; +} + +static void +terminate_slots(struct libevdev *dev, + const struct slot_change_state changes[dev->num_slots], + int *last_reported_slot) +{ + const unsigned int map[] = {BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, + BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP, + BTN_TOOL_QUINTTAP}; + bool touches_stopped = false; + int ntouches_before = 0, ntouches_after = 0; + + /* For BTN_TOOL_* emulation, we need to know how many touches we had + * before and how many we have left once we terminate all the ones + * that changed and all the ones that stopped. + */ + for (int slot = 0; slot < dev->num_slots; slot++) { + switch(changes[slot].state) { + case TOUCH_OFF: + break; + case TOUCH_CHANGED: + case TOUCH_STOPPED: + queue_push_event(dev, EV_ABS, ABS_MT_SLOT, slot); + queue_push_event(dev, EV_ABS, ABS_MT_TRACKING_ID, -1); + + *last_reported_slot = slot; + touches_stopped = true; + ntouches_before++; + break; + case TOUCH_ONGOING: + ntouches_before++; + ntouches_after++; + break; + case TOUCH_STARTED: + break; + } + } + + /* If any of the touches stopped, we need to split the sync state + into two frames - one with all the stopped touches, one with the + new touches starting (if any) */ + if (touches_stopped) { + /* Send through the required BTN_TOOL_ 0 and 1 events for + * the previous and current number of fingers. And update + * our own key state accordingly, so that during the second + * sync event frame sync_key_state() sets everything correctly + * for the *real* number of touches. + */ + if (ntouches_before > 0 && ntouches_before <= 5) { + struct input_event ev = { + .type = EV_KEY, + .code = map[ntouches_before - 1], + .value = 0, + }; + queue_push_event(dev, ev.type, ev.code, ev.value); + update_key_state(dev, &ev); + } + + if (ntouches_after > 0 && ntouches_after <= 5) { + struct input_event ev = { + .type = EV_KEY, + .code = map[ntouches_after - 1], + .value = 1, + }; + queue_push_event(dev, ev.type, ev.code, ev.value); + update_key_state(dev, &ev); + } + + queue_push_event(dev, EV_SYN, SYN_REPORT, 0); + } +} + +static int +push_mt_sync_events(struct libevdev *dev, + const struct slot_change_state changes[dev->num_slots], + int last_reported_slot) +{ + struct input_absinfo abs_info; + int rc; + + for (int slot = 0; slot < dev->num_slots; slot++) { + bool have_slot_event = false; + + if (!bit_is_set(changes[slot].axes, ABS_MT_SLOT)) + continue; + + for (int axis = ABS_MT_MIN; axis <= ABS_MT_MAX; axis++) { + if (axis == ABS_MT_SLOT || + !libevdev_has_event_code(dev, EV_ABS, axis)) + continue; + + if (bit_is_set(changes[slot].axes, axis)) { + /* We already sent the tracking id -1 in + * terminate_slots so don't do that again. There + * may be other axes like ABS_MT_TOOL_TYPE that + * need to be synced despite no touch being active */ + if (axis == ABS_MT_TRACKING_ID && + *slot_value(dev, slot, axis) == -1) + continue; + + if (!have_slot_event) { + queue_push_event(dev, EV_ABS, ABS_MT_SLOT, slot); + last_reported_slot = slot; + have_slot_event = true; + } + + queue_push_event(dev, EV_ABS, axis, + *slot_value(dev, slot, axis)); + } + } + } + + /* add one last slot event to make sure the client is on the same + slot as the kernel */ + + rc = ioctl(dev->fd, EVIOCGABS(ABS_MT_SLOT), &abs_info); + if (rc < 0) + goto out; + + dev->current_slot = abs_info.value; + + if (dev->current_slot != last_reported_slot) + queue_push_event(dev, EV_ABS, ABS_MT_SLOT, dev->current_slot); + + rc = 0; +out: + return rc ? -errno : 0; +} + +static int +read_more_events(struct libevdev *dev) +{ + int free_elem; + int len; + struct input_event *next; + + free_elem = queue_num_free_elements(dev); + if (free_elem <= 0) + return 0; + + next = queue_next_element(dev); + len = read(dev->fd, next, free_elem * sizeof(struct input_event)); + if (len < 0) + return -errno; + + if (len > 0 && len % sizeof(struct input_event) != 0) + return -EINVAL; + + if (len > 0) { + int nev = len/sizeof(struct input_event); + queue_set_num_elements(dev, queue_num_elements(dev) + nev); + } + + return 0; +} + +static inline void +drain_events(struct libevdev *dev) +{ + int rc; + size_t nelem; + int iterations = 0; + const int max_iterations = 8; /* EVDEV_BUF_PACKETS in + kernel/drivers/input/evedev.c */ + + queue_shift_multiple(dev, queue_num_elements(dev), NULL); + + do { + rc = read_more_events(dev); + if (rc == -EAGAIN) + return; + + if (rc < 0) { + log_error(dev, "Failed to drain events before sync.\n"); + return; + } + + nelem = queue_num_elements(dev); + queue_shift_multiple(dev, nelem, NULL); + } while (iterations++ < max_iterations && nelem >= queue_size(dev)); + + /* Our buffer should be roughly the same or bigger than the kernel + buffer in most cases, so we usually don't expect to recurse. If + we do, make sure we stop after max_iterations and proceed with + what we have. This could happen if events queue up faster than + we can drain them. + */ + if (iterations >= max_iterations) + log_info(dev, "Unable to drain events, buffer size mismatch.\n"); +} + +static int +sync_state(struct libevdev *dev) +{ + int rc = 0; + bool want_mt_sync = false; + int last_reported_slot = 0; + struct slot_change_state changes[dev->num_slots > 0 ? dev->num_slots : 1]; + + memset(changes, 0, sizeof(changes)); + + /* see section "Discarding events before synchronizing" in + * libevdev/libevdev.h */ + drain_events(dev); + + /* We generate one or two event frames during sync. + * The first one (if it exists) terminates all slots that have + * either terminated during SYN_DROPPED or changed their tracking + * ID. + * + * The second frame syncs everything up to the current state of the + * device - including re-starting those slots that have a changed + * tracking id. + */ + if (dev->num_slots > -1 && + libevdev_has_event_code(dev, EV_ABS, ABS_MT_SLOT)) { + want_mt_sync = true; + rc = sync_mt_state(dev, changes); + if (rc == 0) + terminate_slots(dev, changes, &last_reported_slot); + else + want_mt_sync = false; + } + + if (libevdev_has_event_type(dev, EV_KEY)) + rc = sync_key_state(dev); + if (libevdev_has_event_type(dev, EV_LED)) + rc = sync_led_state(dev); + if (libevdev_has_event_type(dev, EV_SW)) + rc = sync_sw_state(dev); + if (rc == 0 && libevdev_has_event_type(dev, EV_ABS)) + rc = sync_abs_state(dev); + if (rc == 0 && want_mt_sync) + push_mt_sync_events(dev, changes, last_reported_slot); + + dev->queue_nsync = queue_num_elements(dev); + + if (dev->queue_nsync > 0) { + queue_push_event(dev, EV_SYN, SYN_REPORT, 0); + dev->queue_nsync++; + } + + return rc; +} + +static int +update_key_state(struct libevdev *dev, const struct input_event *e) +{ + if (!libevdev_has_event_type(dev, EV_KEY)) + return 1; + + if (e->code > KEY_MAX) + return 1; + + set_bit_state(dev->key_values, e->code, e->value != 0); + + return 0; +} + +static int +update_mt_state(struct libevdev *dev, const struct input_event *e) +{ + if (e->code == ABS_MT_SLOT && dev->num_slots > -1) { + int i; + dev->current_slot = e->value; + /* sync abs_info with the current slot values */ + for (i = ABS_MT_SLOT + 1; i <= ABS_MT_MAX; i++) { + if (libevdev_has_event_code(dev, EV_ABS, i)) + dev->abs_info[i].value = *slot_value(dev, dev->current_slot, i); + } + + return 0; + } + + if (dev->current_slot == -1) + return 1; + + *slot_value(dev, dev->current_slot, e->code) = e->value; + + return 0; +} + +static int +update_abs_state(struct libevdev *dev, const struct input_event *e) +{ + if (!libevdev_has_event_type(dev, EV_ABS)) + return 1; + + if (e->code > ABS_MAX) + return 1; + + if (e->code >= ABS_MT_MIN && e->code <= ABS_MT_MAX) + update_mt_state(dev, e); + + dev->abs_info[e->code].value = e->value; + + return 0; +} + +static int +update_led_state(struct libevdev *dev, const struct input_event *e) +{ + if (!libevdev_has_event_type(dev, EV_LED)) + return 1; + + if (e->code > LED_MAX) + return 1; + + set_bit_state(dev->led_values, e->code, e->value != 0); + + return 0; +} + +static int +update_sw_state(struct libevdev *dev, const struct input_event *e) +{ + if (!libevdev_has_event_type(dev, EV_SW)) + return 1; + + if (e->code > SW_MAX) + return 1; + + set_bit_state(dev->sw_values, e->code, e->value != 0); + + return 0; +} + +static int +update_state(struct libevdev *dev, const struct input_event *e) +{ + int rc = 0; + + switch(e->type) { + case EV_SYN: + case EV_REL: + break; + case EV_KEY: + rc = update_key_state(dev, e); + break; + case EV_ABS: + rc = update_abs_state(dev, e); + break; + case EV_LED: + rc = update_led_state(dev, e); + break; + case EV_SW: + rc = update_sw_state(dev, e); + break; + } + + dev->last_event_time.tv_sec = e->input_event_sec; + dev->last_event_time.tv_usec = e->input_event_usec; + + return rc; +} + +/** + * Sanitize/modify events where needed. + */ +static inline enum event_filter_status +sanitize_event(const struct libevdev *dev, + struct input_event *ev, + enum SyncState sync_state) +{ + if (!libevdev_has_event_code(dev, ev->type, ev->code)) + return EVENT_FILTER_DISCARD; + + if (unlikely(dev->num_slots > -1 && + libevdev_event_is_code(ev, EV_ABS, ABS_MT_SLOT) && + (ev->value < 0 || ev->value >= dev->num_slots))) { + log_bug(dev, "Device \"%s\" received an invalid slot index %d." + "Capping to announced max slot number %d.\n", + dev->name, ev->value, dev->num_slots - 1); + ev->value = dev->num_slots - 1; + return EVENT_FILTER_MODIFIED; + + /* Drop any invalid tracking IDs, they are only supposed to go from + N to -1 or from -1 to N. Never from -1 to -1, or N to M. Very + unlikely to ever happen from a real device. + */ + } + + if (unlikely(sync_state == SYNC_NONE && + dev->num_slots > -1 && + libevdev_event_is_code(ev, EV_ABS, ABS_MT_TRACKING_ID) && + ((ev->value == -1 && + *slot_value(dev, dev->current_slot, ABS_MT_TRACKING_ID) == -1) || + (ev->value != -1 && + *slot_value(dev, dev->current_slot, ABS_MT_TRACKING_ID) != -1)))) { + log_bug(dev, "Device \"%s\" received a double tracking ID %d in slot %d.\n", + dev->name, ev->value, dev->current_slot); + return EVENT_FILTER_DISCARD; + } + + return EVENT_FILTER_NONE; +} + +LIBEVDEV_EXPORT int +libevdev_next_event(struct libevdev *dev, unsigned int flags, struct input_event *ev) +{ + int rc = LIBEVDEV_READ_STATUS_SUCCESS; + enum event_filter_status filter_status; + const unsigned int valid_flags = LIBEVDEV_READ_FLAG_NORMAL | + LIBEVDEV_READ_FLAG_SYNC | + LIBEVDEV_READ_FLAG_FORCE_SYNC | + LIBEVDEV_READ_FLAG_BLOCKING; + + if (!dev->initialized) { + log_bug(dev, "device not initialized. call libevdev_set_fd() first\n"); + return -EBADF; + } + + if (dev->fd < 0) + return -EBADF; + + if ((flags & valid_flags) == 0) { + log_bug(dev, "invalid flags %#x.\n", flags); + return -EINVAL; + } + + if (flags & LIBEVDEV_READ_FLAG_SYNC) { + if (dev->sync_state == SYNC_NEEDED) { + rc = sync_state(dev); + if (rc != 0) + return rc; + dev->sync_state = SYNC_IN_PROGRESS; + } + + if (dev->queue_nsync == 0) { + dev->sync_state = SYNC_NONE; + return -EAGAIN; + } + + } else if (dev->sync_state != SYNC_NONE) { + struct input_event e; + + /* call update_state for all events here, otherwise the library has the wrong view + of the device too */ + while (queue_shift(dev, &e) == 0) { + dev->queue_nsync--; + if (sanitize_event(dev, &e, dev->sync_state) != EVENT_FILTER_DISCARD) + update_state(dev, &e); + } + + dev->sync_state = SYNC_NONE; + } + + /* Always read in some more events. Best case this smoothes over a potential SYN_DROPPED, + worst case we don't read fast enough and end up with SYN_DROPPED anyway. + + Except if the fd is in blocking mode and we still have events from the last read, don't + read in any more. + */ + do { + if (queue_num_elements(dev) == 0) { + rc = read_more_events(dev); + if (rc < 0 && rc != -EAGAIN) + goto out; + } + + if (flags & LIBEVDEV_READ_FLAG_FORCE_SYNC) { + dev->sync_state = SYNC_NEEDED; + rc = LIBEVDEV_READ_STATUS_SYNC; + goto out; + } + + if (queue_shift(dev, ev) != 0) + return -EAGAIN; + + filter_status = sanitize_event(dev, ev, dev->sync_state); + if (filter_status != EVENT_FILTER_DISCARD) + update_state(dev, ev); + + /* if we disabled a code, get the next event instead */ + } while(filter_status == EVENT_FILTER_DISCARD || + !libevdev_has_event_code(dev, ev->type, ev->code)); + + rc = LIBEVDEV_READ_STATUS_SUCCESS; + if (ev->type == EV_SYN && ev->code == SYN_DROPPED) { + dev->sync_state = SYNC_NEEDED; + rc = LIBEVDEV_READ_STATUS_SYNC; + } + + if (flags & LIBEVDEV_READ_FLAG_SYNC && dev->queue_nsync > 0) { + dev->queue_nsync--; + rc = LIBEVDEV_READ_STATUS_SYNC; + if (dev->queue_nsync == 0) + dev->sync_state = SYNC_NONE; + } + +out: + return rc; +} + +LIBEVDEV_EXPORT int +libevdev_has_event_pending(struct libevdev *dev) +{ + struct pollfd fds = { dev->fd, POLLIN, 0 }; + int rc; + + if (!dev->initialized) { + log_bug(dev, "device not initialized. call libevdev_set_fd() first\n"); + return -EBADF; + } + + if (dev->fd < 0) + return -EBADF; + + if (queue_num_elements(dev) != 0) + return 1; + + rc = poll(&fds, 1, 0); + return (rc >= 0) ? rc : -errno; +} + +LIBEVDEV_EXPORT const char * +libevdev_get_name(const struct libevdev *dev) +{ + return dev->name ? dev->name : ""; +} + +LIBEVDEV_EXPORT const char * +libevdev_get_phys(const struct libevdev *dev) +{ + return dev->phys; +} + +LIBEVDEV_EXPORT const char * +libevdev_get_uniq(const struct libevdev *dev) +{ + return dev->uniq; +} + +#define STRING_SETTER(field) \ +LIBEVDEV_EXPORT void libevdev_set_##field(struct libevdev *dev, const char *field) \ +{ \ + if (field == NULL) \ + return; \ + free(dev->field); \ + dev->field = strdup(field); \ +} + +STRING_SETTER(name) +STRING_SETTER(phys) +STRING_SETTER(uniq) + +#define PRODUCT_GETTER(name) \ +LIBEVDEV_EXPORT int libevdev_get_id_##name(const struct libevdev *dev) \ +{ \ + return dev->ids.name; \ +} + +PRODUCT_GETTER(product) +PRODUCT_GETTER(vendor) +PRODUCT_GETTER(bustype) +PRODUCT_GETTER(version) + +#define PRODUCT_SETTER(field) \ +LIBEVDEV_EXPORT void libevdev_set_id_##field(struct libevdev *dev, int field) \ +{ \ + dev->ids.field = field;\ +} + +PRODUCT_SETTER(product) +PRODUCT_SETTER(vendor) +PRODUCT_SETTER(bustype) +PRODUCT_SETTER(version) + +LIBEVDEV_EXPORT int +libevdev_get_driver_version(const struct libevdev *dev) +{ + return dev->driver_version; +} + +LIBEVDEV_EXPORT int +libevdev_has_property(const struct libevdev *dev, unsigned int prop) +{ + return (prop <= INPUT_PROP_MAX) && bit_is_set(dev->props, prop); +} + +LIBEVDEV_EXPORT int +libevdev_enable_property(struct libevdev *dev, unsigned int prop) +{ + if (prop > INPUT_PROP_MAX) + return -1; + + set_bit(dev->props, prop); + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_disable_property(struct libevdev *dev, unsigned int prop) +{ + if (prop > INPUT_PROP_MAX) + return -1; + + clear_bit(dev->props, prop); + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_has_event_type(const struct libevdev *dev, unsigned int type) +{ + return type == EV_SYN ||(type <= EV_MAX && bit_is_set(dev->bits, type)); +} + +LIBEVDEV_EXPORT int +libevdev_has_event_code(const struct libevdev *dev, unsigned int type, unsigned int code) +{ + const unsigned long *mask = NULL; + int max; + + if (!libevdev_has_event_type(dev, type)) + return 0; + + if (type == EV_SYN) + return 1; + + max = type_to_mask_const(dev, type, &mask); + + if (max == -1 || code > (unsigned int)max) + return 0; + + return bit_is_set(mask, code); +} + +LIBEVDEV_EXPORT int +libevdev_get_event_value(const struct libevdev *dev, unsigned int type, unsigned int code) +{ + int value = 0; + + if (!libevdev_has_event_type(dev, type) || !libevdev_has_event_code(dev, type, code)) + return 0; + + switch (type) { + case EV_ABS: value = dev->abs_info[code].value; break; + case EV_KEY: value = bit_is_set(dev->key_values, code); break; + case EV_LED: value = bit_is_set(dev->led_values, code); break; + case EV_SW: value = bit_is_set(dev->sw_values, code); break; + case EV_REP: + switch(code) { + case REP_DELAY: + libevdev_get_repeat(dev, &value, NULL); + break; + case REP_PERIOD: + libevdev_get_repeat(dev, NULL, &value); + break; + default: + value = 0; + break; + } + break; + default: + value = 0; + break; + } + + return value; +} + +LIBEVDEV_EXPORT int +libevdev_set_event_value(struct libevdev *dev, unsigned int type, unsigned int code, int value) +{ + int rc = 0; + struct input_event e; + + if (!libevdev_has_event_type(dev, type) || !libevdev_has_event_code(dev, type, code)) + return -1; + + e.type = type; + e.code = code; + e.value = value; + + if (sanitize_event(dev, &e, SYNC_NONE) != EVENT_FILTER_NONE) + return -1; + + switch(type) { + case EV_ABS: rc = update_abs_state(dev, &e); break; + case EV_KEY: rc = update_key_state(dev, &e); break; + case EV_LED: rc = update_led_state(dev, &e); break; + case EV_SW: rc = update_sw_state(dev, &e); break; + default: + rc = -1; + break; + } + + return rc; +} + +LIBEVDEV_EXPORT int +libevdev_fetch_event_value(const struct libevdev *dev, unsigned int type, unsigned int code, int *value) +{ + if (libevdev_has_event_type(dev, type) && + libevdev_has_event_code(dev, type, code)) { + *value = libevdev_get_event_value(dev, type, code); + return 1; + } + + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_get_slot_value(const struct libevdev *dev, unsigned int slot, unsigned int code) +{ + if (!libevdev_has_event_type(dev, EV_ABS) || !libevdev_has_event_code(dev, EV_ABS, code)) + return 0; + + if (dev->num_slots < 0 || slot >= (unsigned int)dev->num_slots) + return 0; + + if (code > ABS_MT_MAX || code < ABS_MT_MIN) + return 0; + + return *slot_value(dev, slot, code); +} + +LIBEVDEV_EXPORT int +libevdev_set_slot_value(struct libevdev *dev, unsigned int slot, unsigned int code, int value) +{ + if (!libevdev_has_event_type(dev, EV_ABS) || !libevdev_has_event_code(dev, EV_ABS, code)) + return -1; + + if (dev->num_slots == -1 || slot >= (unsigned int)dev->num_slots) + return -1; + + if (code > ABS_MT_MAX || code < ABS_MT_MIN) + return -1; + + if (code == ABS_MT_SLOT) { + if (value < 0 || value >= libevdev_get_num_slots(dev)) + return -1; + dev->current_slot = value; + } + + *slot_value(dev, slot, code) = value; + + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_fetch_slot_value(const struct libevdev *dev, unsigned int slot, unsigned int code, int *value) +{ + if (libevdev_has_event_type(dev, EV_ABS) && + libevdev_has_event_code(dev, EV_ABS, code) && + dev->num_slots >= 0 && + slot < (unsigned int)dev->num_slots) { + *value = libevdev_get_slot_value(dev, slot, code); + return 1; + } + + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_get_num_slots(const struct libevdev *dev) +{ + return dev->num_slots; +} + +LIBEVDEV_EXPORT int +libevdev_get_current_slot(const struct libevdev *dev) +{ + return dev->current_slot; +} + +LIBEVDEV_EXPORT const struct input_absinfo* +libevdev_get_abs_info(const struct libevdev *dev, unsigned int code) +{ + if (!libevdev_has_event_type(dev, EV_ABS) || + !libevdev_has_event_code(dev, EV_ABS, code)) + return NULL; + + return &dev->abs_info[code]; +} + +#define ABS_GETTER(name) \ +LIBEVDEV_EXPORT int libevdev_get_abs_##name(const struct libevdev *dev, unsigned int code) \ +{ \ + const struct input_absinfo *absinfo = libevdev_get_abs_info(dev, code); \ + return absinfo ? absinfo->name : 0; \ +} + +ABS_GETTER(maximum) +ABS_GETTER(minimum) +ABS_GETTER(fuzz) +ABS_GETTER(flat) +ABS_GETTER(resolution) + +#define ABS_SETTER(field) \ +LIBEVDEV_EXPORT void libevdev_set_abs_##field(struct libevdev *dev, unsigned int code, int val) \ +{ \ + if (!libevdev_has_event_code(dev, EV_ABS, code)) \ + return; \ + dev->abs_info[code].field = val; \ +} + +ABS_SETTER(maximum) +ABS_SETTER(minimum) +ABS_SETTER(fuzz) +ABS_SETTER(flat) +ABS_SETTER(resolution) + +LIBEVDEV_EXPORT void +libevdev_set_abs_info(struct libevdev *dev, unsigned int code, const struct input_absinfo *abs) +{ + if (!libevdev_has_event_code(dev, EV_ABS, code)) + return; + + dev->abs_info[code] = *abs; +} + +LIBEVDEV_EXPORT int +libevdev_enable_event_type(struct libevdev *dev, unsigned int type) +{ + int max; + + if (type > EV_MAX) + return -1; + + if (libevdev_has_event_type(dev, type)) + return 0; + + max = libevdev_event_type_get_max(type); + if (max == -1) + return -1; + + set_bit(dev->bits, type); + + if (type == EV_REP) { + int delay = 0, period = 0; + libevdev_enable_event_code(dev, EV_REP, REP_DELAY, &delay); + libevdev_enable_event_code(dev, EV_REP, REP_PERIOD, &period); + } + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_disable_event_type(struct libevdev *dev, unsigned int type) +{ + int max; + + if (type > EV_MAX || type == EV_SYN) + return -1; + + max = libevdev_event_type_get_max(type); + if (max == -1) + return -1; + + clear_bit(dev->bits, type); + + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_enable_event_code(struct libevdev *dev, unsigned int type, + unsigned int code, const void *data) +{ + unsigned int max; + unsigned long *mask = NULL; + + if (libevdev_enable_event_type(dev, type)) + return -1; + + switch(type) { + case EV_SYN: + return 0; + case EV_ABS: + case EV_REP: + if (data == NULL) + return -1; + break; + default: + if (data != NULL) + return -1; + break; + } + + max = type_to_mask(dev, type, &mask); + + if (code > max || (int)max == -1) + return -1; + + set_bit(mask, code); + + if (type == EV_ABS) { + const struct input_absinfo *abs = data; + dev->abs_info[code] = *abs; + if (code == ABS_MT_SLOT) { + if (init_slots(dev) != 0) + return -1; + } else if (code == ABS_MT_TRACKING_ID) { + reset_tracking_ids(dev); + } + } else if (type == EV_REP) { + const int *value = data; + dev->rep_values[code] = *value; + } + + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_disable_event_code(struct libevdev *dev, unsigned int type, unsigned int code) +{ + unsigned int max; + unsigned long *mask = NULL; + + if (type > EV_MAX || type == EV_SYN) + return -1; + + max = type_to_mask(dev, type, &mask); + + if (code > max || (int)max == -1) + return -1; + + clear_bit(mask, code); + + if (type == EV_ABS) { + if (code == ABS_MT_SLOT) { + if (init_slots(dev) != 0) + return -1; + } else if (code == ABS_MT_TRACKING_ID) { + reset_tracking_ids(dev); + } + } + + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_kernel_set_abs_info(struct libevdev *dev, unsigned int code, const struct input_absinfo *abs) +{ + int rc; + + if (!dev->initialized) { + log_bug(dev, "device not initialized. call libevdev_set_fd() first\n"); + return -EBADF; + } + + if (dev->fd < 0) + return -EBADF; + + if (code > ABS_MAX) + return -EINVAL; + + rc = ioctl(dev->fd, EVIOCSABS(code), abs); + if (rc < 0) + rc = -errno; + else + rc = libevdev_enable_event_code(dev, EV_ABS, code, abs); + + return rc; +} + +LIBEVDEV_EXPORT int +libevdev_grab(struct libevdev *dev, enum libevdev_grab_mode grab) +{ + int rc = 0; + + if (!dev->initialized) { + log_bug(dev, "device not initialized. call libevdev_set_fd() first\n"); + return -EBADF; + } + + if (dev->fd < 0) + return -EBADF; + + if (grab != LIBEVDEV_GRAB && grab != LIBEVDEV_UNGRAB) { + log_bug(dev, "invalid grab parameter %#x\n", grab); + return -EINVAL; + } + + if (grab == dev->grabbed) + return 0; + + if (grab == LIBEVDEV_GRAB) + rc = ioctl(dev->fd, EVIOCGRAB, (void *)1); + else if (grab == LIBEVDEV_UNGRAB) + rc = ioctl(dev->fd, EVIOCGRAB, (void *)0); + + if (rc == 0) + dev->grabbed = grab; + + return rc < 0 ? -errno : 0; +} + +LIBEVDEV_EXPORT int +libevdev_event_is_type(const struct input_event *ev, unsigned int type) +{ + return type < EV_CNT && ev->type == type; +} + +LIBEVDEV_EXPORT int +libevdev_event_is_code(const struct input_event *ev, unsigned int type, unsigned int code) +{ + int max; + + if (!libevdev_event_is_type(ev, type)) + return 0; + + max = libevdev_event_type_get_max(type); + return (max > -1 && code <= (unsigned int)max && ev->code == code); +} + +LIBEVDEV_EXPORT const char* +libevdev_event_type_get_name(unsigned int type) +{ + if (type > EV_MAX) + return NULL; + + return ev_map[type]; +} + +LIBEVDEV_EXPORT const char* +libevdev_event_code_get_name(unsigned int type, unsigned int code) +{ + int max = libevdev_event_type_get_max(type); + + if (max == -1 || code > (unsigned int)max) + return NULL; + + return event_type_map[type][code]; +} + +LIBEVDEV_EXPORT const char * +libevdev_event_value_get_name(unsigned int type, + unsigned int code, + int value) +{ + /* This is a simplified version because nothing else + is an enum like ABS_MT_TOOL_TYPE so we don't need + a generic lookup */ + if (type != EV_ABS || code != ABS_MT_TOOL_TYPE) + return NULL; + + if (value < 0 || value > MT_TOOL_MAX) + return NULL; + + return mt_tool_map[value]; +} + +LIBEVDEV_EXPORT const char* +libevdev_property_get_name(unsigned int prop) +{ + if (prop > INPUT_PROP_MAX) + return NULL; + + return input_prop_map[prop]; +} + +LIBEVDEV_EXPORT int +libevdev_event_type_get_max(unsigned int type) +{ + if (type > EV_MAX) + return -1; + + return ev_max[type]; +} + +LIBEVDEV_EXPORT int +libevdev_get_repeat(const struct libevdev *dev, int *delay, int *period) +{ + if (!libevdev_has_event_type(dev, EV_REP)) + return -1; + + if (delay != NULL) + *delay = dev->rep_values[REP_DELAY]; + if (period != NULL) + *period = dev->rep_values[REP_PERIOD]; + + return 0; +} + +LIBEVDEV_EXPORT int +libevdev_kernel_set_led_value(struct libevdev *dev, unsigned int code, enum libevdev_led_value value) +{ + return libevdev_kernel_set_led_values(dev, code, value, -1); +} + +LIBEVDEV_EXPORT int +libevdev_kernel_set_led_values(struct libevdev *dev, ...) +{ + struct input_event ev[LED_MAX + 1]; + enum libevdev_led_value val; + va_list args; + int code; + int rc = 0; + size_t nleds = 0; + + if (!dev->initialized) { + log_bug(dev, "device not initialized. call libevdev_set_fd() first\n"); + return -EBADF; + } + + if (dev->fd < 0) + return -EBADF; + + memset(ev, 0, sizeof(ev)); + + va_start(args, dev); + code = va_arg(args, unsigned int); + while (code != -1) { + if (code > LED_MAX) { + rc = -EINVAL; + break; + } + val = va_arg(args, enum libevdev_led_value); + if (val != LIBEVDEV_LED_ON && val != LIBEVDEV_LED_OFF) { + rc = -EINVAL; + break; + } + + if (libevdev_has_event_code(dev, EV_LED, code)) { + struct input_event *e = ev; + + while (e->type > 0 && e->code != code) + e++; + + if (e->type == 0) + nleds++; + e->type = EV_LED; + e->code = code; + e->value = (val == LIBEVDEV_LED_ON); + } + code = va_arg(args, unsigned int); + } + va_end(args); + + if (rc == 0 && nleds > 0) { + ev[nleds].type = EV_SYN; + ev[nleds++].code = SYN_REPORT; + + rc = write(libevdev_get_fd(dev), ev, nleds * sizeof(ev[0])); + if (rc > 0) { + nleds--; /* last is EV_SYN */ + while (nleds--) + update_led_state(dev, &ev[nleds]); + } + rc = (rc != -1) ? 0 : -errno; + } + + return rc; +} + +LIBEVDEV_EXPORT int +libevdev_set_clock_id(struct libevdev *dev, int clockid) +{ + if (!dev->initialized) { + log_bug(dev, "device not initialized. call libevdev_set_fd() first\n"); + return -EBADF; + } + + if (dev->fd < 0) + return -EBADF; + + return ioctl(dev->fd, EVIOCSCLOCKID, &clockid) ? -errno : 0; +} diff --git a/nativelib/src/main/cpp/libevdev/libevdev.h b/nativelib/src/main/cpp/libevdev/libevdev.h new file mode 100644 index 0000000000..142dac2198 --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev.h @@ -0,0 +1,2379 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright © 2013 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef LIBEVDEV_H +#define LIBEVDEV_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define LIBEVDEV_ATTRIBUTE_PRINTF(_format, _args) __attribute__ ((format (printf, _format, _args))) + +/** + * @mainpage + * + * **libevdev** is a library for handling evdev kernel devices. It abstracts + * the \ref ioctls through type-safe interfaces and provides functions to change + * the appearance of the device. + * + * Development + * =========== + * The git repository is available here: + * + * - https://gitlab.freedesktop.org/libevdev/libevdev + * + * Development of libevdev is discussed on + * [input-tools@lists.freedesktop.org](http://lists.freedesktop.org/mailman/listinfo/input-tools). + * Please submit patches, questions or general comments there. + * + * Handling events and SYN_DROPPED + * =============================== + * + * libevdev provides an interface for handling events, including most notably + * `SYN_DROPPED` events. `SYN_DROPPED` events are sent by the kernel when the + * process does not read events fast enough and the kernel is forced to drop + * some events. This causes the device to get out of sync with the process' + * view of it. libevdev handles this by telling the caller that a * `SYN_DROPPED` + * has been received and that the state of the device is different to what is + * to be expected. It then provides the delta between the previous state and + * the actual state of the device as a set of events. See + * libevdev_next_event() and @ref syn_dropped for more information on how + * `SYN_DROPPED` is handled. + * + * Signal safety + * ============= + * + * libevdev is signal-safe for the majority of its operations, i.e. many of + * its functions are safe to be called from within a signal handler. + * Check the API documentation to make sure, unless explicitly stated a call + * is not signal safe. + * + * Device handling + * =============== + * + * A libevdev context is valid for a given file descriptor and its + * duration. Closing the file descriptor will not destroy the libevdev device + * but libevdev will not be able to read further events. + * + * libevdev does not attempt duplicate detection. Initializing two libevdev + * devices for the same fd is valid and behaves the same as for two different + * devices. + * + * libevdev does not handle the file descriptors directly, it merely uses + * them. The caller is responsible for opening the file descriptors, setting + * them to `O_NONBLOCK` and handling permissions. A caller should drain any + * events pending on the file descriptor before passing it to libevdev. + * + * Where does libevdev sit? + * ======================== + * + * libevdev is essentially a `read(2)` on steroids for `/dev/input/eventX` + * devices. It sits below the process that handles input events, in between + * the kernel and that process. In the simplest case, e.g. an evtest-like tool + * the stack would look like this: + * + * kernel → libevdev → evtest + * + * For X.Org input modules, the stack would look like this: + * + * kernel → libevdev → xf86-input-evdev → X server → X client + * + * For anything using libinput (e.g. most Wayland compositors), the stack + * the stack would look like this: + * + * kernel → libevdev → libinput → Compositor → Wayland client + * + * libevdev does **not** have knowledge of X clients or Wayland clients, it is + * too low in the stack. + * + * Example + * ======= + * Below is a simple example that shows how libevdev could be used. This example + * opens a device, checks for relative axes and a left mouse button and if it + * finds them monitors the device to print the event. + * + * @code + * struct libevdev *dev = NULL; + * int fd; + * int rc = 1; + * + * fd = open("/dev/input/event0", O_RDONLY|O_NONBLOCK); + * rc = libevdev_new_from_fd(fd, &dev); + * if (rc < 0) { + * fprintf(stderr, "Failed to init libevdev (%s)\n", strerror(-rc)); + * exit(1); + * } + * printf("Input device name: \"%s\"\n", libevdev_get_name(dev)); + * printf("Input device ID: bus %#x vendor %#x product %#x\n", + * libevdev_get_id_bustype(dev), + * libevdev_get_id_vendor(dev), + * libevdev_get_id_product(dev)); + * if (!libevdev_has_event_type(dev, EV_REL) || + * !libevdev_has_event_code(dev, EV_KEY, BTN_LEFT)) { + * printf("This device does not look like a mouse\n"); + * exit(1); + * } + * + * do { + * struct input_event ev; + * rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + * if (rc == 0) + * printf("Event: %s %s %d\n", + * libevdev_event_type_get_name(ev.type), + * libevdev_event_code_get_name(ev.type, ev.code), + * ev.value); + * } while (rc == 1 || rc == 0 || rc == -EAGAIN); + * @endcode + * + * A more complete example is available with the libevdev-events tool here: + * https://gitlab.freedesktop.org/libevdev/libevdev/blob/master/tools/libevdev-events.c + * + * Backwards compatibility with older kernel + * ========================================= + * libevdev attempts to build and run correctly on a number of kernel versions. + * If features required are not available, libevdev attempts to work around them + * in the most reasonable way. For more details see \ref backwardscompatibility. + * + * License information + * =================== + * libevdev is licensed under the + * [MIT license](http://cgit.freedesktop.org/libevdev/tree/COPYING). + * + * Bindings + * =================== + * - Python: https://gitlab.freedesktop.org/libevdev/python-libevdev + * - Haskell: http://hackage.haskell.org/package/evdev + * - Rust: https://crates.io/crates/evdev-rs + * + * Reporting bugs + * ============== + * Please report bugs in the freedesktop.org GitLab instance: + * https://gitlab.freedesktop.org/libevdev/libevdev/issues/ + */ + +/** + * @page syn_dropped SYN_DROPPED handling + * + * This page describes how libevdev handles `SYN_DROPPED` events. + * + * Receiving `SYN_DROPPED` events + * ============================== + * + * The kernel sends evdev events separated by an event of type `EV_SYN` and + * code `SYN_REPORT`. Such an event marks the end of a frame of hardware + * events. The number of events between `SYN_REPORT` events is arbitrary and + * depends on the hardware. An example event sequence may look like this: + * @code + * EV_ABS ABS_X 9 + * EV_ABS ABS_Y 8 + * EV_SYN SYN_REPORT 0 + * ------------------------ + * EV_ABS ABS_X 10 + * EV_ABS ABS_Y 10 + * EV_KEY BTN_TOUCH 1 + * EV_SYN SYN_REPORT 0 + * ------------------------ + * EV_ABS ABS_X 11 + * EV_SYN SYN_REPORT 0 + * @endcode + * + * Events are handed to the client buffer as they appear, the kernel adjusts + * the buffer size to handle at least one full event. In the normal case, + * the client reads the event and the kernel can place the next event in the + * buffer. If the client is not fast enough, the kernel places an event of + * type `EV_SYN` and code `SYN_DROPPED` into the buffer, effectively notifying + * the client that some events were lost. The above example event sequence + * may look like this (note the missing/repeated events): + * @code + * EV_ABS ABS_X 9 + * EV_ABS ABS_Y 8 + * EV_SYN SYN_REPORT 0 + * ------------------------ + * EV_ABS ABS_X 10 + * EV_ABS ABS_Y 10 + * EV_SYN SYN_DROPPED 0 + * EV_ABS ABS_Y 15 + * EV_SYN SYN_REPORT 0 + * ------------------------ + * EV_ABS ABS_X 11 + * EV_KEY BTN_TOUCH 0 + * EV_SYN SYN_REPORT 0 + * @endcode + * + * A `SYN_DROPPED` event may be recieved at any time in the event sequence. + * When a `SYN_DROPPED` event is received, the client must: + * * discard all events since the last `SYN_REPORT` + * * discard all events until including the next `SYN_REPORT` + * These event are part of incomplete event frames. + * + * Synchronizing the state of the device + * ===================================== + * + * The handling of the device after a `SYN_DROPPED` depends on the available + * event codes. For all event codes of type `EV_REL`, no handling is + * necessary, there is no state attached. For all event codes of type + * `EV_KEY`, `EV_SW`, `EV_LED` and `EV_SND`, the matching @ref ioctls retrieve the + * current state. The caller must then compare the last-known state to the + * retrieved state and handle the deltas accordingly. + * libevdev simplifies this approach: if the state of the device has + * changed, libevdev generates an event for each code with the new value and + * passes it to the caller during libevdev_next_event() if + * @ref LIBEVDEV_READ_FLAG_SYNC is set. + * + * For events of type `EV_ABS` and an event code less than `ABS_MT_SLOT`, the + * handling of state changes is as described above. For events between + * `ABS_MT_SLOT` and `ABS_MAX`, the event handling differs. + * Slots are the vehicles to transport information for multiple simultaneous + * touchpoints on a device. Slots are re-used once a touchpoint has ended. + * The kernel sends an `ABS_MT_SLOT` event whenever the current slot + * changes; any event in the above axis range applies only to the currently + * active slot. + * Thus, an event sequence from a slot-capable device may look like this: + * @code + * EV_ABS ABS_MT_POSITION_Y 10 + * EV_ABS ABS_MT_SLOT 1 + * EV_ABS ABS_MT_POSITION_X 100 + * EV_ABS ABS_MT_POSITION_Y 80 + * EV_SYN SYN_REPORT 0 + * @endcode + * Note the lack of `ABS_MT_SLOT`: the first `ABS_MT_POSITION_Y` applies to + * a slot opened previously, and is the only axis that changed for that + * slot. The touchpoint in slot 1 now has position `100/80`. + * The kernel does not provide events if a value does not change, and does + * not send `ABS_MT_SLOT` events if the slot does not change, or none of the + * values within a slot changes. A client must thus keep the state for each + * slot. + * + * If a `SYN_DROPPED` is received, the client must sync all slots + * individually and update its internal state. libevdev simplifies this by + * generating multiple events: + * * for each slot on the device, libevdev generates an + * `ABS_MT_SLOT` event with the value set to the slot number + * * for each event code between `ABS_MT_SLOT + 1` and `ABS_MAX` that changed + * state for this slot, libevdev generates an event for the new state + * * libevdev sends a final `ABS_MT_SLOT` event for the current slot as + * seen by the kernel + * * libevdev terminates this sequence with an `EV_SYN SYN_REPORT` event + * + * An example event sequence for such a sync may look like this: + * @code + * EV_ABS ABS_MT_SLOT 0 + * EV_ABS ABS_MT_POSITION_Y 10 + * EV_ABS ABS_MT_SLOT 1 + * EV_ABS ABS_MT_POSITION_X 100 + * EV_ABS ABS_MT_POSITION_Y 80 + * EV_ABS ABS_MT_SLOT 2 + * EV_ABS ABS_MT_POSITION_Y 8 + * EV_ABS ABS_MT_PRESSURE 12 + * EV_ABS ABS_MT_SLOT 1 + * EV_SYN SYN_REPORT 0 + * @endcode + * Note the terminating `ABS_MT_SLOT` event, this indicates that the kernel + * currently has slot 1 active. + * + * Synchronizing ABS_MT_TRACKING_ID + * ================================ + * + * The event code `ABS_MT_TRACKING_ID` is used to denote the start and end of + * a touch point within a slot. An `ABS_MT_TRACKING_ID` of zero or greater + * denotes the start of a touchpoint, an `ABS_MT_TRACKING_ID` of -1 denotes + * the end of a touchpoint within this slot. During `SYN_DROPPED`, a touch + * point may have ended and re-started within a slot - a client must check + * the `ABS_MT_TRACKING_ID`. libevdev simplifies this by emulating extra + * events if the `ABS_MT_TRACKING_ID` has changed: + * * if the `ABS_MT_TRACKING_ID` was valid and is -1, libevdev enqueues an + * `ABS_MT_TRACKING_ID` event with value -1. + * * if the `ABS_MT_TRACKING_ID` was -1 and is now a valid ID, libevdev + * enqueues an `ABS_MT_TRACKING_ID` event with the current value. + * * if the `ABS_MT_TRACKING_ID` was a valid ID and is now a different valid + * ID, libevev enqueues an `ABS_MT_TRACKING_ID` event with value -1 and + * another `ABS_MT_TRACKING_ID` event with the new value. + * + * An example event sequence for such a sync may look like this: + * @code + * EV_ABS ABS_MT_SLOT 0 + * EV_ABS ABS_MT_TRACKING_ID -1 + * EV_ABS ABS_MT_SLOT 2 + * EV_ABS ABS_MT_TRACKING_ID -1 + * EV_SYN SYN_REPORT 0 + * ------------------------ + * EV_ABS ABS_MT_SLOT 1 + * EV_ABS ABS_MT_POSITION_X 100 + * EV_ABS ABS_MT_POSITION_Y 80 + * EV_ABS ABS_MT_SLOT 2 + * EV_ABS ABS_MT_TRACKING_ID 45 + * EV_ABS ABS_MT_POSITION_Y 8 + * EV_ABS ABS_MT_PRESSURE 12 + * EV_ABS ABS_MT_SLOT 1 + * EV_SYN SYN_REPORT 0 + * @endcode + * Note how the touchpoint in slot 0 was terminated, the touchpoint in slot + * 2 was terminated and then started with a new `ABS_MT_TRACKING_ID`. The touchpoint + * in slot 1 maintained the same `ABS_MT_TRACKING_ID` and only updated the + * coordinates. Slot 1 is the currently active slot. + * + * In the case of a `SYN_DROPPED` event, a touch point may be invisible to a + * client if it started after `SYN_DROPPED` and finished before the client + * handles events again. The below example shows an example event sequence + * and what libevdev sees in the case of a `SYN_DROPPED` event: + * @code + * + * kernel | userspace + * | + * EV_ABS ABS_MT_SLOT 0 | EV_ABS ABS_MT_SLOT 0 + * EV_ABS ABS_MT_TRACKING_ID -1 | EV_ABS ABS_MT_TRACKING_ID -1 + * EV_SYN SYN_REPORT 0 | EV_SYN SYN_REPORT 0 + * ------------------------ | ------------------------ + * EV_ABS ABS_MT_TRACKING_ID 30 | + * EV_ABS ABS_MT_POSITION_X 100 | + * EV_ABS ABS_MT_POSITION_Y 80 | + * EV_SYN SYN_REPORT 0 | SYN_DROPPED + * ------------------------ | + * EV_ABS ABS_MT_TRACKING_ID -1 | + * EV_SYN SYN_REPORT 0 | + * ------------------------ | ------------------------ + * EV_ABS ABS_MT_SLOT 1 | EV_ABS ABS_MT_SLOT 1 + * EV_ABS ABS_MT_POSITION_X 90 | EV_ABS ABS_MT_POSITION_X 90 + * EV_ABS ABS_MT_POSITION_Y 10 | EV_ABS ABS_MT_POSITION_Y 10 + * EV_SYN SYN_REPORT 0 | EV_SYN SYN_REPORT 0 + * @endcode + * If such an event sequence occurs, libevdev will send all updated axes + * during the sync process. Axis events may thus be generated for devices + * without a currently valid `ABS_MT_TRACKING_ID`. Specifically for the above + * example, the client would receive the following event sequence: + * @code + * EV_ABS ABS_MT_SLOT 0 ← LIBEVDEV_READ_FLAG_NORMAL + * EV_ABS ABS_MT_TRACKING_ID -1 + * EV_SYN SYN_REPORT 0 + * ------------------------ + * EV_SYN SYN_DROPPED 0 → LIBEVDEV_READ_STATUS_SYNC + * ------------------------ + * EV_ABS ABS_MT_POSITION_X 100 ← LIBEVDEV_READ_FLAG_SYNC + * EV_ABS ABS_MT_POSITION_Y 80 + * EV_SYN SYN_REPORT 0 + * ----------------------------- → -EGAIN + * EV_ABS ABS_MT_SLOT 1 ← LIBEVDEV_READ_FLAG_NORMAL + * EV_ABS ABS_MT_POSITION_X 90 + * EV_ABS ABS_MT_POSITION_Y 10 + * EV_SYN SYN_REPORT 0 + * ------------------- + * @endcode + * The axis events do not reflect the position of a current touch point, a + * client must take care not to generate a new touch point based on those + * updates. + * + * Discarding events before synchronizing + * ===================================== + * + * The kernel implements the client buffer as a ring buffer. `SYN_DROPPED` + * events are handled when the buffer is full and a new event is received + * from a device. All existing events are discarded, a `SYN_DROPPED` is added + * to the buffer followed by the actual device event. Further events will be + * appended to the buffer until it is either read by the client, or filled + * again, at which point the sequence repeats. + * + * When the client reads the buffer, the buffer will thus always consist of + * exactly one `SYN_DROPPED` event followed by an unspecified number of real + * events. The data the ioctls return is the current state of the device, + * i.e. the state after all these events have been processed. For example, + * assume the buffer contains the following sequence: + * + * @code + * EV_SYN SYN_DROPPED + * EV_ABS ABS_X 1 + * EV_SYN SYN_REPORT 0 + * EV_ABS ABS_X 2 + * EV_SYN SYN_REPORT 0 + * EV_ABS ABS_X 3 + * EV_SYN SYN_REPORT 0 + * EV_ABS ABS_X 4 + * EV_SYN SYN_REPORT 0 + * EV_ABS ABS_X 5 + * EV_SYN SYN_REPORT 0 + * EV_ABS ABS_X 6 + * EV_SYN SYN_REPORT 0 + * @endcode + * An ioctl at any time in this sequence will return a value of 6 for ABS_X. + * + * libevdev discards all events after a `SYN_DROPPED` to ensure the events + * during @ref LIBEVDEV_READ_FLAG_SYNC represent the last known state of the + * device. This loses some granularity of the events especially as the time + * between the `SYN_DROPPED` and the sync process increases. It does however + * avoid spurious cursor movements. In the above example, the event sequence + * by libevdev is: + * @code + * EV_SYN SYN_DROPPED + * EV_ABS ABS_X 6 + * EV_SYN SYN_REPORT 0 + * @endcode + */ + +/** + * @page backwardscompatibility Compatibility and Behavior across kernel versions + * + * This page describes libevdev's behavior when the build-time kernel and the + * run-time kernel differ in their feature set. + * + * With the exception of event names, libevdev defines features that may be + * missing on older kernels and building on such kernels will not disable + * features. Running libevdev on a kernel that is missing some feature will + * change libevdev's behavior. In most cases, the new behavior should be + * obvious, but it is spelled out below in detail. + * + * Minimum requirements + * ==================== + * libevdev requires a 2.6.36 kernel as minimum. Specifically, it requires + * kernel-support for `ABS_MT_SLOT`. + * + * Event and input property names + * ============================== + * Event names and input property names are defined at build-time by the + * linux/input.h shipped with libevdev. + * The list of event names is compiled at build-time, any events not defined + * at build time will not resolve. Specifically, + * libevdev_event_code_get_name() for an undefined type or code will + * always return `NULL`. Likewise, libevdev_property_get_name() will return NULL + * for properties undefined at build-time. + * + * Input properties + * ================ + * If the kernel does not support input properties, specifically the + * `EVIOCGPROPS` ioctl, libevdev does not expose input properties to the caller. + * Specifically, libevdev_has_property() will always return 0 unless the + * property has been manually set with libevdev_enable_property(). + * + * This also applies to the libevdev-uinput code. If uinput does not honor + * `UI_SET_PROPBIT`, libevdev will continue without setting the properties on + * the device. + * + * MT slot behavior + * ================= + * If the kernel does not support the `EVIOCGMTSLOTS` ioctl, libevdev + * assumes all values in all slots are 0 and continues without an error. + * + * SYN_DROPPED behavior + * ==================== + * A kernel without `SYN_DROPPED` won't send such an event. libevdev_next_event() + * will never require the switch to sync mode. + */ + +/** + * @page ioctls evdev ioctls + * + * This page lists the status of the evdev-specific ioctls in libevdev. + * + *
+ *
EVIOCGVERSION:
+ *
supported, see libevdev_get_driver_version()
+ *
EVIOCGID:
+ *
supported, see libevdev_get_id_product(), libevdev_get_id_vendor(), + * libevdev_get_id_bustype(), libevdev_get_id_version()
+ *
EVIOCGREP:
+ *
supported, see libevdev_get_event_value())
+ *
EVIOCSREP:
+ *
supported, see libevdev_enable_event_code()
+ *
EVIOCGKEYCODE:
+ *
currently not supported
+ *
EVIOCSKEYCODE:
+ *
currently not supported
+ *
EVIOCGKEYCODE_V2:
+ *
currently not supported
+ *
EVIOCSKEYCODE_V2:
+ *
currently not supported
+ *
EVIOCGNAME:
+ *
supported, see libevdev_get_name()
+ *
EVIOCGPHYS:
+ *
supported, see libevdev_get_phys()
+ *
EVIOCGUNIQ:
+ *
supported, see libevdev_get_uniq()
+ *
EVIOCGPROP:
+ *
supported, see libevdev_has_property()
+ *
EVIOCGMTSLOTS:
+ *
supported, see libevdev_get_num_slots(), libevdev_get_slot_value()
+ *
EVIOCGKEY:
+ *
supported, see libevdev_has_event_code(), libevdev_get_event_value()
+ *
EVIOCGLED:
+ *
supported, see libevdev_has_event_code(), libevdev_get_event_value()
+ *
EVIOCGSND:
+ *
currently not supported
+ *
EVIOCGSW:
+ *
supported, see libevdev_has_event_code(), libevdev_get_event_value()
+ *
EVIOCGBIT:
+ *
supported, see libevdev_has_event_code(), libevdev_get_event_value()
+ *
EVIOCGABS:
+ *
supported, see libevdev_has_event_code(), libevdev_get_event_value(), + * libevdev_get_abs_info()
+ *
EVIOCSABS:
+ *
supported, see libevdev_kernel_set_abs_info()
+ *
EVIOCSFF:
+ *
currently not supported
+ *
EVIOCRMFF:
+ *
currently not supported
+ *
EVIOCGEFFECTS:
+ *
currently not supported
+ *
EVIOCGRAB:
+ *
supported, see libevdev_grab()
+ *
EVIOCSCLOCKID:
+ *
supported, see libevdev_set_clock_id()
+ *
EVIOCREVOKE:
+ *
currently not supported, see + * http://lists.freedesktop.org/archives/input-tools/2014-January/000688.html
+ *
EVIOCGMASK:
+ *
currently not supported
+ *
EVIOCSMASK:
+ *
currently not supported
+ *
+ * + */ + +/** + * @page kernel_header Kernel header + * + * libevdev provides its own copy of the Linux kernel header file and + * compiles against the definitions define here. Event type and event code + * names, etc. are taken from the file below: + * @include linux/input.h + */ + +/** + * @page static_linking Statically linking libevdev + * + * Statically linking libevdev.a is not recommended. Symbol visibility is + * difficult to control in a static library, so extra care must be taken to + * only use symbols that are explicitly exported. libevdev's API stability + * guarantee only applies to those symbols. + * + * If you do link libevdev statically, note that in addition to the exported + * symbols, libevdev reserves the _libevdev_* namespace. Do not use + * or create symbols with that prefix, they are subject to change at any + * time. + */ + +/** + * @page testing libevdev-internal test suite + * + * libevdev's internal test suite uses the + * [Check unit testing framework](http://check.sourceforge.net/). Tests are + * divided into test suites and test cases. Most tests create a uinput device, + * so you'll need to run as root, and your kernel must have + * `CONFIG_INPUT_UINPUT` enabled. + * + * To run a specific suite only: + * + * export CK_RUN_SUITE="suite name" + * + * To run a specific test case only: + * + * export CK_RUN_TEST="test case name" + * + * To get a list of all suites or tests: + * + * git grep "suite_create" + * git grep "tcase_create" + * + * By default, Check forks, making debugging harder. The test suite tries to detect + * if it is running inside gdb and disable forking. If that doesn't work for + * some reason, run gdb as below to avoid forking. + * + * sudo CK_FORK=no CK_RUN_TEST="test case name" gdb ./test/test-libevdev + * + * A special target `make gcov-report.txt` exists that runs gcov and leaves a + * `libevdev.c.gcov` file. Check that for test coverage. + * + * `make check` is hooked up to run the test and gcov (again, needs root). + * + * The test suite creates a lot of devices, very quickly. Add the following + * xorg.conf.d snippet to avoid the devices being added as X devices (at the + * time of writing, mutter can't handle these devices and exits after getting + * a BadDevice error). + * + * $ cat /etc/X11/xorg.conf.d/99-ignore-libevdev-devices.conf + * Section "InputClass" + * Identifier "Ignore libevdev test devices" + * MatchProduct "libevdev test device" + * Option "Ignore" "on" + * EndSection + * + */ + +/** + * @defgroup init Initialization and setup + * + * Initialization, initial setup and file descriptor handling. + * These functions are the main entry points for users of libevdev, usually a + * caller will use this series of calls: + * + * @code + * struct libevdev *dev; + * int err; + * + * dev = libevdev_new(); + * if (!dev) + * return ENOMEM; + * + * err = libevdev_set_fd(dev, fd); + * if (err < 0) + * printf("Failed (errno %d): %s\n", -err, strerror(-err)); + * + * libevdev_free(dev); + * @endcode + * + * libevdev_set_fd() is the central call and initializes the internal structs + * for the device at the given fd. libevdev functions will fail if called + * before libevdev_set_fd() unless documented otherwise. + */ + +/** + * @defgroup logging Library logging facilities + * + * libevdev provides two methods of logging library-internal messages. The + * old method is to provide a global log handler in + * libevdev_set_log_function(). The new method is to provide a per-context + * log handler in libevdev_set_device_log_function(). Developers are encouraged + * to use the per-context logging facilities over the global log handler as + * it provides access to the libevdev instance that caused a message, and is + * more flexible when libevdev is used from within a shared library. + * + * If a caller sets both the global log handler and a per-context log + * handler, each device with a per-context log handler will only invoke that + * log handler. + * + * @note To set a context-specific log handler, a context is needed. + * Thus developers are discouraged from using libevdev_new_from_fd() as + * important messages from the device initialization process may get lost. + * + * @note A context-specific handler cannot be used for libevdev's uinput + * devices. @ref uinput must use the global log handler. + */ + +/** + * @defgroup bits Querying device capabilities + * + * Abstraction functions to handle device capabilities, specifically + * device properties such as the name of the device and the bits + * representing the events supported by this device. + * + * The logical state returned may lag behind the physical state of the device. + * libevdev queries the device state on libevdev_set_fd() and then relies on + * the caller to parse events through libevdev_next_event(). If a caller does not + * use libevdev_next_event(), libevdev will not update the internal state of the + * device and thus returns outdated values. + */ + +/** + * @defgroup mt Multi-touch related functions + * Functions for querying multi-touch-related capabilities. MT devices + * following the kernel protocol B (using `ABS_MT_SLOT`) provide multiple touch + * points through so-called slots on the same axis. The slots are enumerated, + * a client reading from the device will first get an ABS_MT_SLOT event, then + * the values of axes changed in this slot. Multiple slots may be provided in + * before an `EV_SYN` event. + * + * As with @ref bits, the logical state of the device as seen by the library + * depends on the caller using libevdev_next_event(). + * + * The Linux kernel requires all axes on a device to have a semantic + * meaning, matching the axis names in linux/input.h. Some devices merely + * export a number of axes beyond the available axis list. For those + * devices, the multitouch information is invalid. Specifically, if a device + * provides the `ABS_MT_SLOT` axis AND also the `ABS_RESERVED` axis, the + * device is not treated as multitouch device. No slot information is + * available and the `ABS_MT` axis range for these devices is treated as all + * other `EV_ABS` axes. + * + * Note that because of limitations in the kernel API, such fake multitouch + * devices can not be reliably synced after a `SYN_DROPPED` event. libevdev + * ignores all `ABS_MT` axis values during the sync process and instead + * relies on the device to send the current axis value with the first event + * after `SYN_DROPPED`. + */ + +/** + * @defgroup kernel Modifying the appearance or capabilities of the device + * + * Modifying the set of events reported by this device. By default, the + * libevdev device mirrors the kernel device, enabling only those bits + * exported by the kernel. This set of functions enable or disable bits as + * seen from the caller. + * + * Enabling an event type or code does not affect event reporting - a + * software-enabled event will not be generated by the physical hardware. + * Disabling an event will prevent libevdev from routing such events to the + * caller. Enabling and disabling event types and codes is at the library + * level and thus only affects the caller. + * + * If an event type or code is enabled at kernel-level, future users of this + * device will see this event enabled. Currently there is no option of + * disabling an event type or code at kernel-level. + */ + +/** + * @defgroup misc Miscellaneous helper functions + * + * Functions for printing or querying event ranges. The list of names is + * compiled into libevdev and is independent of the run-time kernel. + * Likewise, the max for each event type is compiled in and does not check + * the kernel at run-time. + */ + +/** + * @defgroup events Event handling + * + * Functions to handle events and fetch the current state of the event. + * libevdev updates its internal state as the event is processed and forwarded + * to the caller. Thus, the libevdev state of the device should always be identical + * to the caller's state. It may however lag behind the actual state of the device. + */ + +/** + * @ingroup init + * + * Opaque struct representing an evdev device. + */ +struct libevdev; + +/** + * @ingroup events + */ +enum libevdev_read_flag { + LIBEVDEV_READ_FLAG_SYNC = 1, /**< Process data in sync mode */ + LIBEVDEV_READ_FLAG_NORMAL = 2, /**< Process data in normal mode */ + LIBEVDEV_READ_FLAG_FORCE_SYNC = 4, /**< Pretend the next event is a SYN_DROPPED and + require the caller to sync */ + LIBEVDEV_READ_FLAG_BLOCKING = 8 /**< The fd is not in O_NONBLOCK and a read may block */ +}; + +/** + * @ingroup init + * + * Initialize a new libevdev device. This function only allocates the + * required memory and initializes the struct to sane default values. + * To actually hook up the device to a kernel device, use + * libevdev_set_fd(). + * + * Memory allocated through libevdev_new() must be released by the + * caller with libevdev_free(). + * + * @see libevdev_set_fd + * @see libevdev_free + */ +struct libevdev* libevdev_new(void); + +/** + * @ingroup init + * + * Initialize a new libevdev device from the given fd. + * + * This is a shortcut for + * + * @code + * int err; + * struct libevdev *dev = libevdev_new(); + * err = libevdev_set_fd(dev, fd); + * @endcode + * + * @param fd A file descriptor to the device in O_RDWR or O_RDONLY mode. + * @param[out] dev The newly initialized evdev device. + * + * @return On success, 0 is returned and dev is set to the newly + * allocated struct. On failure, a negative errno is returned and the value + * of dev is undefined. + * + * @see libevdev_free + */ +int libevdev_new_from_fd(int fd, struct libevdev **dev); + +/** + * @ingroup init + * + * Clean up and free the libevdev struct. After completion, the struct + * libevdev is invalid and must not be used. + * + * Note that calling libevdev_free() does not close the file descriptor + * currently associated with this instance. + * + * @param dev The evdev device + * + * @note This function may be called before libevdev_set_fd(). + */ +void libevdev_free(struct libevdev *dev); + +/** + * @ingroup logging + */ +enum libevdev_log_priority { + LIBEVDEV_LOG_ERROR = 10, /**< critical errors and application bugs */ + LIBEVDEV_LOG_INFO = 20, /**< informational messages */ + LIBEVDEV_LOG_DEBUG = 30 /**< debug information */ +}; + +/** + * @ingroup logging + * + * Logging function called by library-internal logging. + * This function is expected to treat its input like printf would. + * + * @param priority Log priority of this message + * @param data User-supplied data pointer (see libevdev_set_log_function()) + * @param file libevdev source code file generating this message + * @param line libevdev source code line generating this message + * @param func libevdev source code function generating this message + * @param format printf-style format string + * @param args List of arguments + * + * @see libevdev_set_log_function + */ +typedef void (*libevdev_log_func_t)(enum libevdev_log_priority priority, + void *data, + const char *file, int line, + const char *func, + const char *format, va_list args) + LIBEVDEV_ATTRIBUTE_PRINTF(6, 0); + +/** + * @ingroup logging + * + * Set a printf-style logging handler for library-internal logging. The default + * logging function is to stdout. + * + * @note The global log handler is only called if no context-specific log + * handler has been set with libevdev_set_device_log_function(). + * + * @param logfunc The logging function for this device. If NULL, the current + * logging function is unset and no logging is performed. + * @param data User-specific data passed to the log handler. + * + * @note This function may be called before libevdev_set_fd(). + * + * @deprecated Use per-context logging instead, see + * libevdev_set_device_log_function(). + */ +void libevdev_set_log_function(libevdev_log_func_t logfunc, void *data); + +/** + * @ingroup logging + * + * Define the minimum level to be printed to the log handler. + * Messages higher than this level are printed, others are discarded. This + * is a global setting and applies to any future logging messages. + * + * @param priority Minimum priority to be printed to the log. + * + * @deprecated Use per-context logging instead, see + * libevdev_set_device_log_function(). + */ +void libevdev_set_log_priority(enum libevdev_log_priority priority); + +/** + * @ingroup logging + * + * Return the current log priority level. Messages higher than this level + * are printed, others are discarded. This is a global setting. + * + * @return the current log level + * + * @deprecated Use per-context logging instead, see + * libevdev_set_device_log_function(). + */ +enum libevdev_log_priority libevdev_get_log_priority(void); + +/** + * @ingroup logging + * + * Logging function called by library-internal logging for a specific + * libevdev context. This function is expected to treat its input like + * printf would. + * + * @param dev The evdev device + * @param priority Log priority of this message + * @param data User-supplied data pointer (see libevdev_set_log_function()) + * @param file libevdev source code file generating this message + * @param line libevdev source code line generating this message + * @param func libevdev source code function generating this message + * @param format printf-style format string + * @param args List of arguments + * + * @see libevdev_set_log_function + * @since 1.3 + */ +typedef void (*libevdev_device_log_func_t)(const struct libevdev *dev, + enum libevdev_log_priority priority, + void *data, + const char *file, int line, + const char *func, + const char *format, va_list args) + LIBEVDEV_ATTRIBUTE_PRINTF(7, 0); + +/** + * @ingroup logging + * + * Set a printf-style logging handler for library-internal logging for this + * device context. The default logging function is NULL, i.e. the global log + * handler is invoked. If a context-specific log handler is set, the global + * log handler is not invoked for this device. + * + * @note This log function applies for this device context only, even if + * another context exists for the same fd. + * + * @param dev The evdev device + * @param logfunc The logging function for this device. If NULL, the current + * logging function is unset and logging falls back to the global log + * handler, if any. + * @param priority Minimum priority to be printed to the log. + * @param data User-specific data passed to the log handler. + * + * @note This function may be called before libevdev_set_fd(). + * @since 1.3 + */ +void libevdev_set_device_log_function(struct libevdev *dev, + libevdev_device_log_func_t logfunc, + enum libevdev_log_priority priority, + void *data); + +/** + * @ingroup init + */ +enum libevdev_grab_mode { + LIBEVDEV_GRAB = 3, /**< Grab the device if not currently grabbed */ + LIBEVDEV_UNGRAB = 4 /**< Ungrab the device if currently grabbed */ +}; + +/** + * @ingroup init + * + * Grab or ungrab the device through a kernel EVIOCGRAB. This prevents other + * clients (including kernel-internal ones such as rfkill) from receiving + * events from this device. + * + * This is generally a bad idea. Don't do this. + * + * Grabbing an already grabbed device, or ungrabbing an ungrabbed device is + * a noop and always succeeds. + * + * A grab is an operation tied to a file descriptor, not a device. If a + * client changes the file descriptor with libevdev_change_fd(), it must + * also re-issue a grab with libevdev_grab(). + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param grab If true, grab the device. Otherwise ungrab the device. + * + * @return 0 if the device was successfully grabbed or ungrabbed, or a + * negative errno in case of failure. + */ +int libevdev_grab(struct libevdev *dev, enum libevdev_grab_mode grab); + +/** + * @ingroup init + * + * Set the fd for this struct and initialize internal data. + * The fd must be in O_RDONLY or O_RDWR mode. + * + * This function may only be called once per device. If the device changed and + * you need to re-read a device, use libevdev_free() and libevdev_new(). If + * you need to change the fd after closing and re-opening the same device, use + * libevdev_change_fd(). + * + * A caller should ensure that any events currently pending on the fd are + * drained before the file descriptor is passed to libevdev for + * initialization. Due to how the kernel's ioctl handling works, the initial + * device state will reflect the current device state *after* applying all + * events currently pending on the fd. Thus, if the fd is not drained, the + * state visible to the caller will be inconsistent with the events + * immediately available on the device. This does not affect state-less + * events like EV_REL. + * + * Unless otherwise specified, libevdev function behavior is undefined until + * a successful call to libevdev_set_fd(). + * + * @param dev The evdev device + * @param fd The file descriptor for the device + * + * @return 0 on success, or a negative errno on failure + * + * @see libevdev_change_fd + * @see libevdev_new + * @see libevdev_free + */ +int libevdev_set_fd(struct libevdev* dev, int fd); + +/** + * @ingroup init + * + * Change the fd for this device, without re-reading the actual device. If the fd + * changes after initializing the device, for example after a VT-switch in the + * X.org X server, this function updates the internal fd to the newly opened. + * No check is made that new fd points to the same device. If the device has + * changed, libevdev's behavior is undefined. + * + * libevdev does not sync itself after changing the fd and keeps the current + * device state. Use libevdev_next_event with the + * @ref LIBEVDEV_READ_FLAG_FORCE_SYNC flag to force a re-sync. + * + * The example code below illustrates how to force a re-sync of the + * library-internal state. Note that this code doesn't handle the events in + * the caller, it merely forces an update of the internal library state. + * @code + * struct input_event ev; + * libevdev_change_fd(dev, new_fd); + * libevdev_next_event(dev, LIBEVDEV_READ_FLAG_FORCE_SYNC, &ev); + * while (libevdev_next_event(dev, LIBEVDEV_READ_FLAG_SYNC, &ev) == LIBEVDEV_READ_STATUS_SYNC) + * ; // noop + * @endcode + * + * The fd may be open in O_RDONLY or O_RDWR. + * + * After changing the fd, the device is assumed ungrabbed and a caller must + * call libevdev_grab() again. + * + * It is an error to call this function before calling libevdev_set_fd(). + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param fd The new fd + * + * @return 0 on success, or -1 on failure. + * + * @see libevdev_set_fd + */ +int libevdev_change_fd(struct libevdev* dev, int fd); + +/** + * @ingroup init + * + * @param dev The evdev device + * + * @return The previously set fd, or -1 if none had been set previously. + * @note This function may be called before libevdev_set_fd(). + */ +int libevdev_get_fd(const struct libevdev* dev); + +/** + * @ingroup events + */ +enum libevdev_read_status { + /** + * libevdev_next_event() has finished without an error + * and an event is available for processing. + * + * @see libevdev_next_event + */ + LIBEVDEV_READ_STATUS_SUCCESS = 0, + /** + * Depending on the libevdev_next_event() read flag: + * * libevdev received a SYN_DROPPED from the device, and the caller should + * now resync the device, or, + * * an event has been read in sync mode. + * + * @see libevdev_next_event + */ + LIBEVDEV_READ_STATUS_SYNC = 1 +}; + +/** + * @ingroup events + * + * Get the next event from the device. This function operates in two different + * modes: normal mode or sync mode. + * + * In normal mode (when flags has @ref LIBEVDEV_READ_FLAG_NORMAL set), this + * function returns @ref LIBEVDEV_READ_STATUS_SUCCESS and returns the event + * in the argument @p ev. If no events are available at this + * time, it returns -EAGAIN and ev is undefined. + * + * If the current event is an EV_SYN SYN_DROPPED event, this function returns + * @ref LIBEVDEV_READ_STATUS_SYNC and ev is set to the EV_SYN event. + * The caller should now call this function with the + * @ref LIBEVDEV_READ_FLAG_SYNC flag set, to get the set of events that make up the + * device state delta. This function returns @ref LIBEVDEV_READ_STATUS_SYNC for + * each event part of that delta, until it returns -EAGAIN once all events + * have been synced. For more details on what libevdev does to sync after a + * SYN_DROPPED event, see @ref syn_dropped. + * + * If a device needs to be synced by the caller but the caller does not call + * with the @ref LIBEVDEV_READ_FLAG_SYNC flag set, all events from the diff are + * dropped after libevdev updates its internal state and event processing + * continues as normal. Note that the current slot and the state of touch + * points may have updated during the SYN_DROPPED event, it is strongly + * recommended that a caller ignoring all sync events calls + * libevdev_get_current_slot() and checks the ABS_MT_TRACKING_ID values for + * all slots. + * + * If a device has changed state without events being enqueued in libevdev, + * e.g. after changing the file descriptor, use the @ref + * LIBEVDEV_READ_FLAG_FORCE_SYNC flag. This triggers an internal sync of the + * device and libevdev_next_event() returns @ref LIBEVDEV_READ_STATUS_SYNC. + * Any state changes are available as events as described above. If + * @ref LIBEVDEV_READ_FLAG_FORCE_SYNC is set, the value of ev is undefined. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param flags Set of flags to determine behaviour. If @ref LIBEVDEV_READ_FLAG_NORMAL + * is set, the next event is read in normal mode. If @ref LIBEVDEV_READ_FLAG_SYNC is + * set, the next event is read in sync mode. + * @param ev On success, set to the current event. + * @return On failure, a negative errno is returned. + * @retval LIBEVDEV_READ_STATUS_SUCCESS One or more events were read of the + * device and ev points to the next event in the queue + * @retval -EAGAIN No events are currently available on the device + * @retval LIBEVDEV_READ_STATUS_SYNC A SYN_DROPPED event was received, or a + * synced event was returned and ev points to the SYN_DROPPED event + * + * @note This function is signal-safe. + */ +int libevdev_next_event(struct libevdev *dev, unsigned int flags, struct input_event *ev); + +/** + * @ingroup events + * + * Check if there are events waiting for us. This function does not read an + * event off the fd and may not access the fd at all. If there are events + * queued internally this function will return non-zero. If the internal + * queue is empty, this function will poll the file descriptor for data. + * + * This is a convenience function for simple processes, most complex programs + * are expected to use select(2) or poll(2) on the file descriptor. The kernel + * guarantees that if data is available, it is a multiple of sizeof(struct + * input_event), and thus calling libevdev_next_event() when select(2) or + * poll(2) return is safe. You do not need libevdev_has_event_pending() if + * you're using select(2) or poll(2). + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @return On failure, a negative errno is returned. + * @retval 0 No event is currently available + * @retval 1 One or more events are available on the fd + * + * @note This function is signal-safe. + */ +int libevdev_has_event_pending(struct libevdev *dev); + +/** + * @ingroup bits + * + * Retrieve the device's name, either as set by the caller or as read from + * the kernel. The string returned is valid until libevdev_free() or until + * libevdev_set_name(), whichever comes earlier. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The device name as read off the kernel device. The name is never + * NULL but it may be the empty string. + * + * @note This function is signal-safe. + */ +const char* libevdev_get_name(const struct libevdev *dev); + +/** + * @ingroup kernel + * + * Change the device's name as returned by libevdev_get_name(). This + * function destroys the string previously returned by libevdev_get_name(), + * a caller must take care that no references are kept. + * + * @param dev The evdev device + * @param name The new, non-NULL, name to assign to this device. + * + * @note This function may be called before libevdev_set_fd(). A call to + * libevdev_set_fd() will overwrite any previously set value. + */ +void libevdev_set_name(struct libevdev *dev, const char *name); + +/** + * @ingroup bits + * + * Retrieve the device's physical location, either as set by the caller or + * as read from the kernel. The string returned is valid until + * libevdev_free() or until libevdev_set_phys(), whichever comes earlier. + * + * Virtual devices such as uinput devices have no phys location. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The physical location of this device, or NULL if there is none + * + * @note This function is signal safe. + */ +const char * libevdev_get_phys(const struct libevdev *dev); + +/** + * @ingroup kernel + * + * Change the device's physical location as returned by libevdev_get_phys(). + * This function destroys the string previously returned by + * libevdev_get_phys(), a caller must take care that no references are kept. + * + * @param dev The evdev device + * @param phys The new phys to assign to this device. + * + * @note This function may be called before libevdev_set_fd(). A call to + * libevdev_set_fd() will overwrite any previously set value. + */ +void libevdev_set_phys(struct libevdev *dev, const char *phys); + +/** + * @ingroup bits + * + * Retrieve the device's unique identifier, either as set by the caller or + * as read from the kernel. The string returned is valid until + * libevdev_free() or until libevdev_set_uniq(), whichever comes earlier. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The unique identifier for this device, or NULL if there is none + * + * @note This function is signal safe. + */ +const char * libevdev_get_uniq(const struct libevdev *dev); + +/** + * @ingroup kernel + * + * Change the device's unique identifier as returned by libevdev_get_uniq(). + * This function destroys the string previously returned by + * libevdev_get_uniq(), a caller must take care that no references are kept. + * + * @param dev The evdev device + * @param uniq The new uniq to assign to this device. + * + * @note This function may be called before libevdev_set_fd(). A call to + * libevdev_set_fd() will overwrite any previously set value. + */ +void libevdev_set_uniq(struct libevdev *dev, const char *uniq); + +/** + * @ingroup bits + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The device's product ID + * + * @note This function is signal-safe. + */ +int libevdev_get_id_product(const struct libevdev *dev); + +/** + * @ingroup kernel + * + * @param dev The evdev device + * @param product_id The product ID to assign to this device + * + * @note This function may be called before libevdev_set_fd(). A call to + * libevdev_set_fd() will overwrite any previously set value. Even though + * the function accepts an int for product_id the value is truncated at 16 + * bits. + */ +void libevdev_set_id_product(struct libevdev *dev, int product_id); + +/** + * @ingroup bits + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The device's vendor ID + * + * @note This function is signal-safe. + */ +int libevdev_get_id_vendor(const struct libevdev *dev); + +/** + * @ingroup kernel + * + * @param dev The evdev device + * @param vendor_id The vendor ID to assign to this device + * + * @note This function may be called before libevdev_set_fd(). A call to + * libevdev_set_fd() will overwrite any previously set value. Even though + * the function accepts an int for vendor_id the value is truncated at 16 + * bits. + */ +void libevdev_set_id_vendor(struct libevdev *dev, int vendor_id); + +/** + * @ingroup bits + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The device's bus type + * + * @note This function is signal-safe. + */ +int libevdev_get_id_bustype(const struct libevdev *dev); + +/** + * @ingroup kernel + * + * @param dev The evdev device + * @param bustype The bustype to assign to this device + * + * @note This function may be called before libevdev_set_fd(). A call to + * libevdev_set_fd() will overwrite any previously set value. Even though + * the function accepts an int for bustype the value is truncated at 16 + * bits. + */ +void libevdev_set_id_bustype(struct libevdev *dev, int bustype); + +/** + * @ingroup bits + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The device's firmware version + * + * @note This function is signal-safe. + */ +int libevdev_get_id_version(const struct libevdev *dev); + +/** + * @ingroup kernel + * + * @param dev The evdev device + * @param version The version to assign to this device + * + * @note This function may be called before libevdev_set_fd(). A call to + * libevdev_set_fd() will overwrite any previously set value. Even though + * the function accepts an int for version the value is truncated at 16 + * bits. + */ +void libevdev_set_id_version(struct libevdev *dev, int version); + +/** + * @ingroup bits + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The driver version for this device + * + * @note This function is signal-safe. + */ +int libevdev_get_driver_version(const struct libevdev *dev); + +/** + * @ingroup bits + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param prop The input property to query for, one of INPUT_PROP_... + * + * @return 1 if the device provides this input property, or 0 otherwise. + * + * @note This function is signal-safe + */ +int libevdev_has_property(const struct libevdev *dev, unsigned int prop); + +/** + * @ingroup kernel + * + * @param dev The evdev device + * @param prop The input property to enable, one of INPUT_PROP_... + * + * @return 0 on success or -1 on failure + * + * @note This function may be called before libevdev_set_fd(). A call to + * libevdev_set_fd() will overwrite any previously set value. + */ +int libevdev_enable_property(struct libevdev *dev, unsigned int prop); + +/** + * @ingroup kernel + * + * @param dev The evdev device + * @param prop The input property to disable, one of INPUT_PROP_... + * + * @return 0 on success or -1 on failure + */ +int libevdev_disable_property(struct libevdev *dev, unsigned int prop); + +/** + * @ingroup bits + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type to query for, one of EV_SYN, EV_REL, etc. + * + * @return 1 if the device supports this event type, or 0 otherwise. + * + * @note This function is signal-safe. + */ +int libevdev_has_event_type(const struct libevdev *dev, unsigned int type); + +/** + * @ingroup bits + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type for the code to query (EV_SYN, EV_REL, etc.) + * @param code The event code to query for, one of ABS_X, REL_X, etc. + * + * @return 1 if the device supports this event type and code, or 0 otherwise. + * + * @note This function is signal-safe. + */ +int libevdev_has_event_code(const struct libevdev *dev, unsigned int type, unsigned int code); + +/** + * @ingroup bits + * + * Get the minimum axis value for the given axis, as advertised by the kernel. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code The EV_ABS event code to query for, one of ABS_X, ABS_Y, etc. + * + * @return axis minimum for the given axis or 0 if the axis is invalid + * + * @note This function is signal-safe. + */ +int libevdev_get_abs_minimum(const struct libevdev *dev, unsigned int code); + +/** + * @ingroup bits + * + * Get the maximum axis value for the given axis, as advertised by the kernel. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code The EV_ABS event code to query for, one of ABS_X, ABS_Y, etc. + * + * @return axis maximum for the given axis or 0 if the axis is invalid + * + * @note This function is signal-safe. + */ +int libevdev_get_abs_maximum(const struct libevdev *dev, unsigned int code); + +/** + * @ingroup bits + * + * Get the axis fuzz for the given axis, as advertised by the kernel. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code The EV_ABS event code to query for, one of ABS_X, ABS_Y, etc. + * + * @return axis fuzz for the given axis or 0 if the axis is invalid + * + * @note This function is signal-safe. + */ +int libevdev_get_abs_fuzz(const struct libevdev *dev, unsigned int code); + +/** + * @ingroup bits + * + * Get the axis flat for the given axis, as advertised by the kernel. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code The EV_ABS event code to query for, one of ABS_X, ABS_Y, etc. + * + * @return axis flat for the given axis or 0 if the axis is invalid + * + * @note This function is signal-safe. + */ +int libevdev_get_abs_flat(const struct libevdev *dev, unsigned int code); + +/** + * @ingroup bits + * + * Get the axis resolution for the given axis, as advertised by the kernel. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code The EV_ABS event code to query for, one of ABS_X, ABS_Y, etc. + * + * @return axis resolution for the given axis or 0 if the axis is invalid + * + * @note This function is signal-safe. + */ +int libevdev_get_abs_resolution(const struct libevdev *dev, unsigned int code); + +/** + * @ingroup bits + * + * Get the axis info for the given axis, as advertised by the kernel. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code The EV_ABS event code to query for, one of ABS_X, ABS_Y, etc. + * + * @return The input_absinfo for the given code, or NULL if the device does + * not support this event code. + * + * @note This function is signal-safe. + */ +const struct input_absinfo* libevdev_get_abs_info(const struct libevdev *dev, unsigned int code); + +/** + * @ingroup bits + * + * Behaviour of this function is undefined if the device does not provide + * the event. + * + * If the device supports ABS_MT_SLOT, the value returned for any ABS_MT_* + * event code is undefined. Use libevdev_get_slot_value() instead. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type for the code to query (EV_SYN, EV_REL, etc.) + * @param code The event code to query for, one of ABS_X, REL_X, etc. + * + * @return The current value of the event. + * + * @note This function is signal-safe. + * @note The value for ABS_MT_ events is undefined, use + * libevdev_get_slot_value() instead + * + * @see libevdev_get_slot_value + */ +int libevdev_get_event_value(const struct libevdev *dev, unsigned int type, unsigned int code); + +/** + * @ingroup kernel + * + * Set the value for a given event type and code. This only makes sense for + * some event types, e.g. setting the value for EV_REL is pointless. + * + * This is a local modification only affecting only this representation of + * this device. A future call to libevdev_get_event_value() will return this + * value, unless the value was overwritten by an event. + * + * If the device supports ABS_MT_SLOT, the value set for any ABS_MT_* + * event code is the value of the currently active slot. You should use + * libevdev_set_slot_value() instead. + * + * If the device supports ABS_MT_SLOT and the type is EV_ABS and the code is + * ABS_MT_SLOT, the value must be a positive number less then the number of + * slots on the device. Otherwise, libevdev_set_event_value() returns -1. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type for the code to query (EV_SYN, EV_REL, etc.) + * @param code The event code to set the value for, one of ABS_X, LED_NUML, etc. + * @param value The new value to set + * + * @return 0 on success, or -1 on failure. + * @retval -1 + * - the device does not have the event type or + * - code enabled, or the code is outside the, or + * - the code is outside the allowed limits for the given type, or + * - the type cannot be set, or + * - the value is not permitted for the given code. + * + * @see libevdev_set_slot_value + * @see libevdev_get_event_value + */ +int libevdev_set_event_value(struct libevdev *dev, unsigned int type, unsigned int code, int value); + +/** + * @ingroup bits + * + * Fetch the current value of the event type. This is a shortcut for + * + * @code + * if (libevdev_has_event_type(dev, t) && libevdev_has_event_code(dev, t, c)) + * val = libevdev_get_event_value(dev, t, c); + * @endcode + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type for the code to query (EV_SYN, EV_REL, etc.) + * @param code The event code to query for, one of ABS_X, REL_X, etc. + * @param[out] value The current value of this axis returned. + * + * @return If the device supports this event type and code, the return value is + * non-zero and value is set to the current value of this axis. Otherwise, + * 0 is returned and value is unmodified. + * + * @note This function is signal-safe. + * @note The value for ABS_MT_ events is undefined, use + * libevdev_fetch_slot_value() instead + * + * @see libevdev_fetch_slot_value + */ +int libevdev_fetch_event_value(const struct libevdev *dev, unsigned int type, unsigned int code, int *value); + +/** + * @ingroup mt + * + * Return the current value of the code for the given slot. + * + * The return value is undefined for a slot exceeding the available slots on + * the device, for a code that is not in the permitted ABS_MT range or for a + * device that does not have slots. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param slot The numerical slot number, must be smaller than the total number + * of slots on this device + * @param code The event code to query for, one of ABS_MT_POSITION_X, etc. + * + * @note This function is signal-safe. + * @note The value for events other than ABS_MT_ is undefined, use + * libevdev_fetch_value() instead + * + * @see libevdev_get_event_value + */ +int libevdev_get_slot_value(const struct libevdev *dev, unsigned int slot, unsigned int code); + +/** + * @ingroup kernel + * + * Set the value for a given code for the given slot. + * + * This is a local modification only affecting only this representation of + * this device. A future call to libevdev_get_slot_value() will return this + * value, unless the value was overwritten by an event. + * + * This function does not set event values for axes outside the ABS_MT range, + * use libevdev_set_event_value() instead. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param slot The numerical slot number, must be smaller than the total number + * of slots on this device + * @param code The event code to set the value for, one of ABS_MT_POSITION_X, etc. + * @param value The new value to set + * + * @return 0 on success, or -1 on failure. + * @retval -1 + * - the device does not have the event code enabled, or + * - the code is outside the allowed limits for multitouch events, or + * - the slot number is outside the limits for this device, or + * - the device does not support multitouch events. + * + * @see libevdev_set_event_value + * @see libevdev_get_slot_value + */ +int libevdev_set_slot_value(struct libevdev *dev, unsigned int slot, unsigned int code, int value); + +/** + * @ingroup mt + * + * Fetch the current value of the code for the given slot. This is a shortcut for + * + * @code + * if (libevdev_has_event_type(dev, EV_ABS) && + * libevdev_has_event_code(dev, EV_ABS, c) && + * slot < device->number_of_slots) + * val = libevdev_get_slot_value(dev, slot, c); + * @endcode + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param slot The numerical slot number, must be smaller than the total number + * of slots on this * device + * @param[out] value The current value of this axis returned. + * + * @param code The event code to query for, one of ABS_MT_POSITION_X, etc. + * @return If the device supports this event code, the return value is + * non-zero and value is set to the current value of this axis. Otherwise, or + * if the event code is not an ABS_MT_* event code, 0 is returned and value + * is unmodified. + * + * @note This function is signal-safe. + */ +int libevdev_fetch_slot_value(const struct libevdev *dev, unsigned int slot, unsigned int code, int *value); + +/** + * @ingroup mt + * + * Get the number of slots supported by this device. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return The number of slots supported, or -1 if the device does not provide + * any slots + * + * @note A device may provide ABS_MT_SLOT but a total number of 0 slots. Hence + * the return value of -1 for "device does not provide slots at all" + */ +int libevdev_get_num_slots(const struct libevdev *dev); + +/** + * @ingroup mt + * + * Get the currently active slot. This may differ from the value + * an ioctl may return at this time as events may have been read off the fd + * since changing the slot value but those events are still in the buffer + * waiting to be processed. The returned value is the value a caller would + * see if it were to process events manually one-by-one. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * + * @return the currently active slot (logically) + * + * @note This function is signal-safe. + */ +int libevdev_get_current_slot(const struct libevdev *dev); + +/** + * @ingroup kernel + * + * Change the minimum for the given EV_ABS event code, if the code exists. + * This function has no effect if libevdev_has_event_code() returns false for + * this code. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code One of ABS_X, ABS_Y, ... + * @param val The new minimum for this axis + */ +void libevdev_set_abs_minimum(struct libevdev *dev, unsigned int code, int val); + +/** + * @ingroup kernel + * + * Change the maximum for the given EV_ABS event code, if the code exists. + * This function has no effect if libevdev_has_event_code() returns false for + * this code. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code One of ABS_X, ABS_Y, ... + * @param val The new maxium for this axis + */ +void libevdev_set_abs_maximum(struct libevdev *dev, unsigned int code, int val); + +/** + * @ingroup kernel + * + * Change the fuzz for the given EV_ABS event code, if the code exists. + * This function has no effect if libevdev_has_event_code() returns false for + * this code. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code One of ABS_X, ABS_Y, ... + * @param val The new fuzz for this axis + */ +void libevdev_set_abs_fuzz(struct libevdev *dev, unsigned int code, int val); + +/** + * @ingroup kernel + * + * Change the flat for the given EV_ABS event code, if the code exists. + * This function has no effect if libevdev_has_event_code() returns false for + * this code. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code One of ABS_X, ABS_Y, ... + * @param val The new flat for this axis + */ +void libevdev_set_abs_flat(struct libevdev *dev, unsigned int code, int val); + +/** + * @ingroup kernel + * + * Change the resolution for the given EV_ABS event code, if the code exists. + * This function has no effect if libevdev_has_event_code() returns false for + * this code. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code One of ABS_X, ABS_Y, ... + * @param val The new axis resolution + */ +void libevdev_set_abs_resolution(struct libevdev *dev, unsigned int code, int val); + +/** + * @ingroup kernel + * + * Change the abs info for the given EV_ABS event code, if the code exists. + * This function has no effect if libevdev_has_event_code() returns false for + * this code. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code One of ABS_X, ABS_Y, ... + * @param abs The new absolute axis data (min, max, fuzz, flat, resolution) + */ +void libevdev_set_abs_info(struct libevdev *dev, unsigned int code, const struct input_absinfo *abs); + +/** + * @ingroup kernel + * + * Forcibly enable an event type on this device, even if the underlying + * device does not support it. While this cannot make the device actually + * report such events, it will now return true for libevdev_has_event_type(). + * + * This is a local modification only affecting only this representation of + * this device. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type to enable (EV_ABS, EV_KEY, ...) + * + * @return 0 on success or -1 otherwise + * + * @see libevdev_has_event_type + */ +int libevdev_enable_event_type(struct libevdev *dev, unsigned int type); + +/** + * @ingroup kernel + * + * Forcibly disable an event type on this device, even if the underlying + * device provides it. This effectively mutes the respective set of + * events. libevdev will filter any events matching this type and none will + * reach the caller. libevdev_has_event_type() will return false for this + * type. + * + * In most cases, a caller likely only wants to disable a single code, not + * the whole type. Use libevdev_disable_event_code() for that. + * + * Disabling EV_SYN will not work. Don't shoot yourself in the foot. + * It hurts. + * + * This is a local modification only affecting only this representation of + * this device. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type to disable (EV_ABS, EV_KEY, ...) + * + * @return 0 on success or -1 otherwise + * + * @see libevdev_has_event_type + * @see libevdev_disable_event_type + */ +int libevdev_disable_event_type(struct libevdev *dev, unsigned int type); + +/** + * @ingroup kernel + * + * Forcibly enable an event code on this device, even if the underlying + * device does not support it. While this cannot make the device actually + * report such events, it will now return true for libevdev_has_event_code(). + * + * The last argument depends on the type and code: + * - If type is EV_ABS, data must be a pointer to a struct input_absinfo + * containing the data for this axis. + * - If type is EV_REP, data must be a pointer to a int containing the data + * for this axis + * - For all other types, the argument must be NULL. + * + * This function calls libevdev_enable_event_type() if necessary. + * + * This is a local modification only affecting only this representation of + * this device. + * + * If this function is called with a type of EV_ABS and EV_REP on a device + * that already has the given event code enabled, the values in data + * overwrite the previous values. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type to enable (EV_ABS, EV_KEY, ...) + * @param code The event code to enable (ABS_X, REL_X, etc.) + * @param data If type is EV_ABS, data points to a struct input_absinfo. If type is EV_REP, data + * points to an integer. Otherwise, data must be NULL. + * + * @return 0 on success or -1 otherwise + * + * @see libevdev_enable_event_type + */ +int libevdev_enable_event_code(struct libevdev *dev, unsigned int type, unsigned int code, const void *data); + +/** + * @ingroup kernel + * + * Forcibly disable an event code on this device, even if the underlying + * device provides it. This effectively mutes the respective set of + * events. libevdev will filter any events matching this type and code and + * none will reach the caller. libevdev_has_event_code() will return false for + * this code. + * + * Disabling all event codes for a given type will not disable the event + * type. Use libevdev_disable_event_type() for that. + * + * This is a local modification only affecting only this representation of + * this device. + * + * Disabling codes of type EV_SYN will not work. Don't shoot yourself in the + * foot. It hurts. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param type The event type to disable (EV_ABS, EV_KEY, ...) + * @param code The event code to disable (ABS_X, REL_X, etc.) + * + * @return 0 on success or -1 otherwise + * + * @see libevdev_has_event_code + * @see libevdev_disable_event_type + */ +int libevdev_disable_event_code(struct libevdev *dev, unsigned int type, unsigned int code); + +/** + * @ingroup kernel + * + * Set the device's EV_ABS axis to the value defined in the abs + * parameter. This will be written to the kernel. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code The EV_ABS event code to modify, one of ABS_X, ABS_Y, etc. + * @param abs Axis info to set the kernel axis to + * + * @return 0 on success, or a negative errno on failure + * + * @see libevdev_enable_event_code + */ +int libevdev_kernel_set_abs_info(struct libevdev *dev, unsigned int code, const struct input_absinfo *abs); + +/** + * @ingroup kernel + */ +enum libevdev_led_value { + LIBEVDEV_LED_ON = 3, /**< Turn the LED on */ + LIBEVDEV_LED_OFF = 4 /**< Turn the LED off */ +}; + +/** + * @ingroup kernel + * + * Turn an LED on or off. Convenience function, if you need to modify multiple + * LEDs simultaneously, use libevdev_kernel_set_led_values() instead. + * + * @note enabling an LED requires write permissions on the device's file descriptor. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param code The EV_LED event code to modify, one of LED_NUML, LED_CAPSL, ... + * @param value Specifies whether to turn the LED on or off + * @return 0 on success, or a negative errno on failure + */ +int libevdev_kernel_set_led_value(struct libevdev *dev, unsigned int code, enum libevdev_led_value value); + +/** + * @ingroup kernel + * + * Turn multiple LEDs on or off simultaneously. This function expects a pair + * of LED codes and values to set them to, terminated by a -1. For example, to + * switch the NumLock LED on but the CapsLock LED off, use: + * + * @code + * libevdev_kernel_set_led_values(dev, LED_NUML, LIBEVDEV_LED_ON, + * LED_CAPSL, LIBEVDEV_LED_OFF, + * -1); + * @endcode + * + * If any LED code or value is invalid, this function returns -EINVAL and no + * LEDs are modified. + * + * @note enabling an LED requires write permissions on the device's file descriptor. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param ... A pair of LED_* event codes and libevdev_led_value_t, followed by + * -1 to terminate the list. + * @return 0 on success, or a negative errno on failure + */ +int libevdev_kernel_set_led_values(struct libevdev *dev, ...); + +/** + * @ingroup kernel + * + * Set the clock ID to be used for timestamps. Further events from this device + * will report an event time based on the given clock. + * + * This is a modification only affecting this representation of + * this device. + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param clockid The clock to use for future events. Permitted values + * are CLOCK_MONOTONIC and CLOCK_REALTIME (the default). + * @return 0 on success, or a negative errno on failure + */ +int libevdev_set_clock_id(struct libevdev *dev, int clockid); + +/** + * @ingroup misc + * + * Helper function to check if an event is of a specific type. This is + * virtually the same as: + * + * ev->type == type + * + * with the exception that some sanity checks are performed to ensure type + * is valid. + * + * @note The ranges for types are compiled into libevdev. If the kernel + * changes the max value, libevdev will not automatically pick these up. + * + * @param ev The input event to check + * @param type Input event type to compare the event against (EV_REL, EV_ABS, + * etc.) + * + * @return 1 if the event type matches the given type, 0 otherwise (or if + * type is invalid) + */ +int libevdev_event_is_type(const struct input_event *ev, unsigned int type); + +/** + * @ingroup misc + * + * Helper function to check if an event is of a specific type and code. This + * is virtually the same as: + * + * ev->type == type && ev->code == code + * + * with the exception that some sanity checks are performed to ensure type and + * code are valid. + * + * @note The ranges for types and codes are compiled into libevdev. If the kernel + * changes the max value, libevdev will not automatically pick these up. + * + * @param ev The input event to check + * @param type Input event type to compare the event against (EV_REL, EV_ABS, + * etc.) + * @param code Input event code to compare the event against (ABS_X, REL_X, + * etc.) + * + * @return 1 if the event type matches the given type and code, 0 otherwise + * (or if type/code are invalid) + */ +int libevdev_event_is_code(const struct input_event *ev, unsigned int type, unsigned int code); + +/** + * @ingroup misc + * + * @param type The event type to return the name for. + * + * @return The name of the given event type (e.g. EV_ABS) or NULL for an + * invalid type + * + * @note The list of names is compiled into libevdev. If the kernel adds new + * defines for new event types, libevdev will not automatically pick these up. + */ +const char * libevdev_event_type_get_name(unsigned int type); +/** + * @ingroup misc + * + * @param type The event type for the code to query (EV_SYN, EV_REL, etc.) + * @param code The event code to return the name for (e.g. ABS_X) + * + * @return The name of the given event code (e.g. ABS_X) or NULL for an + * invalid type or code + * + * @note The list of names is compiled into libevdev. If the kernel adds new + * defines for new event codes, libevdev will not automatically pick these up. + */ +const char * libevdev_event_code_get_name(unsigned int type, unsigned int code); + +/** + * @ingroup misc + * + * This function resolves the event value for a code. + * + * For almost all event codes this will return NULL as the value is just a + * numerical value. As of kernel 4.17, the only event code that will return + * a non-NULL value is EV_ABS/ABS_MT_TOOL_TYPE. + * + * @param type The event type for the value to query (EV_ABS, etc.) + * @param code The event code for the value to query (e.g. ABS_MT_TOOL_TYPE) + * @param value The event value to return the name for (e.g. MT_TOOL_PALM) + * + * @return The name of the given event value (e.g. MT_TOOL_PALM) or NULL for + * an invalid type or code or NULL for an axis that has numerical values + * only. + * + * @note The list of names is compiled into libevdev. If the kernel adds new + * defines for new event values, libevdev will not automatically pick these up. + */ +const char * libevdev_event_value_get_name(unsigned int type, + unsigned int code, + int value); +/** + * @ingroup misc + * + * @param prop The input prop to return the name for (e.g. INPUT_PROP_BUTTONPAD) + * + * @return The name of the given input prop (e.g. INPUT_PROP_BUTTONPAD) or NULL for an + * invalid property + * + * @note The list of names is compiled into libevdev. If the kernel adds new + * defines for new properties libevdev will not automatically pick these up. + * @note On older kernels input properties may not be defined and + * libevdev_property_get_name() will always return NULL + */ +const char* libevdev_property_get_name(unsigned int prop); + +/** + * @ingroup misc + * + * @param type The event type to return the maximum for (EV_ABS, EV_REL, etc.). No max is defined for + * EV_SYN. + * + * @return The max value defined for the given event type, e.g. ABS_MAX for a type of EV_ABS, or -1 + * for an invalid type. + * + * @note The max value is compiled into libevdev. If the kernel changes the + * max value, libevdev will not automatically pick these up. + */ +int libevdev_event_type_get_max(unsigned int type); + +/** + * @ingroup misc + * + * Look up an event-type by its name. Event-types start with "EV_" followed by + * the name (eg., "EV_ABS"). The "EV_" prefix must be included in the name. It + * returns the constant assigned to the event-type or -1 if not found. + * + * @param name A non-NULL string describing an input-event type ("EV_KEY", + * "EV_ABS", ...), zero-terminated. + * + * @return The given type constant for the passed name or -1 if not found. + * + * @note EV_MAX is also recognized. + */ +int libevdev_event_type_from_name(const char *name); + +/** + * @ingroup misc + * + * Look up an event-type by its name. Event-types start with "EV_" followed by + * the name (eg., "EV_ABS"). The "EV_" prefix must be included in the name. It + * returns the constant assigned to the event-type or -1 if not found. + * + * @param name A non-NULL string describing an input-event type ("EV_KEY", + * "EV_ABS", ...). + * @param len The length of the passed string excluding any terminating 0 + * character. + * + * @return The given type constant for the passed name or -1 if not found. + * + * @note EV_MAX is also recognized. + */ +int libevdev_event_type_from_name_n(const char *name, size_t len); + +/** + * @ingroup misc + * + * Look up an event code by its type and name. Event codes start with a fixed + * prefix followed by their name (eg., "ABS_X"). The prefix must be included in + * the name. It returns the constant assigned to the event code or -1 if not + * found. + * + * You have to pass the event type where to look for the name. For instance, to + * resolve "ABS_X" you need to pass EV_ABS as type and "ABS_X" as string. + * Supported event codes are codes starting with SYN_, KEY_, BTN_, REL_, ABS_, + * MSC_, SND_, SW_, LED_, REP_, FF_. + * + * @param type The event type (EV_* constant) where to look for the name. + * @param name A non-NULL string describing an input-event code ("KEY_A", + * "ABS_X", "BTN_Y", ...), zero-terminated. + * + * @return The given code constant for the passed name or -1 if not found. + */ +int libevdev_event_code_from_name(unsigned int type, const char *name); + +/** + * @ingroup misc + * + * Look up an event code by its type and name. Event codes start with a fixed + * prefix followed by their name (eg., "ABS_X"). The prefix must be included in + * the name. It returns the constant assigned to the event code or -1 if not + * found. + * + * You have to pass the event type where to look for the name. For instance, to + * resolve "ABS_X" you need to pass EV_ABS as type and "ABS_X" as string. + * Supported event codes are codes starting with SYN_, KEY_, BTN_, REL_, ABS_, + * MSC_, SND_, SW_, LED_, REP_, FF_. + * + * @param type The event type (EV_* constant) where to look for the name. + * @param name A non-NULL string describing an input-event code ("KEY_A", + * "ABS_X", "BTN_Y", ...). + * @param len The length of the string in @p name excluding any terminating 0 + * character. + * + * @return The given code constant for the name or -1 if not found. + */ +int libevdev_event_code_from_name_n(unsigned int type, const char *name, + size_t len); + +/** + * @ingroup misc + * + * Look up an event value by its type, code and name. Event values start + * with a fixed prefix followed by their name (eg., "MT_TOOL_PALM"). The + * prefix must be included in the name. It returns the constant assigned + * to the event code or -1 if not found. + * + * You have to pass the event type and code where to look for the name. For + * instance, to resolve "MT_TOOL_PALM" you need to pass EV_ABS as type, + * ABS_MT_TOOL_TYPE as code and "MT_TOOL_PALM" as string. + * + * As of kernel 4.17, only EV_ABS/ABS_MT_TOOL_TYPE support name resolution. + * + * @param type The event type (EV_* constant) where to look for the name. + * @param code The event code (ABS_* constant) where to look for the name. + * @param name A non-NULL string describing an input-event value + * ("MT_TOOL_TYPE", ...) + * + * @return The given value constant for the name or -1 if not found. + */ +int libevdev_event_value_from_name(unsigned int type, unsigned int code, + const char *name); + +/** + * @ingroup misc + * + * Look up an event type for a event code name. For example, the name + * "ABS_Y" returns EV_ABS. For the lookup to succeed, the name must be + * unique, which is the case for all defines as of kernel 5.0 and likely to + * be the case in the future. + * + * This is equivalent to libevdev_event_type_from_name() but takes the code + * name instead of the type name. + * + * @param name A non-NULL string describing an input-event value + * ("ABS_X", "REL_Y", "KEY_A", ...) + * + * @return The given event code for the name or -1 if not found. + */ +int +libevdev_event_type_from_code_name(const char *name); + +/** + * @ingroup misc + * + * Look up an event type for a event code name. For example, the name + * "ABS_Y" returns EV_ABS. For the lookup to succeed, the name must be + * unique, which is the case for all defines as of kernel 5.0 and likely to + * be the case in the future. + * + * This is equivalent to libevdev_event_type_from_name_n() but takes the code + * name instead of the type name. + * + * @param name A non-NULL string describing an input-event value + * ("ABS_X", "REL_Y", "KEY_A", ...) + * @param len The length of the passed string excluding any terminating 0 + * character. + * + * @return The given event code for the name or -1 if not found. + */ +int +libevdev_event_type_from_code_name_n(const char *name, size_t len); + +/** + * @ingroup misc + * + * Look up an event code by its name. For example, the name "ABS_Y" + * returns 1. For the lookup to succeed, the name must be unique, which is + * the case for all defines as of kernel 5.0 and likely to be the case in + * the future. + * + * This is equivalent to libevdev_event_code_from_name() without the need + * for knowing the event type. + * + * @param name A non-NULL string describing an input-event value + * ("ABS_X", "REL_Y", "KEY_A", ...) + * + * @return The given event code for the name or -1 if not found. + */ +int +libevdev_event_code_from_code_name(const char *name); + +/** + * @ingroup misc + * + * Look up an event code by its name. For example, the name "ABS_Y" + * returns 1. For the lookup to succeed, the name must be unique, which is + * the case for all defines as of kernel 5.0 and likely to be the case in + * the future. + * + * This is equivalent to libevdev_event_code_from_name_n() without the need + * for knowing the event type. + * + * @param name A non-NULL string describing an input-event value + * ("ABS_X", "REL_Y", "KEY_A", ...) + * @param len The length of the passed string excluding any terminating 0 + * character. + * + * @return The given event code for the name or -1 if not found. + */ +int +libevdev_event_code_from_code_name_n(const char *name, size_t len); + +/** + * @ingroup misc + * + * Look up an event value by its type, code and name. Event values start + * with a fixed prefix followed by their name (eg., "MT_TOOL_PALM"). The + * prefix must be included in the name. It returns the constant assigned + * to the event code or -1 if not found. + * + * You have to pass the event type and code where to look for the name. For + * instance, to resolve "MT_TOOL_PALM" you need to pass EV_ABS as type, + * ABS_MT_TOOL_TYPE as code and "MT_TOOL_PALM" as string. + * + * As of kernel 4.17, only EV_ABS/ABS_MT_TOOL_TYPE support name resolution. + * + * @param type The event type (EV_* constant) where to look for the name. + * @param code The event code (ABS_* constant) where to look for the name. + * @param name A non-NULL string describing an input-event value + * ("MT_TOOL_TYPE", ...) + * @param len The length of the string in @p name excluding any terminating 0 + * character. + * + * @return The given value constant for the name or -1 if not found. + */ +int libevdev_event_value_from_name_n(unsigned int type, unsigned int code, + const char *name, size_t len); + +/** + * @ingroup misc + * + * Look up an input property by its name. Properties start with the fixed + * prefix "INPUT_PROP_" followed by their name (eg., "INPUT_PROP_POINTER"). + * The prefix must be included in the name. It returns the constant assigned + * to the property or -1 if not found. + * + * @param name A non-NULL string describing an input property + * + * @return The given code constant for the name or -1 if not found. + */ +int libevdev_property_from_name(const char *name); + +/** + * @ingroup misc + * + * Look up an input property by its name. Properties start with the fixed + * prefix "INPUT_PROP_" followed by their name (eg., "INPUT_PROP_POINTER"). + * The prefix must be included in the name. It returns the constant assigned + * to the property or -1 if not found. + * + * @param name A non-NULL string describing an input property + * @param len The length of the string in @p name excluding any terminating 0 + * character. + * + * @return The given code constant for the name or -1 if not found. + */ +int libevdev_property_from_name_n(const char *name, size_t len); + +/** + * @ingroup bits + * + * Get the repeat delay and repeat period values for this device. This + * function is a convenience function only, EV_REP is supported by + * libevdev_get_event_value(). + * + * @param dev The evdev device, already initialized with libevdev_set_fd() + * @param delay If not null, set to the repeat delay value + * @param period If not null, set to the repeat period value + * + * @return 0 on success, -1 if this device does not have repeat settings. + * + * @note This function is signal-safe + * + * @see libevdev_get_event_value + */ +int libevdev_get_repeat(const struct libevdev *dev, int *delay, int *period); + +/********* DEPRECATED SECTION *********/ +#if defined(__GNUC__) && __GNUC__ >= 4 +#define LIBEVDEV_DEPRECATED __attribute__ ((deprecated)) +#else +#define LIBEVDEV_DEPRECATED +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* LIBEVDEV_H */ diff --git a/nativelib/src/main/cpp/libevdev/libevdev.sym b/nativelib/src/main/cpp/libevdev/libevdev.sym new file mode 100644 index 0000000000..4161962dc8 --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/libevdev.sym @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2013 David Herrmann + */ + +LIBEVDEV_1 { +global: + libevdev_change_fd; + libevdev_disable_event_code; + libevdev_disable_event_type; + libevdev_enable_event_code; + libevdev_enable_event_type; + libevdev_enable_property; + libevdev_event_code_from_name; + libevdev_event_code_from_name_n; + libevdev_event_code_get_name; + libevdev_event_is_code; + libevdev_event_is_type; + libevdev_event_type_from_name; + libevdev_event_type_from_name_n; + libevdev_event_type_get_max; + libevdev_event_type_get_name; + libevdev_fetch_event_value; + libevdev_fetch_slot_value; + libevdev_free; + libevdev_get_abs_flat; + libevdev_get_abs_fuzz; + libevdev_get_abs_info; + libevdev_get_abs_maximum; + libevdev_get_abs_minimum; + libevdev_get_abs_resolution; + libevdev_get_current_slot; + libevdev_get_driver_version; + libevdev_get_event_value; + libevdev_get_fd; + libevdev_get_id_bustype; + libevdev_get_id_product; + libevdev_get_id_vendor; + libevdev_get_id_version; + libevdev_get_log_priority; + libevdev_get_name; + libevdev_get_num_slots; + libevdev_get_phys; + libevdev_get_repeat; + libevdev_get_slot_value; + libevdev_get_uniq; + libevdev_grab; + libevdev_has_event_code; + libevdev_has_event_pending; + libevdev_has_event_type; + libevdev_has_property; + libevdev_kernel_set_abs_info; + libevdev_kernel_set_led_value; + libevdev_kernel_set_led_values; + libevdev_new; + libevdev_new_from_fd; + libevdev_next_event; + libevdev_property_get_name; + libevdev_set_abs_flat; + libevdev_set_abs_fuzz; + libevdev_set_abs_info; + libevdev_set_abs_maximum; + libevdev_set_abs_minimum; + libevdev_set_abs_resolution; + libevdev_set_clock_id; + libevdev_set_event_value; + libevdev_set_fd; + libevdev_set_id_bustype; + libevdev_set_id_product; + libevdev_set_id_vendor; + libevdev_set_id_version; + libevdev_set_log_function; + libevdev_set_log_priority; + libevdev_set_name; + libevdev_set_phys; + libevdev_set_slot_value; + libevdev_set_uniq; + libevdev_uinput_create_from_device; + libevdev_uinput_destroy; + libevdev_uinput_get_devnode; + libevdev_uinput_get_fd; + libevdev_uinput_get_syspath; + libevdev_uinput_write_event; + +local: + *; +}; + +LIBEVDEV_1_3 { +global: + libevdev_set_device_log_function; + libevdev_property_from_name; + libevdev_property_from_name_n; + +local: + *; +} LIBEVDEV_1; + +LIBEVDEV_1_6 { +global: + libevdev_event_value_get_name; + libevdev_event_value_from_name; + libevdev_event_value_from_name_n; +local: + *; +} LIBEVDEV_1_3; + +LIBEVDEV_1_7 { +global: + libevdev_event_code_from_code_name; + libevdev_event_code_from_code_name_n; + libevdev_event_type_from_code_name; + libevdev_event_type_from_code_name_n; +local: + *; +} LIBEVDEV_1_6; + +LIBEVDEV_1_10 { +global: + libevdev_disable_property; +local: + *; +} LIBEVDEV_1_7; diff --git a/nativelib/src/main/cpp/libevdev/make-event-names.py b/nativelib/src/main/cpp/libevdev/make-event-names.py new file mode 100755 index 0000000000..743b4b58b1 --- /dev/null +++ b/nativelib/src/main/cpp/libevdev/make-event-names.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# +# Parses linux/input.h scanning for #define KEY_FOO 134 +# Prints C header files or Python files that can be used as +# mapping and lookup tables. +# + +import re +import sys + + +class Bits(object): + def __init__(self): + self.max_codes = {} + + +prefixes = [ + "EV_", + "REL_", + "ABS_", + "KEY_", + "BTN_", + "LED_", + "SND_", + "MSC_", + "SW_", + "FF_", + "SYN_", + "REP_", + "INPUT_PROP_", + "MT_TOOL_", +] + +duplicates = [ + "EV_VERSION", + "BTN_MISC", + "BTN_MOUSE", + "BTN_JOYSTICK", + "BTN_GAMEPAD", + "BTN_DIGI", + "BTN_WHEEL", + "BTN_TRIGGER_HAPPY", + "SW_MAX", + "REP_MAX", +] + +btn_additional = [ + [0, "BTN_A"], + [0, "BTN_B"], + [0, "BTN_X"], + [0, "BTN_Y"], +] + +code_prefixes = [ + "REL_", + "ABS_", + "KEY_", + "BTN_", + "LED_", + "SND_", + "MSC_", + "SW_", + "FF_", + "SYN_", + "REP_", +] + + +def print_bits(bits, prefix): + if not hasattr(bits, prefix): + return + print("static const char * const %s_map[%s_MAX + 1] = {" % (prefix, prefix.upper())) + for val, name in sorted(list(getattr(bits, prefix).items())): + print(" [%s] = \"%s\"," % (name, name)) + if prefix == "key": + for val, name in sorted(list(getattr(bits, "btn").items())): + print(" [%s] = \"%s\"," % (name, name)) + print("};") + print("") + + +def print_map(bits): + print("static const char * const * const event_type_map[EV_MAX + 1] = {") + + for prefix in prefixes: + if prefix in ["BTN_", "EV_", "INPUT_PROP_", "MT_TOOL_"]: + continue + print(" [EV_%s] = %s_map," % (prefix[:-1], prefix[:-1].lower())) + + print("};") + print("") + + print("#if __clang__") + print("#pragma clang diagnostic push") + print("#pragma clang diagnostic ignored \"-Winitializer-overrides\"") + print("#elif __GNUC__") + print("#pragma GCC diagnostic push") + print("#pragma GCC diagnostic ignored \"-Woverride-init\"") + print("#endif") + print("static const int ev_max[EV_MAX + 1] = {") + for val in range(bits.max_codes["EV_MAX"] + 1): + if val in bits.ev: + prefix = bits.ev[val][3:] + if prefix + "_" in prefixes: + print(" %s_MAX," % prefix) + continue + print(" -1,") + print("};") + print("#if __clang__") + print("#pragma clang diagnostic pop /* \"-Winitializer-overrides\" */") + print("#elif __GNUC__") + print("#pragma GCC diagnostic pop /* \"-Woverride-init\" */") + print("#endif") + print("") + + +def print_lookup(bits, prefix): + if not hasattr(bits, prefix): + return + + names = sorted(list(getattr(bits, prefix).items())) + if prefix == "btn": + names = names + btn_additional + + # We need to manually add the _MAX codes because some are + # duplicates + maxname = "%s_MAX" % (prefix.upper()) + if maxname in duplicates: + names.append((bits.max_codes[maxname], maxname)) + + for val, name in sorted(names, key=lambda e: e[1]): + print(" { .name = \"%s\", .value = %s }," % (name, name)) + + +def print_lookup_table(bits): + print("struct name_entry {") + print(" const char *name;") + print(" unsigned int value;") + print("};") + print("") + print("static const struct name_entry tool_type_names[] = {") + print_lookup(bits, "mt_tool") + print("};") + print("") + print("static const struct name_entry ev_names[] = {") + print_lookup(bits, "ev") + print("};") + print("") + + print("static const struct name_entry code_names[] = {") + for prefix in sorted(code_prefixes, key=lambda e: e): + print_lookup(bits, prefix[:-1].lower()) + print("};") + print("") + print("static const struct name_entry prop_names[] = {") + print_lookup(bits, "input_prop") + print("};") + print("") + + +def print_mapping_table(bits): + print("/* THIS FILE IS GENERATED, DO NOT EDIT */") + print("") + print("#ifndef EVENT_NAMES_H") + print("#define EVENT_NAMES_H") + print("") + + for prefix in prefixes: + if prefix == "BTN_": + continue + print_bits(bits, prefix[:-1].lower()) + + print_map(bits) + print_lookup_table(bits) + + print("#endif /* EVENT_NAMES_H */") + + +def parse_define(bits, line): + m = re.match(r"^#define\s+(\w+)\s+(\w+)", line) + if m is None: + return + + name = m.group(1) + + try: + value = int(m.group(2), 0) + except ValueError: + return + + for prefix in prefixes: + if not name.startswith(prefix): + continue + + if name.endswith("_MAX"): + bits.max_codes[name] = value + + if name in duplicates: + return + + attrname = prefix[:-1].lower() + + if not hasattr(bits, attrname): + setattr(bits, attrname, {}) + b = getattr(bits, attrname) + b[value] = name + + +def parse(lines): + bits = Bits() + for line in lines: + if not line.startswith("#define"): + continue + parse_define(bits, line) + + return bits + + +def usage(prog): + print("Usage: %s ".format(prog)) + + +if __name__ == "__main__": + if len(sys.argv) <= 1: + usage(sys.argv[0]) + sys.exit(2) + + from itertools import chain + lines = chain(*[open(f).readlines() for f in sys.argv[1:]]) + bits = parse(lines) + print_mapping_table(bits) diff --git a/nativelib/src/main/cpp/nativelib.cpp b/nativelib/src/main/cpp/nativelib.cpp new file mode 100644 index 0000000000..9268290c76 --- /dev/null +++ b/nativelib/src/main/cpp/nativelib.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include "libevdev/libevdev.h" + +extern "C" JNIEXPORT jstring JNICALL +Java_io_github_sds100_keymapper_nativelib_NativeLib_stringFromJNI( + JNIEnv *env, + jobject /* this */) { + std::string hello = "Hello from C++"; + + struct libevdev *dev = NULL; + int fd; + int rc = 1; + + fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + rc = libevdev_new_from_fd(fd, &dev); + if (rc < 0) { + fprintf(stderr, "Failed to init libevdev (%s)\n", strerror(-rc)); + exit(1); + } + printf("Input device name: \"%s\"\n", libevdev_get_name(dev)); + printf("Input device ID: bus %#x vendor %#x product %#x\n", + libevdev_get_id_bustype(dev), + libevdev_get_id_vendor(dev), + libevdev_get_id_product(dev)); + if (!libevdev_has_event_type(dev, EV_REL) || + !libevdev_has_event_code(dev, EV_KEY, BTN_LEFT)) { + printf("This device does not look like a mouse\n"); + exit(1); + } + + do { + struct input_event ev; + rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + if (rc == 0) + printf("Event: %s %s %d\n", + libevdev_event_type_get_name(ev.type), + libevdev_event_code_get_name(ev.type, ev.code), + ev.value); + } while (rc == 1 || rc == 0 || rc == -EAGAIN); + + return env->NewStringUTF(hello.c_str()); +} \ No newline at end of file diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/NativeLib.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/NativeLib.kt new file mode 100644 index 0000000000..680a3d2a20 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/NativeLib.kt @@ -0,0 +1,17 @@ +package io.github.sds100.keymapper.nativelib + +class NativeLib { + + /** + * A native method that is implemented by the 'nativelib' native library, + * which is packaged with this application. + */ + external fun stringFromJNI(): String + + companion object { + // Used to load the 'nativelib' library on application startup. + init { + System.loadLibrary("nativelib") + } + } +} \ No newline at end of file diff --git a/nativelib/src/test/java/io/github/sds100/keymapper/nativelib/ExampleUnitTest.kt b/nativelib/src/test/java/io/github/sds100/keymapper/nativelib/ExampleUnitTest.kt new file mode 100644 index 0000000000..1f0be0cbd6 --- /dev/null +++ b/nativelib/src/test/java/io/github/sds100/keymapper/nativelib/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package io.github.sds100.keymapper.nativelib + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index a2a582b50a..ad704f8629 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ include ':app' include ':systemstubs' +include ':nativelib' From 6f8e16b6f218b391356740b5b9ee6c18718a4f41 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 16:58:05 +0200 Subject: [PATCH 55/95] #1394 log errors from native library --- nativelib/src/main/cpp/nativelib.cpp | 55 +++++++++++++++++----------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/nativelib/src/main/cpp/nativelib.cpp b/nativelib/src/main/cpp/nativelib.cpp index 9268290c76..9b2888d9fb 100644 --- a/nativelib/src/main/cpp/nativelib.cpp +++ b/nativelib/src/main/cpp/nativelib.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "libevdev/libevdev.h" extern "C" JNIEXPORT jstring JNICALL @@ -14,32 +15,42 @@ Java_io_github_sds100_keymapper_nativelib_NativeLib_stringFromJNI( int fd; int rc = 1; - fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); + std::string input_file_path = "/dev/input/event3"; + fd = open(input_file_path.c_str(), O_RDONLY); + + if (fd == -1) { + __android_log_print(ANDROID_LOG_ERROR, "Key Mapper", "Failed to open input file (%s)", + input_file_path.c_str()); + return env->NewStringUTF("Failed"); + } + rc = libevdev_new_from_fd(fd, &dev); if (rc < 0) { - fprintf(stderr, "Failed to init libevdev (%s)\n", strerror(-rc)); - exit(1); - } - printf("Input device name: \"%s\"\n", libevdev_get_name(dev)); - printf("Input device ID: bus %#x vendor %#x product %#x\n", - libevdev_get_id_bustype(dev), - libevdev_get_id_vendor(dev), - libevdev_get_id_product(dev)); - if (!libevdev_has_event_type(dev, EV_REL) || - !libevdev_has_event_code(dev, EV_KEY, BTN_LEFT)) { - printf("This device does not look like a mouse\n"); - exit(1); + __android_log_print(ANDROID_LOG_ERROR, "Key Mapper", "Failed to init libevdev (%s)", + strerror(-rc)); + return env->NewStringUTF("Failed to init"); } - do { - struct input_event ev; - rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); - if (rc == 0) - printf("Event: %s %s %d\n", - libevdev_event_type_get_name(ev.type), - libevdev_event_code_get_name(ev.type, ev.code), - ev.value); - } while (rc == 1 || rc == 0 || rc == -EAGAIN); +// printf("Input device name: \"%s\"\n", libevdev_get_name(dev)); +// printf("Input device ID: bus %#x vendor %#x product %#x\n", +// libevdev_get_id_bustype(dev), +// libevdev_get_id_vendor(dev), +// libevdev_get_id_product(dev)); +// if (!libevdev_has_event_type(dev, EV_REL) || +// !libevdev_has_event_code(dev, EV_KEY, BTN_LEFT)) { +// printf("This device does not look like a mouse\n"); +// exit(1); +// } +// +// do { +// struct input_event ev; +// rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); +// if (rc == 0) +// printf("Event: %s %s %d\n", +// libevdev_event_type_get_name(ev.type), +// libevdev_event_code_get_name(ev.type, ev.code), +// ev.value); +// } while (rc == 1 || rc == 0 || rc == -EAGAIN); return env->NewStringUTF(hello.c_str()); } \ No newline at end of file From 7cab3a3fa75807b72961973743b0538ab2a97420 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 17:46:22 +0200 Subject: [PATCH 56/95] #1394 grabbing an input device and detect input events works --- nativelib/build.gradle.kts | 7 ++++ .../keymapper/nativelib/IEvdevService.aidl | 10 +++++ nativelib/src/main/cpp/nativelib.cpp | 42 ++++++++++--------- .../keymapper/nativelib/EvdevService.kt | 37 ++++++++++++++++ .../sds100/keymapper/nativelib/NativeLib.kt | 17 -------- 5 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 nativelib/src/main/aidl/io/github/sds100/keymapper/nativelib/IEvdevService.aidl create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/EvdevService.kt delete mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/NativeLib.kt diff --git a/nativelib/build.gradle.kts b/nativelib/build.gradle.kts index 8200e8ba83..7b0d83dff8 100644 --- a/nativelib/build.gradle.kts +++ b/nativelib/build.gradle.kts @@ -28,16 +28,23 @@ android { ) } } + + buildFeatures { + aidl = true + } + externalNativeBuild { cmake { path("src/main/cpp/CMakeLists.txt") version = "3.22.1" } } + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } + kotlinOptions { jvmTarget = "11" } diff --git a/nativelib/src/main/aidl/io/github/sds100/keymapper/nativelib/IEvdevService.aidl b/nativelib/src/main/aidl/io/github/sds100/keymapper/nativelib/IEvdevService.aidl new file mode 100644 index 0000000000..678e8c1cc7 --- /dev/null +++ b/nativelib/src/main/aidl/io/github/sds100/keymapper/nativelib/IEvdevService.aidl @@ -0,0 +1,10 @@ +package io.github.sds100.keymapper.nativelib; + +interface IEvdevService { + // Destroy method defined by Shizuku server. This is required + // for Shizuku user services. + // See demo/service/UserService.java in the Shizuku-API repository. + void destroy() = 16777114; + + String sendEvent() = 1; +} \ No newline at end of file diff --git a/nativelib/src/main/cpp/nativelib.cpp b/nativelib/src/main/cpp/nativelib.cpp index 9b2888d9fb..268ca83c2d 100644 --- a/nativelib/src/main/cpp/nativelib.cpp +++ b/nativelib/src/main/cpp/nativelib.cpp @@ -5,10 +5,10 @@ #include #include "libevdev/libevdev.h" -extern "C" JNIEXPORT jstring JNICALL -Java_io_github_sds100_keymapper_nativelib_NativeLib_stringFromJNI( - JNIEnv *env, - jobject /* this */) { +extern "C" +JNIEXPORT jstring JNICALL +Java_io_github_sds100_keymapper_nativelib_EvdevService_stringFromJNI(JNIEnv *env, + jobject /* this */) { std::string hello = "Hello from C++"; struct libevdev *dev = NULL; @@ -31,26 +31,30 @@ Java_io_github_sds100_keymapper_nativelib_NativeLib_stringFromJNI( return env->NewStringUTF("Failed to init"); } -// printf("Input device name: \"%s\"\n", libevdev_get_name(dev)); -// printf("Input device ID: bus %#x vendor %#x product %#x\n", -// libevdev_get_id_bustype(dev), -// libevdev_get_id_vendor(dev), -// libevdev_get_id_product(dev)); + __android_log_print(ANDROID_LOG_ERROR, "Key Mapper", "Input device name: \"%s\"\n", + libevdev_get_name(dev)); + __android_log_print(ANDROID_LOG_ERROR, "Key Mapper", + "Input device ID: bus %#x vendor %#x product %#x\n", + libevdev_get_id_bustype(dev), + libevdev_get_id_vendor(dev), + libevdev_get_id_product(dev)); + // if (!libevdev_has_event_type(dev, EV_REL) || // !libevdev_has_event_code(dev, EV_KEY, BTN_LEFT)) { // printf("This device does not look like a mouse\n"); // exit(1); // } -// -// do { -// struct input_event ev; -// rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); -// if (rc == 0) -// printf("Event: %s %s %d\n", -// libevdev_event_type_get_name(ev.type), -// libevdev_event_code_get_name(ev.type, ev.code), -// ev.value); -// } while (rc == 1 || rc == 0 || rc == -EAGAIN); + libevdev_grab(dev, LIBEVDEV_GRAB); + + do { + struct input_event ev; + rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev); + if (rc == 0) + __android_log_print(ANDROID_LOG_ERROR, "Key Mapper", "Event: %s %s %d\n", + libevdev_event_type_get_name(ev.type), + libevdev_event_code_get_name(ev.type, ev.code), + ev.value); + } while (rc == 1 || rc == 0 || rc == -EAGAIN); return env->NewStringUTF(hello.c_str()); } \ No newline at end of file diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/EvdevService.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/EvdevService.kt new file mode 100644 index 0000000000..661fcff715 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/EvdevService.kt @@ -0,0 +1,37 @@ +package io.github.sds100.keymapper.nativelib + +import android.system.Os +import android.util.Log +import kotlin.system.exitProcess + +/** + * See demo/service/UserService.java in the Shizuku-API repository for how a Shizuku user service + * is set up. + */ +class EvdevService : IEvdevService.Stub() { + + /** + * A native method that is implemented by the 'nativelib' native library, + * which is packaged with this application. + */ + external fun stringFromJNI(): String + + companion object { + // Used to load the 'nativelib' library on application startup. + init { + System.loadLibrary("nativelib") + } + } + + private val TAG: String = "EvdevService" + + override fun destroy() { + Log.i(TAG, "destroy") + exitProcess(0) + } + + override fun sendEvent(): String { + Log.e(TAG, "UID = ${Os.getuid()}") + return stringFromJNI() + } +} diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/NativeLib.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/NativeLib.kt deleted file mode 100644 index 680a3d2a20..0000000000 --- a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/NativeLib.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.sds100.keymapper.nativelib - -class NativeLib { - - /** - * A native method that is implemented by the 'nativelib' native library, - * which is packaged with this application. - */ - external fun stringFromJNI(): String - - companion object { - // Used to load the 'nativelib' library on application startup. - init { - System.loadLibrary("nativelib") - } - } -} \ No newline at end of file From f8557ffac7d4ae37ab137349c6197df919ca2f12 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 19:28:04 +0200 Subject: [PATCH 57/95] #1394 WIP: build project with Shizuku ADB and server starter code --- nativelib/build.gradle.kts | 29 +- nativelib/src/main/cpp/CMakeLists.txt | 49 +++ nativelib/src/main/cpp/adb_pairing.cpp | 230 ++++++++++++ nativelib/src/main/cpp/adb_pairing.h | 4 + nativelib/src/main/cpp/android.cpp | 30 ++ nativelib/src/main/cpp/android.h | 8 + nativelib/src/main/cpp/cgroup.cpp | 73 ++++ nativelib/src/main/cpp/cgroup.h | 9 + nativelib/src/main/cpp/helper.cpp | 73 ++++ nativelib/src/main/cpp/logging.h | 30 ++ nativelib/src/main/cpp/misc.cpp | 195 +++++++++++ nativelib/src/main/cpp/misc.h | 14 + nativelib/src/main/cpp/nativelib.cpp | 20 +- nativelib/src/main/cpp/selinux.cpp | 105 ++++++ nativelib/src/main/cpp/selinux.h | 21 ++ nativelib/src/main/cpp/starter.cpp | 306 ++++++++++++++++ .../keymapper/nativelib/adb/AdbClient.kt | 180 ++++++++++ .../keymapper/nativelib/adb/AdbException.kt | 16 + .../sds100/keymapper/nativelib/adb/AdbKey.kt | 331 ++++++++++++++++++ .../sds100/keymapper/nativelib/adb/AdbMdns.kt | 135 +++++++ .../keymapper/nativelib/adb/AdbMessage.kt | 132 +++++++ .../nativelib/adb/AdbPairingClient.kt | 316 +++++++++++++++++ .../keymapper/nativelib/adb/AdbProtocol.kt | 22 ++ 23 files changed, 2314 insertions(+), 14 deletions(-) create mode 100644 nativelib/src/main/cpp/adb_pairing.cpp create mode 100644 nativelib/src/main/cpp/adb_pairing.h create mode 100644 nativelib/src/main/cpp/android.cpp create mode 100644 nativelib/src/main/cpp/android.h create mode 100644 nativelib/src/main/cpp/cgroup.cpp create mode 100644 nativelib/src/main/cpp/cgroup.h create mode 100644 nativelib/src/main/cpp/helper.cpp create mode 100644 nativelib/src/main/cpp/logging.h create mode 100644 nativelib/src/main/cpp/misc.cpp create mode 100644 nativelib/src/main/cpp/misc.h create mode 100644 nativelib/src/main/cpp/selinux.cpp create mode 100644 nativelib/src/main/cpp/selinux.h create mode 100644 nativelib/src/main/cpp/starter.cpp create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbClient.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbException.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMdns.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMessage.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbProtocol.kt diff --git a/nativelib/build.gradle.kts b/nativelib/build.gradle.kts index 7b0d83dff8..6f7f438c9d 100644 --- a/nativelib/build.gradle.kts +++ b/nativelib/build.gradle.kts @@ -12,9 +12,11 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") + externalNativeBuild { cmake { - cppFlags("") + // This is required by Rikka's library: https://github.com/RikkaW/libcxx-prefab + arguments("-DANDROID_STL=none") } } } @@ -24,13 +26,14 @@ android { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } buildFeatures { aidl = true + prefab = true } externalNativeBuild { @@ -40,6 +43,11 @@ android { } } + packaging { + jniLibs { + useLegacyPackaging = false + } + } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -51,11 +59,24 @@ android { } dependencies { - implementation("androidx.core:core-ktx:1.16.0") implementation("androidx.appcompat:appcompat:1.7.0") implementation("com.google.android.material:material:1.12.0") + + // From Shizuku :manager module build.gradle file. + implementation("io.github.vvb2060.ndk:boringssl:20250114") + implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0") + implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3") + implementation("org.bouncycastle:bcpkix-jdk15on:1.70") + implementation("me.zhanghai.android.appiconloader:appiconloader:1.5.0") + implementation("dev.rikka.rikkax.core:core-ktx:1.4.1") + implementation("dev.rikka.hidden:compat:4.3.3") + compileOnly("dev.rikka.hidden:stub:4.3.3") + +// annotationProcessor("dev.rikka.tools.refine:annotation-processor:4.4.0") +// compileOnly("dev.rikka.tools.refine:annotation:4.4.0") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") -} \ No newline at end of file +} diff --git a/nativelib/src/main/cpp/CMakeLists.txt b/nativelib/src/main/cpp/CMakeLists.txt index 417847fe0c..fe6cab703b 100644 --- a/nativelib/src/main/cpp/CMakeLists.txt +++ b/nativelib/src/main/cpp/CMakeLists.txt @@ -11,6 +11,55 @@ cmake_minimum_required(VERSION 3.22.1) # build script scope). project("nativelib") +# FROM SHIZUKU +set(CMAKE_CXX_STANDARD 17) + +set(C_FLAGS "-Werror=format -fdata-sections -ffunction-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics") +set(LINKER_FLAGS "-Wl,--hash-style=both") + +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message("Builing Release...") + + set(C_FLAGS "${C_FLAGS} -O2 -fvisibility=hidden -fvisibility-inlines-hidden") + set(LINKER_FLAGS "${LINKER_FLAGS} -Wl,-exclude-libs,ALL -Wl,--gc-sections") +else() + message("Builing Debug...") + + add_definitions(-DDEBUG) +endif () + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_FLAGS}") + +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") + +find_library(log-lib log) +find_package(boringssl REQUIRED CONFIG) +find_package(cxx REQUIRED CONFIG) + +add_executable(libshizuku.so + starter.cpp misc.cpp selinux.cpp cgroup.cpp android.cpp) + +target_link_libraries(libshizuku.so ${log-lib} cxx::cxx) + +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + add_custom_command(TARGET libshizuku.so POST_BUILD + COMMAND ${CMAKE_STRIP} --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libshizuku.so") +endif () + +add_library(adb SHARED + adb_pairing.cpp misc.cpp) + +target_link_libraries(adb ${log-lib} boringssl::crypto_static cxx::cxx) + +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + add_custom_command(TARGET adb POST_BUILD + COMMAND ${CMAKE_STRIP} --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libadb.so") +endif () + +# END FROM SHIZUKU + # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. diff --git a/nativelib/src/main/cpp/adb_pairing.cpp b/nativelib/src/main/cpp/adb_pairing.cpp new file mode 100644 index 0000000000..1a44981e28 --- /dev/null +++ b/nativelib/src/main/cpp/adb_pairing.cpp @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "adb_pairing.h" + +#define LOG_TAG "AdbPairClient" + +#include "logging.h" + +// --------------------------------------------------------- + +static constexpr spake2_role_t kClientRole = spake2_role_alice; +static constexpr spake2_role_t kServerRole = spake2_role_bob; + +static const uint8_t kClientName[] = "adb pair client"; +static const uint8_t kServerName[] = "adb pair server"; + +static constexpr size_t kHkdfKeyLength = 16; + +struct PairingContextNative { + SPAKE2_CTX *spake2_ctx; + uint8_t key[SPAKE2_MAX_MSG_SIZE]; + size_t key_size; + + EVP_AEAD_CTX *aes_ctx; + uint64_t dec_sequence; + uint64_t enc_sequence; +}; + +static jlong PairingContext_Constructor(JNIEnv *env, jclass clazz, jboolean isClient, jbyteArray jPassword) { + spake2_role_t spake_role; + const uint8_t *my_name; + const uint8_t *their_name; + size_t my_len; + size_t their_len; + + if (isClient) { + spake_role = kClientRole; + my_name = kClientName; + my_len = sizeof(kClientName); + their_name = kServerName; + their_len = sizeof(kServerName); + } else { + spake_role = kServerRole; + my_name = kServerName; + my_len = sizeof(kServerName); + their_name = kClientName; + their_len = sizeof(kClientName); + } + + auto spake2_ctx = SPAKE2_CTX_new(spake_role, my_name, my_len, their_name, their_len); + if (spake2_ctx == nullptr) { + LOGE("Unable to create a SPAKE2 context."); + return 0; + } + + auto pswd_size = env->GetArrayLength(jPassword); + auto pswd = env->GetByteArrayElements(jPassword, nullptr); + + size_t key_size = 0; + uint8_t key[SPAKE2_MAX_MSG_SIZE]; + int status = SPAKE2_generate_msg(spake2_ctx, key, &key_size, SPAKE2_MAX_MSG_SIZE, (uint8_t *) pswd, pswd_size); + if (status != 1 || key_size == 0) { + LOGE("Unable to generate the SPAKE2 public key."); + + env->ReleaseByteArrayElements(jPassword, pswd, 0); + SPAKE2_CTX_free(spake2_ctx); + return 0; + } + env->ReleaseByteArrayElements(jPassword, pswd, 0); + + auto ctx = (PairingContextNative *) malloc(sizeof(PairingContextNative)); + memset(ctx, 0, sizeof(PairingContextNative)); + ctx->spake2_ctx = spake2_ctx; + memcpy(ctx->key, key, SPAKE2_MAX_MSG_SIZE); + ctx->key_size = key_size; + return (jlong) ctx; +} + +static jbyteArray PairingContext_Msg(JNIEnv *env, jobject obj, jlong ptr) { + auto ctx = (PairingContextNative *) ptr; + jbyteArray our_msg = env->NewByteArray(ctx->key_size); + env->SetByteArrayRegion(our_msg, 0, ctx->key_size, (jbyte *) ctx->key); + return our_msg; +} + +static jboolean PairingContext_InitCipher(JNIEnv *env, jobject obj, jlong ptr, jbyteArray jTheirMsg) { + auto res = JNI_TRUE; + + auto ctx = (PairingContextNative *) ptr; + auto spake2_ctx = ctx->spake2_ctx; + auto their_msg_size = env->GetArrayLength(jTheirMsg); + + if (their_msg_size > SPAKE2_MAX_MSG_SIZE) { + LOGE("their_msg size [%d] greater then max size [%d].", their_msg_size, SPAKE2_MAX_MSG_SIZE); + return JNI_FALSE; + } + + auto their_msg = env->GetByteArrayElements(jTheirMsg, nullptr); + + size_t key_material_len = 0; + uint8_t key_material[SPAKE2_MAX_KEY_SIZE]; + int status = SPAKE2_process_msg(spake2_ctx, key_material, &key_material_len, + sizeof(key_material), (uint8_t *) their_msg, their_msg_size); + + env->ReleaseByteArrayElements(jTheirMsg, their_msg, 0); + + if (status != 1) { + LOGE("Unable to process their public key"); + return JNI_FALSE; + } + + // -------- + uint8_t key[kHkdfKeyLength]; + uint8_t info[] = "adb pairing_auth aes-128-gcm key"; + + status = HKDF(key, sizeof(key), EVP_sha256(), key_material, key_material_len, nullptr, 0, info, + sizeof(info) - 1); + if (status != 1) { + LOGE("HKDF"); + return JNI_FALSE; + } + + ctx->aes_ctx = EVP_AEAD_CTX_new(EVP_aead_aes_128_gcm(), key, sizeof(key), EVP_AEAD_DEFAULT_TAG_LENGTH); + + if (!ctx->aes_ctx) { + LOGE("EVP_AEAD_CTX_new"); + return JNI_FALSE; + } + + return res; +} + +static jbyteArray PairingContext_Encrypt(JNIEnv *env, jobject obj, jlong ptr, jbyteArray jIn) { + auto ctx = (PairingContextNative *) ptr; + auto aes_ctx = ctx->aes_ctx; + + auto in = env->GetByteArrayElements(jIn, nullptr); + auto in_size = env->GetArrayLength(jIn); + + auto out_size = (size_t) in_size + EVP_AEAD_max_overhead(EVP_AEAD_CTX_aead(ctx->aes_ctx)); + uint8_t out[out_size]; + + auto nonce_size = EVP_AEAD_nonce_length(EVP_AEAD_CTX_aead(aes_ctx)); + uint8_t nonce[nonce_size]; + memset(nonce, 0, nonce_size); + memcpy(nonce, &ctx->enc_sequence, sizeof(ctx->enc_sequence)); + + size_t written_sz; + int status = EVP_AEAD_CTX_seal(aes_ctx, out, &written_sz, out_size, nonce, nonce_size, (uint8_t *) in, in_size, nullptr, 0); + + env->ReleaseByteArrayElements(jIn, in, 0); + + if (!status) { + LOGE("Failed to encrypt (in_len=%d, out_len=%" PRIuPTR", out_len_needed=%d)", in_size, out_size, in_size); + return nullptr; + } + ++ctx->enc_sequence; + + jbyteArray jOut = env->NewByteArray(written_sz); + env->SetByteArrayRegion(jOut, 0, written_sz, (jbyte *) out); + return jOut; +} + +static jbyteArray PairingContext_Decrypt(JNIEnv *env, jobject obj, jlong ptr, jbyteArray jIn) { + auto ctx = (PairingContextNative *) ptr; + auto aes_ctx = ctx->aes_ctx; + + auto in = env->GetByteArrayElements(jIn, nullptr); + auto in_size = env->GetArrayLength(jIn); + + auto out_size = (size_t) in_size; + uint8_t out[out_size]; + + auto nonce_size = EVP_AEAD_nonce_length(EVP_AEAD_CTX_aead(aes_ctx)); + uint8_t nonce[nonce_size]; + memset(nonce, 0, nonce_size); + memcpy(nonce, &ctx->dec_sequence, sizeof(ctx->dec_sequence)); + + size_t written_sz; + int status = EVP_AEAD_CTX_open(aes_ctx, out, &written_sz, out_size, nonce, nonce_size, (uint8_t *) in, in_size, nullptr, 0); + + env->ReleaseByteArrayElements(jIn, in, 0); + + if (!status) { + LOGE("Failed to decrypt (in_len=%d, out_len=%" PRIuPTR", out_len_needed=%d)", in_size, out_size, in_size); + return nullptr; + } + ++ctx->dec_sequence; + + jbyteArray jOut = env->NewByteArray(written_sz); + env->SetByteArrayRegion(jOut, 0, written_sz, (jbyte *) out); + return jOut; +} + +static void PairingContext_Destroy(JNIEnv *env, jobject obj, jlong ptr) { + auto ctx = (PairingContextNative *) ptr; + SPAKE2_CTX_free(ctx->spake2_ctx); + if (ctx->aes_ctx) EVP_AEAD_CTX_free(ctx->aes_ctx); + free(ctx); +} + +// --------------------------------------------------------- + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env = nullptr; + + if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) + return -1; + + JNINativeMethod methods_PairingContext[] = { + {"nativeConstructor", "(Z[B)J", (void *) PairingContext_Constructor}, + {"nativeMsg", "(J)[B", (void *) PairingContext_Msg}, + {"nativeInitCipher", "(J[B)Z", (void *) PairingContext_InitCipher}, + {"nativeEncrypt", "(J[B)[B", (void *) PairingContext_Encrypt}, + {"nativeDecrypt", "(J[B)[B", (void *) PairingContext_Decrypt}, + {"nativeDestroy", "(J)V", (void *) PairingContext_Destroy}, + }; + + env->RegisterNatives(env->FindClass("moe/shizuku/manager/adb/PairingContext"), methods_PairingContext, + sizeof(methods_PairingContext) / sizeof(JNINativeMethod)); + + return JNI_VERSION_1_6; +} diff --git a/nativelib/src/main/cpp/adb_pairing.h b/nativelib/src/main/cpp/adb_pairing.h new file mode 100644 index 0000000000..3f9eb70a66 --- /dev/null +++ b/nativelib/src/main/cpp/adb_pairing.h @@ -0,0 +1,4 @@ +#ifndef ADB_H +#define ADB_H + +#endif // ADB_H diff --git a/nativelib/src/main/cpp/android.cpp b/nativelib/src/main/cpp/android.cpp new file mode 100644 index 0000000000..4bb6c44e0e --- /dev/null +++ b/nativelib/src/main/cpp/android.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +namespace android { + + int GetApiLevel() { + static int apiLevel = 0; + if (apiLevel > 0) return apiLevel; + + char buf[PROP_VALUE_MAX + 1]; + if (__system_property_get("ro.build.version.sdk", buf) > 0) + apiLevel = atoi(buf); + + return apiLevel; + } + + int GetPreviewApiLevel() { + static int previewApiLevel = 0; + if (previewApiLevel > 0) return previewApiLevel; + + char buf[PROP_VALUE_MAX + 1]; + if (__system_property_get("ro.build.version.preview_sdk", buf) > 0) + previewApiLevel = atoi(buf); + + return previewApiLevel; + } +} \ No newline at end of file diff --git a/nativelib/src/main/cpp/android.h b/nativelib/src/main/cpp/android.h new file mode 100644 index 0000000000..a4b78fba96 --- /dev/null +++ b/nativelib/src/main/cpp/android.h @@ -0,0 +1,8 @@ +#pragma once + +namespace android { + + int GetApiLevel(); + + int GetPreviewApiLevel(); +} \ No newline at end of file diff --git a/nativelib/src/main/cpp/cgroup.cpp b/nativelib/src/main/cpp/cgroup.cpp new file mode 100644 index 0000000000..c1015b72f5 --- /dev/null +++ b/nativelib/src/main/cpp/cgroup.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include + +namespace cgroup { + + static ssize_t fdgets(char *buf, const size_t size, int fd) { + ssize_t len = 0; + buf[0] = '\0'; + while (len < size - 1) { + ssize_t ret = read(fd, buf + len, 1); + if (ret < 0) + return -1; + if (ret == 0) + break; + if (buf[len] == '\0' || buf[len++] == '\n') { + break; + } + } + buf[len] = '\0'; + buf[size - 1] = '\0'; + return len; + } + + int get_cgroup(int pid, int* cuid, int *cpid) { + char buf[PATH_MAX]; + snprintf(buf, PATH_MAX, "/proc/%d/cgroup", pid); + + int fd = open(buf, O_RDONLY); + if (fd == -1) + return -1; + + while (fdgets(buf, PATH_MAX, fd) > 0) { + if (sscanf(buf, "%*d:cpuacct:/uid_%d/pid_%d", cuid, cpid) == 2) { + close(fd); + return 0; + } + } + close(fd); + return -1; + } + + static int switch_cgroup(int pid, int cuid, int cpid, const char *name) { + char buf[PATH_MAX]; + if (cuid != -1 && cpid != -1) { + snprintf(buf, PATH_MAX, "/acct/uid_%d/pid_%d/%s", cuid, cpid, name); + } else { + snprintf(buf, PATH_MAX, "/acct/%s", name); + } + + int fd = open(buf, O_WRONLY | O_APPEND); + if (fd == -1) + return -1; + + snprintf(buf, PATH_MAX, "%d\n", pid); + if (write(fd, buf, strlen(buf)) == -1) { + close(fd); + return -1; + } + + close(fd); + return 0; + } + + int switch_cgroup(int pid, int cuid, int cpid) { + int res = 0; + res += switch_cgroup(pid, cuid, cpid, "cgroup.procs"); + res += switch_cgroup(pid, cuid, cpid, "tasks"); + return res; + } + +} \ No newline at end of file diff --git a/nativelib/src/main/cpp/cgroup.h b/nativelib/src/main/cpp/cgroup.h new file mode 100644 index 0000000000..361a0b1919 --- /dev/null +++ b/nativelib/src/main/cpp/cgroup.h @@ -0,0 +1,9 @@ +#ifndef CGROUP_H +#define CGROUP_H + +namespace cgroup { + int get_cgroup(int pid, int* cuid, int *cpid); + int switch_cgroup(int pid, int cuid, int cpid); +} + +#endif // CGROUP_H diff --git a/nativelib/src/main/cpp/helper.cpp b/nativelib/src/main/cpp/helper.cpp new file mode 100644 index 0000000000..1001f8316d --- /dev/null +++ b/nativelib/src/main/cpp/helper.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "selinux.h" + +#define LOG_TAG "ShizukuServer" + +#include "logging.h" + +static jint setcontext(JNIEnv *env, jobject thiz, jstring jName) { + const char *name = env->GetStringUTFChars(jName, nullptr); + + if (!se::setcon) + return -1; + + int res = se::setcon(name); + if (res == -1) PLOGE("setcon %s", name); + + env->ReleaseStringUTFChars(jName, name); + + return res; +} + +static JNINativeMethod gMethods[] = { + {"setSELinuxContext", "(Ljava/lang/String;)I", (void *) setcontext}, +}; + +static int registerNativeMethods(JNIEnv *env, const char *className, + JNINativeMethod *gMethods, int numMethods) { + jclass clazz; + clazz = env->FindClass(className); + if (clazz == nullptr) + return JNI_FALSE; + + if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) + return JNI_FALSE; + + return JNI_TRUE; +} + +static int registerNatives(JNIEnv *env) { + if (!registerNativeMethods(env, "moe/shizuku/server/utils/NativeHelper", gMethods, + sizeof(gMethods) / sizeof(gMethods[0]))) + return JNI_FALSE; + + return JNI_TRUE; +} + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env = nullptr; + jint result; + + if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) + return -1; + + assert(env != nullptr); + + se::init(); + + if (!registerNatives(env)) { + LOGE("registerNatives NativeHelper"); + return -1; + } + + result = JNI_VERSION_1_6; + + return result; +} diff --git a/nativelib/src/main/cpp/logging.h b/nativelib/src/main/cpp/logging.h new file mode 100644 index 0000000000..91a750f4a0 --- /dev/null +++ b/nativelib/src/main/cpp/logging.h @@ -0,0 +1,30 @@ +#ifndef _LOGGING_H +#define _LOGGING_H + +#include +#include "android/log.h" + +#ifndef LOG_TAG +#define LOG_TAG "Key Mapper" +#endif + +#ifndef NO_LOG +#ifndef NO_DEBUG_LOG +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#else +#define LOGD(...) +#endif +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) +#else +#define LOGD(...) +#define LOGV(...) +#define LOGI(...) +#define LOGW(...) +#define LOGE(...) +#define PLOGE(fmt, args...) +#endif +#endif // _LOGGING_H diff --git a/nativelib/src/main/cpp/misc.cpp b/nativelib/src/main/cpp/misc.cpp new file mode 100644 index 0000000000..e7d2f8ccd7 --- /dev/null +++ b/nativelib/src/main/cpp/misc.cpp @@ -0,0 +1,195 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "misc.h" + +ssize_t fdgets(char *buf, const size_t size, int fd) { + ssize_t len = 0; + buf[0] = '\0'; + while (len < size - 1) { + ssize_t ret = read(fd, buf + len, 1); + if (ret < 0) + return -1; + if (ret == 0) + break; + if (buf[len] == '\0' || buf[len++] == '\n') { + buf[len] = '\0'; + break; + } + } + buf[len] = '\0'; + buf[size - 1] = '\0'; + return len; +} + +int get_proc_name(int pid, char *name, size_t _size) { + int fd; + ssize_t __size; + + char buf[1024]; + snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid); + if (access(buf, R_OK) == -1 || (fd = open(buf, O_RDONLY)) == -1) + return 1; + if ((__size = fdgets(buf, sizeof(buf), fd)) == 0) { + snprintf(buf, sizeof(buf), "/proc/%d/comm", pid); + close(fd); + if (access(buf, R_OK) == -1 || (fd = open(buf, O_RDONLY)) == -1) + return 1; + __size = fdgets(buf, sizeof(buf), fd); + } + close(fd); + + if (__size < _size) { + strncpy(name, buf, static_cast(__size)); + name[__size] = '\0'; + } else { + strncpy(name, buf, _size); + name[_size] = '\0'; + } + + return 0; +} + +int is_num(const char *s) { + size_t len = strlen(s); + for (size_t i = 0; i < len; ++i) + if (s[i] < '0' || s[i] > '9') + return 0; + return 1; +} + +int copyfileat(int src_path_fd, const char *src_path, int dst_path_fd, const char *dst_path) { + int src_fd; + int dst_fd; + struct stat stat_buf{}; + int64_t size_remaining; + size_t count; + ssize_t result; + + if ((src_fd = openat(src_path_fd, src_path, O_RDONLY)) == -1) + return -1; + + if (fstat(src_fd, &stat_buf) == -1) + return -1; + + dst_fd = openat(dst_path_fd, dst_path, O_WRONLY | O_CREAT | O_TRUNC, stat_buf.st_mode); + if (dst_fd == -1) { + close(src_fd); + return -1; + } + + size_remaining = stat_buf.st_size; + for (;;) { + if (size_remaining > 0x7ffff000) + count = 0x7ffff000; + else + count = static_cast(size_remaining); + + result = sendfile(dst_fd, src_fd, nullptr, count); + if (result == -1) { + close(src_fd); + close(dst_fd); + unlink(dst_path); + return -1; + } + + size_remaining -= result; + if (size_remaining == 0) { + close(src_fd); + close(dst_fd); + return 0; + } + } +} + +int copyfile(const char *src_path, const char *dst_path) { + return copyfileat(0, src_path, 0, dst_path); +} + +uintptr_t memsearch(const uintptr_t start, const uintptr_t end, const void *value, size_t size) { + uintptr_t _start = start; + while (true) { + if (_start + size >= end) + return 0; + + if (memcmp((const void *) _start, value, size) == 0) + return _start; + + _start += 1; + } +} + +int switch_mnt_ns(int pid) { + char mnt[32]; + snprintf(mnt, sizeof(mnt), "/proc/%d/ns/mnt", pid); + if (access(mnt, R_OK) == -1) return -1; + + int fd = open(mnt, O_RDONLY); + if (fd < 0) return -1; + + int res = setns(fd, 0); + close(fd); + return res; +} + +void foreach_proc(foreach_proc_function *func) { + DIR *dir; + struct dirent *entry; + + if (!(dir = opendir("/proc"))) + return; + + while ((entry = readdir(dir))) { + if (entry->d_type != DT_DIR) continue; + if (!is_num(entry->d_name)) continue; + pid_t pid = atoi(entry->d_name); + func(pid); + } + + closedir(dir); +} + +char *trim(char *str) { + size_t len = 0; + char *frontp = str; + char *endp = nullptr; + + if (str == nullptr) { return nullptr; } + if (str[0] == '\0') { return str; } + + len = strlen(str); + endp = str + len; + + /* Move the front and back pointers to address the first non-whitespace + * characters from each end. + */ + while (isspace((unsigned char) *frontp)) { ++frontp; } + if (endp != frontp) { + while (isspace((unsigned char) *(--endp)) && endp != frontp) {} + } + + if (str + len - 1 != endp) + *(endp + 1) = '\0'; + else if (frontp != str && endp == frontp) + *str = '\0'; + + /* Shift the string so that it starts at str so that if it's dynamically + * allocated, we can still free it on the returned pointer. Note the reuse + * of endp to mean the front of the string buffer now. + */ + endp = str; + if (frontp != str) { + while (*frontp) { *endp++ = *frontp++; } + *endp = '\0'; + } + + return str; +} \ No newline at end of file diff --git a/nativelib/src/main/cpp/misc.h b/nativelib/src/main/cpp/misc.h new file mode 100644 index 0000000000..3ab3e87d4a --- /dev/null +++ b/nativelib/src/main/cpp/misc.h @@ -0,0 +1,14 @@ +#ifndef MISC_H +#define MISC_H + +int copyfile(const char *src_path, const char *dst_path); +uintptr_t memsearch(const uintptr_t start, const uintptr_t end, const void *value, size_t size); +int switch_mnt_ns(int pid); +int get_proc_name(int pid, char *name, size_t _size); + +using foreach_proc_function = void(pid_t); +void foreach_proc(foreach_proc_function *func); + +char *trim(char *str); + +#endif // MISC_H diff --git a/nativelib/src/main/cpp/nativelib.cpp b/nativelib/src/main/cpp/nativelib.cpp index 268ca83c2d..6175a9dbb5 100644 --- a/nativelib/src/main/cpp/nativelib.cpp +++ b/nativelib/src/main/cpp/nativelib.cpp @@ -1,33 +1,33 @@ #include -#include #include #include #include #include "libevdev/libevdev.h" +#define LOG_TAG "KeyMapperNative" + +#include "logging.h" + extern "C" JNIEXPORT jstring JNICALL Java_io_github_sds100_keymapper_nativelib_EvdevService_stringFromJNI(JNIEnv *env, jobject /* this */) { - std::string hello = "Hello from C++"; - + char *input_file_path = "/dev/input/event3"; struct libevdev *dev = NULL; int fd; int rc = 1; - std::string input_file_path = "/dev/input/event3"; - fd = open(input_file_path.c_str(), O_RDONLY); + fd = open(input_file_path, O_RDONLY); if (fd == -1) { - __android_log_print(ANDROID_LOG_ERROR, "Key Mapper", "Failed to open input file (%s)", - input_file_path.c_str()); + LOGE("Failed to open input file (%s)", + input_file_path); return env->NewStringUTF("Failed"); } rc = libevdev_new_from_fd(fd, &dev); if (rc < 0) { - __android_log_print(ANDROID_LOG_ERROR, "Key Mapper", "Failed to init libevdev (%s)", - strerror(-rc)); + LOGE("Failed to init libevdev"); return env->NewStringUTF("Failed to init"); } @@ -56,5 +56,5 @@ Java_io_github_sds100_keymapper_nativelib_EvdevService_stringFromJNI(JNIEnv *env ev.value); } while (rc == 1 || rc == 0 || rc == -EAGAIN); - return env->NewStringUTF(hello.c_str()); + return env->NewStringUTF("Hello!"); } \ No newline at end of file diff --git a/nativelib/src/main/cpp/selinux.cpp b/nativelib/src/main/cpp/selinux.cpp new file mode 100644 index 0000000000..9b9ebc63cf --- /dev/null +++ b/nativelib/src/main/cpp/selinux.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include +#include +#include +#include "selinux.h" + +namespace se { + + static int __getcon(char **context) { + int fd = open("/proc/self/attr/current", O_RDONLY | O_CLOEXEC); + if (fd < 0) + return fd; + + char *buf; + size_t size; + int errno_hold; + ssize_t ret; + + size = sysconf(_SC_PAGE_SIZE); + buf = (char *) malloc(size); + if (!buf) { + ret = -1; + goto out; + } + memset(buf, 0, size); + + do { + ret = read(fd, buf, size - 1); + } while (ret < 0 && errno == EINTR); + if (ret < 0) + goto out2; + + if (ret == 0) { + *context = nullptr; + goto out2; + } + + *context = strdup(buf); + if (!(*context)) { + ret = -1; + goto out2; + } + ret = 0; + out2: + free(buf); + out: + errno_hold = errno; + close(fd); + errno = errno_hold; + return 0; + } + + static int __setcon(const char *ctx) { + int fd = open("/proc/self/attr/current", O_WRONLY | O_CLOEXEC); + if (fd < 0) + return fd; + size_t len = strlen(ctx) + 1; + ssize_t rc = write(fd, ctx, len); + close(fd); + return rc != len; + } + + static int __setfilecon(const char *path, const char *ctx) { + int rc = syscall(__NR_setxattr, path, "security.selinux"/*XATTR_NAME_SELINUX*/, ctx, + strlen(ctx) + 1, 0); + if (rc) { + errno = -rc; + return -1; + } + return 0; + } + + static int __selinux_check_access(const char *scon, const char *tcon, + const char *tclass, const char *perm, void *auditdata) { + return 0; + } + + static void __freecon(char *con) { + free(con); + } + + getcon_t *getcon = __getcon; + setcon_t *setcon = __setcon; + setfilecon_t *setfilecon = __setfilecon; + selinux_check_access_t *selinux_check_access = __selinux_check_access; + freecon_t *freecon = __freecon; + + void init() { + if (access("/system/lib/libselinux.so", F_OK) != 0 && access("/system/lib64/libselinux.so", F_OK) != 0) + return; + + void *handle = dlopen("libselinux.so", RTLD_LAZY | RTLD_LOCAL); + if (handle == nullptr) + return; + + getcon = (getcon_t *) dlsym(handle, "getcon"); + setcon = (setcon_t *) dlsym(handle, "setcon"); + setfilecon = (setfilecon_t *) dlsym(handle, "setfilecon"); + selinux_check_access = (selinux_check_access_t *) dlsym(handle, "selinux_check_access"); + freecon = (freecon_t *) (dlsym(handle, "freecon")); + } +} diff --git a/nativelib/src/main/cpp/selinux.h b/nativelib/src/main/cpp/selinux.h new file mode 100644 index 0000000000..f5e47eb291 --- /dev/null +++ b/nativelib/src/main/cpp/selinux.h @@ -0,0 +1,21 @@ +#ifndef SELINUX_H +#define SELINUX_H + +namespace se { + void init(); + + using getcon_t = int(char **); + using setcon_t = int(const char *); + using setfilecon_t = int(const char *, const char *); + using selinux_check_access_t = int(const char *, const char *, const char *, const char *, + void *); + using freecon_t = void(char *); + + extern getcon_t *getcon; + extern setcon_t *setcon; + extern setfilecon_t *setfilecon; + extern selinux_check_access_t *selinux_check_access; + extern freecon_t *freecon; +} + +#endif // SELINUX_H diff --git a/nativelib/src/main/cpp/starter.cpp b/nativelib/src/main/cpp/starter.cpp new file mode 100644 index 0000000000..bd9bb865bb --- /dev/null +++ b/nativelib/src/main/cpp/starter.cpp @@ -0,0 +1,306 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "android.h" +#include "misc.h" +#include "selinux.h" +#include "cgroup.h" +#include "logging.h" + +#ifdef DEBUG +#define JAVA_DEBUGGABLE +#endif + +#define perrorf(...) fprintf(stderr, __VA_ARGS__) + +#define EXIT_FATAL_SET_CLASSPATH 3 +#define EXIT_FATAL_FORK 4 +#define EXIT_FATAL_APP_PROCESS 5 +#define EXIT_FATAL_UID 6 +#define EXIT_FATAL_PM_PATH 7 +#define EXIT_FATAL_KILL 9 +#define EXIT_FATAL_BINDER_BLOCKED_BY_SELINUX 10 + +#define PACKAGE_NAME "moe.shizuku.privileged.api" +#define SERVER_NAME "shizuku_server" +#define SERVER_CLASS_PATH "rikka.shizuku.server.ShizukuService" + +#if defined(__arm__) +#define ABI "armeabi-v7a" +#elif defined(__i386__) +#define ABI "x86" +#elif defined(__x86_64__) +#define ABI "x86_64" +#elif defined(__aarch64__) +#define ABI "arm64-v8a" +#endif + +static void run_server(const char *dex_path, const char *main_class, const char *process_name) { + if (setenv("CLASSPATH", dex_path, true)) { + LOGE("can't set CLASSPATH\n"); + exit(EXIT_FATAL_SET_CLASSPATH); + } + +#define ARG(v) char **v = nullptr; \ + char buf_##v[PATH_MAX]; \ + size_t v_size = 0; \ + uintptr_t v_current = 0; +#define ARG_PUSH(v, arg) v_size += sizeof(char *); \ +if (v == nullptr) { \ + v = (char **) malloc(v_size); \ +} else { \ + v = (char **) realloc(v, v_size);\ +} \ +v_current = (uintptr_t) v + v_size - sizeof(char *); \ +*((char **) v_current) = arg ? strdup(arg) : nullptr; + +#define ARG_END(v) ARG_PUSH(v, nullptr) + +#define ARG_PUSH_FMT(v, fmt, ...) snprintf(buf_##v, PATH_MAX, fmt, __VA_ARGS__); \ + ARG_PUSH(v, buf_##v) + +#ifdef JAVA_DEBUGGABLE +#define ARG_PUSH_DEBUG_ONLY(v, arg) ARG_PUSH(v, arg) +#define ARG_PUSH_DEBUG_VM_PARAMS(v) \ + if (android::GetApiLevel() >= 30) { \ + ARG_PUSH(v, "-Xcompiler-option"); \ + ARG_PUSH(v, "--debuggable"); \ + ARG_PUSH(v, "-XjdwpProvider:adbconnection"); \ + ARG_PUSH(v, "-XjdwpOptions:suspend=n,server=y"); \ + } else if (android::GetApiLevel() >= 28) { \ + ARG_PUSH(v, "-Xcompiler-option"); \ + ARG_PUSH(v, "--debuggable"); \ + ARG_PUSH(v, "-XjdwpProvider:internal"); \ + ARG_PUSH(v, "-XjdwpOptions:transport=dt_android_adb,suspend=n,server=y"); \ + } else { \ + ARG_PUSH(v, "-Xcompiler-option"); \ + ARG_PUSH(v, "--debuggable"); \ + ARG_PUSH(v, "-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y"); \ + } +#else +#define ARG_PUSH_DEBUG_VM_PARAMS(v) +#define ARG_PUSH_DEBUG_ONLY(v, arg) +#endif + + char lib_path[PATH_MAX]{0}; + snprintf(lib_path, PATH_MAX, "%s!/lib/%s", dex_path, ABI); + + ARG(argv) + ARG_PUSH(argv, "/system/bin/app_process") + ARG_PUSH_FMT(argv, "-Djava.class.path=%s", dex_path) + ARG_PUSH_FMT(argv, "-Dshizuku.library.path=%s", lib_path) + ARG_PUSH_DEBUG_VM_PARAMS(argv) + ARG_PUSH(argv, "/system/bin") + ARG_PUSH_FMT(argv, "--nice-name=%s", process_name) + ARG_PUSH(argv, main_class) + ARG_PUSH_DEBUG_ONLY(argv, "--debug") + ARG_END(argv) + + LOGD("exec app_process"); + + if (execvp((const char *) argv[0], argv)) { + exit(EXIT_FATAL_APP_PROCESS); + } +} + +static void start_server(const char *path, const char *main_class, const char *process_name) { + if (daemon(false, false) == 0) { + LOGD("child"); + run_server(path, main_class, process_name); + } else { + perrorf("fatal: can't fork\n"); + exit(EXIT_FATAL_FORK); + } +} + +static int check_selinux(const char *s, const char *t, const char *c, const char *p) { + int res = se::selinux_check_access(s, t, c, p, nullptr); +#ifndef DEBUG + if (res != 0) { +#endif + printf("info: selinux_check_access %s %s %s %s: %d\n", s, t, c, p, res); + fflush(stdout); +#ifndef DEBUG + } +#endif + return res; +} + +static int switch_cgroup() { + int s_cuid, s_cpid; + int spid = getpid(); + + if (cgroup::get_cgroup(spid, &s_cuid, &s_cpid) != 0) { + printf("warn: can't read cgroup\n"); + fflush(stdout); + return -1; + } + + printf("info: cgroup is /uid_%d/pid_%d\n", s_cuid, s_cpid); + fflush(stdout); + + if (cgroup::switch_cgroup(spid, -1, -1) != 0) { + printf("warn: can't switch cgroup\n"); + fflush(stdout); + return -1; + } + + if (cgroup::get_cgroup(spid, &s_cuid, &s_cpid) != 0) { + printf("info: switch cgroup succeeded\n"); + fflush(stdout); + return 0; + } + + printf("warn: can't switch self, current cgroup is /uid_%d/pid_%d\n", s_cuid, s_cpid); + fflush(stdout); + return -1; +} + +char *context = nullptr; + +int starter_main(int argc, char *argv[]) { + char *apk_path = nullptr; + for (int i = 0; i < argc; ++i) { + if (strncmp(argv[i], "--apk=", 6) == 0) { + apk_path = argv[i] + 6; + } + } + + int uid = getuid(); + if (uid != 0 && uid != 2000) { + perrorf("fatal: run Shizuku from non root nor adb user (uid=%d).\n", uid); + exit(EXIT_FATAL_UID); + } + + se::init(); + + if (uid == 0) { + chown("/data/local/tmp/shizuku_starter", 2000, 2000); + se::setfilecon("/data/local/tmp/shizuku_starter", "u:object_r:shell_data_file:s0"); + switch_cgroup(); + + int sdkLevel = 0; + char buf[PROP_VALUE_MAX + 1]; + if (__system_property_get("ro.build.version.sdk", buf) > 0) + sdkLevel = atoi(buf); + + if (sdkLevel >= 29) { + printf("info: switching mount namespace to init...\n"); + switch_mnt_ns(1); + } + } + + if (uid == 0) { + if (se::getcon(&context) == 0) { + int res = 0; + + res |= check_selinux("u:r:untrusted_app:s0", context, "binder", "call"); + res |= check_selinux("u:r:untrusted_app:s0", context, "binder", "transfer"); + + if (res != 0) { + perrorf("fatal: the su you are using does not allow app (u:r:untrusted_app:s0) to connect to su (%s) with binder.\n", + context); + exit(EXIT_FATAL_BINDER_BLOCKED_BY_SELINUX); + } + se::freecon(context); + } + } + + mkdir("/data/local/tmp/shizuku", 0707); + chmod("/data/local/tmp/shizuku", 0707); + if (uid == 0) { + chown("/data/local/tmp/shizuku", 2000, 2000); + se::setfilecon("/data/local/tmp/shizuku", "u:object_r:shell_data_file:s0"); + } + + printf("info: starter begin\n"); + fflush(stdout); + + // kill old server + printf("info: killing old process...\n"); + fflush(stdout); + + foreach_proc([](pid_t pid) { + if (pid == getpid()) return; + + char name[1024]; + if (get_proc_name(pid, name, 1024) != 0) return; + + if (strcmp(SERVER_NAME, name) != 0 + && strcmp("shizuku_server_legacy", name) != 0) + return; + + if (kill(pid, SIGKILL) == 0) + printf("info: killed %d (%s)\n", pid, name); + else if (errno == EPERM) { + perrorf("fatal: can't kill %d, please try to stop existing Shizuku from app first.\n", pid); + exit(EXIT_FATAL_KILL); + } else { + printf("warn: failed to kill %d (%s)\n", pid, name); + } + }); + + if (access(apk_path, R_OK) == 0) { + printf("info: use apk path from argv\n"); + fflush(stdout); + } + + if (!apk_path) { + auto f = popen("pm path " PACKAGE_NAME, "r"); + if (f) { + char line[PATH_MAX]{0}; + fgets(line, PATH_MAX, f); + trim(line); + if (strstr(line, "package:") == line) { + apk_path = line + strlen("package:"); + } + pclose(f); + } + } + + if (!apk_path) { + perrorf("fatal: can't get path of manager\n"); + exit(EXIT_FATAL_PM_PATH); + } + + printf("info: apk path is %s\n", apk_path); + if (access(apk_path, R_OK) != 0) { + perrorf("fatal: can't access manager %s\n", apk_path); + exit(EXIT_FATAL_PM_PATH); + } + + printf("info: starting server...\n"); + fflush(stdout); + LOGD("start_server"); + start_server(apk_path, SERVER_CLASS_PATH, SERVER_NAME); + exit(EXIT_SUCCESS); +} + +using main_func = int (*)(int, char *[]); + +static main_func applet_main[] = {starter_main, nullptr}; + +int main(int argc, char **argv) { + std::string_view base = basename(argv[0]); + + LOGD("applet %s", base.data()); + + constexpr const char *applet_names[] = {"shizuku_starter", nullptr}; + + for (int i = 0; applet_names[i]; ++i) { + if (base == applet_names[i]) { + return (*applet_main[i])(argc, argv); + } + } + + return 1; +} diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbClient.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbClient.kt new file mode 100644 index 0000000000..f2f822e80a --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbClient.kt @@ -0,0 +1,180 @@ +package moe.shizuku.manager.adb + +import android.os.Build +import android.util.Log +import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_RSAPUBLICKEY +import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_SIGNATURE +import moe.shizuku.manager.adb.AdbProtocol.ADB_AUTH_TOKEN +import moe.shizuku.manager.adb.AdbProtocol.A_AUTH +import moe.shizuku.manager.adb.AdbProtocol.A_CLSE +import moe.shizuku.manager.adb.AdbProtocol.A_CNXN +import moe.shizuku.manager.adb.AdbProtocol.A_MAXDATA +import moe.shizuku.manager.adb.AdbProtocol.A_OKAY +import moe.shizuku.manager.adb.AdbProtocol.A_OPEN +import moe.shizuku.manager.adb.AdbProtocol.A_STLS +import moe.shizuku.manager.adb.AdbProtocol.A_STLS_VERSION +import moe.shizuku.manager.adb.AdbProtocol.A_VERSION +import moe.shizuku.manager.adb.AdbProtocol.A_WRTE +import java.io.Closeable +import java.io.DataInputStream +import java.io.DataOutputStream +import java.net.Socket +import java.nio.ByteBuffer +import java.nio.ByteOrder +import javax.net.ssl.SSLSocket + +private const val TAG = "AdbClient" + +class AdbClient(private val host: String, private val port: Int, private val key: AdbKey) : Closeable { + + private lateinit var socket: Socket + private lateinit var plainInputStream: DataInputStream + private lateinit var plainOutputStream: DataOutputStream + + private var useTls = false + + private lateinit var tlsSocket: SSLSocket + private lateinit var tlsInputStream: DataInputStream + private lateinit var tlsOutputStream: DataOutputStream + + private val inputStream get() = if (useTls) tlsInputStream else plainInputStream + private val outputStream get() = if (useTls) tlsOutputStream else plainOutputStream + + fun connect() { + socket = Socket(host, port) + socket.tcpNoDelay = true + plainInputStream = DataInputStream(socket.getInputStream()) + plainOutputStream = DataOutputStream(socket.getOutputStream()) + + write(A_CNXN, A_VERSION, A_MAXDATA, "host::") + + var message = read() + if (message.command == A_STLS) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + error("Connect to adb with TLS is not supported before Android 9") + } + write(A_STLS, A_STLS_VERSION, 0) + + val sslContext = key.sslContext + tlsSocket = sslContext.socketFactory.createSocket(socket, host, port, true) as SSLSocket + tlsSocket.startHandshake() + Log.d(TAG, "Handshake succeeded.") + + tlsInputStream = DataInputStream(tlsSocket.inputStream) + tlsOutputStream = DataOutputStream(tlsSocket.outputStream) + useTls = true + + message = read() + } else if (message.command == A_AUTH) { + if (message.command != A_AUTH && message.arg0 != ADB_AUTH_TOKEN) error("not A_AUTH ADB_AUTH_TOKEN") + write(A_AUTH, ADB_AUTH_SIGNATURE, 0, key.sign(message.data)) + + message = read() + if (message.command != A_CNXN) { + write(A_AUTH, ADB_AUTH_RSAPUBLICKEY, 0, key.adbPublicKey) + message = read() + } + } + + if (message.command != A_CNXN) error("not A_CNXN") + } + + fun shellCommand(command: String, listener: ((ByteArray) -> Unit)?) { + val localId = 1 + write(A_OPEN, localId, 0, "shell:$command") + + var message = read() + when (message.command) { + A_OKAY -> { + while (true) { + message = read() + val remoteId = message.arg0 + if (message.command == A_WRTE) { + if (message.data_length > 0) { + listener?.invoke(message.data!!) + } + write(A_OKAY, localId, remoteId) + } else if (message.command == A_CLSE) { + write(A_CLSE, localId, remoteId) + break + } else { + error("not A_WRTE or A_CLSE") + } + } + } + + A_CLSE -> { + val remoteId = message.arg0 + write(A_CLSE, localId, remoteId) + } + + else -> { + error("not A_OKAY or A_CLSE") + } + } + } + + private fun write(command: Int, arg0: Int, arg1: Int, data: ByteArray? = null) = write(AdbMessage(command, arg0, arg1, data)) + + private fun write(command: Int, arg0: Int, arg1: Int, data: String) = write(AdbMessage(command, arg0, arg1, data)) + + private fun write(message: AdbMessage) { + outputStream.write(message.toByteArray()) + outputStream.flush() + Log.d(TAG, "write ${message.toStringShort()}") + } + + private fun read(): AdbMessage { + val buffer = ByteBuffer.allocate(AdbMessage.HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN) + + inputStream.readFully(buffer.array(), 0, 24) + + val command = buffer.int + val arg0 = buffer.int + val arg1 = buffer.int + val dataLength = buffer.int + val checksum = buffer.int + val magic = buffer.int + val data: ByteArray? + if (dataLength >= 0) { + data = ByteArray(dataLength) + inputStream.readFully(data, 0, dataLength) + } else { + data = null + } + val message = AdbMessage(command, arg0, arg1, dataLength, checksum, magic, data) + message.validateOrThrow() + Log.d(TAG, "read ${message.toStringShort()}") + return message + } + + override fun close() { + try { + plainInputStream.close() + } catch (e: Throwable) { + } + try { + plainOutputStream.close() + } catch (e: Throwable) { + } + try { + socket.close() + } catch (e: Exception) { + } + + if (useTls) { + try { + tlsInputStream.close() + } catch (e: Throwable) { + } + try { + tlsOutputStream.close() + } catch (e: Throwable) { + } + try { + tlsSocket.close() + } catch (e: Exception) { + } + } + } +} diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbException.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbException.kt new file mode 100644 index 0000000000..6aef8dadb7 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbException.kt @@ -0,0 +1,16 @@ +package moe.shizuku.manager.adb + +@Suppress("NOTHING_TO_INLINE") +inline fun adbError(message: Any): Nothing = throw AdbException(message.toString()) + +open class AdbException : Exception { + + constructor(message: String, cause: Throwable?) : super(message, cause) + constructor(message: String) : super(message) + constructor(cause: Throwable) : super(cause) + constructor() +} + +class AdbInvalidPairingCodeException : AdbException() + +class AdbKeyException(cause: Throwable) : AdbException(cause) diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt new file mode 100644 index 0000000000..102082dd9d --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt @@ -0,0 +1,331 @@ +package moe.shizuku.manager.adb + +import android.annotation.SuppressLint +import android.content.SharedPreferences +import android.os.Build +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.content.edit +import org.bouncycastle.asn1.x500.X500Name +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo +import org.bouncycastle.cert.X509v3CertificateBuilder +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import rikka.core.ktx.unsafeLazy +import java.io.ByteArrayInputStream +import java.math.BigInteger +import java.net.Socket +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.security.* +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.security.interfaces.RSAPrivateKey +import java.security.interfaces.RSAPublicKey +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.RSAKeyGenParameterSpec +import java.security.spec.RSAPublicKeySpec +import java.util.* +import javax.crypto.Cipher +import javax.crypto.KeyGenerator +import javax.crypto.spec.GCMParameterSpec +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLEngine +import javax.net.ssl.X509ExtendedKeyManager +import javax.net.ssl.X509ExtendedTrustManager + +private const val TAG = "AdbKey" + +class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { + + companion object { + + private const val ANDROID_KEYSTORE = "AndroidKeyStore" + private const val ENCRYPTION_KEY_ALIAS = "_adbkey_encryption_key_" + private const val TRANSFORMATION = "AES/GCM/NoPadding" + + private const val IV_SIZE_IN_BYTES = 12 + private const val TAG_SIZE_IN_BYTES = 16 + + private val PADDING = byteArrayOf( + 0x00, 0x01, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0x00, + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, + 0x04, 0x14) + } + + private val encryptionKey: Key + + private val privateKey: RSAPrivateKey + private val publicKey: RSAPublicKey + private val certificate: X509Certificate + + init { + this.encryptionKey = getOrCreateEncryptionKey() ?: error("Failed to generate encryption key with AndroidKeyManager.") + + this.privateKey = getOrCreatePrivateKey() + this.publicKey = KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(privateKey.modulus, RSAKeyGenParameterSpec.F4)) as RSAPublicKey + + val signer = JcaContentSignerBuilder("SHA256withRSA").build(privateKey) + val x509Certificate = X509v3CertificateBuilder(X500Name("CN=00"), + BigInteger.ONE, + Date(0), + Date(2461449600 * 1000), + Locale.ROOT, + X500Name("CN=00"), + SubjectPublicKeyInfo.getInstance(publicKey.encoded) + ).build(signer) + this.certificate = CertificateFactory.getInstance("X.509") + .generateCertificate(ByteArrayInputStream(x509Certificate.encoded)) as X509Certificate + + Log.d(TAG, privateKey.toString()) + } + + val adbPublicKey: ByteArray by unsafeLazy { + publicKey.adbEncoded(name) + } + + private fun getOrCreateEncryptionKey(): Key? { + val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE) + keyStore.load(null) + + return keyStore.getKey(ENCRYPTION_KEY_ALIAS, null) ?: run { + val parameterSpec = KeyGenParameterSpec.Builder(ENCRYPTION_KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(256) + .build() + val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE) + keyGenerator.init(parameterSpec) + keyGenerator.generateKey() + } + } + + private fun encrypt(plaintext: ByteArray, aad: ByteArray?): ByteArray? { + if (plaintext.size > Int.MAX_VALUE - IV_SIZE_IN_BYTES - TAG_SIZE_IN_BYTES) { + return null + } + val ciphertext = ByteArray(IV_SIZE_IN_BYTES + plaintext.size + TAG_SIZE_IN_BYTES) + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.ENCRYPT_MODE, encryptionKey) + cipher.updateAAD(aad) + cipher.doFinal(plaintext, 0, plaintext.size, ciphertext, IV_SIZE_IN_BYTES) + System.arraycopy(cipher.iv, 0, ciphertext, 0, IV_SIZE_IN_BYTES) + return ciphertext + } + + private fun decrypt(ciphertext: ByteArray, aad: ByteArray?): ByteArray? { + if (ciphertext.size < IV_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES) { + return null + } + val params = GCMParameterSpec(8 * TAG_SIZE_IN_BYTES, ciphertext, 0, IV_SIZE_IN_BYTES) + val cipher = Cipher.getInstance(TRANSFORMATION) + cipher.init(Cipher.DECRYPT_MODE, encryptionKey, params) + cipher.updateAAD(aad) + return cipher.doFinal(ciphertext, IV_SIZE_IN_BYTES, ciphertext.size - IV_SIZE_IN_BYTES) + } + + private fun getOrCreatePrivateKey(): RSAPrivateKey { + var privateKey: RSAPrivateKey? = null + + val aad = ByteArray(16) + "adbkey".toByteArray().copyInto(aad) + + var ciphertext = adbKeyStore.get() + if (ciphertext != null) { + try { + val plaintext = decrypt(ciphertext, aad) + + val keyFactory = KeyFactory.getInstance("RSA") + privateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(plaintext)) as RSAPrivateKey + } catch (e: Exception) { + } + } + if (privateKey == null) { + val keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA) + keyPairGenerator.initialize(RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4)) + val keyPair = keyPairGenerator.generateKeyPair() + privateKey = keyPair.private as RSAPrivateKey + + ciphertext = encrypt(privateKey.encoded, aad) + if (ciphertext != null) { + adbKeyStore.put(ciphertext) + } + } + return privateKey + } + + fun sign(data: ByteArray?): ByteArray { + val cipher = Cipher.getInstance("RSA/ECB/NoPadding") + cipher.init(Cipher.ENCRYPT_MODE, privateKey) + cipher.update(PADDING) + return cipher.doFinal(data) + } + + private val keyManager + get() = object : X509ExtendedKeyManager() { + private val alias = "key" + + override fun chooseClientAlias(keyTypes: Array, issuers: Array?, socket: Socket?): String? { + Log.d(TAG, "chooseClientAlias: keyType=${keyTypes.contentToString()}, issuers=${issuers?.contentToString()}") + for (keyType in keyTypes) { + if (keyType == "RSA") return alias + } + return null + } + + override fun getCertificateChain(alias: String?): Array? { + Log.d(TAG, "getCertificateChain: alias=$alias") + return if (alias == this.alias) arrayOf(certificate) else null + } + + override fun getPrivateKey(alias: String?): PrivateKey? { + Log.d(TAG, "getPrivateKey: alias=$alias") + return if (alias == this.alias) privateKey else null + } + + override fun getClientAliases(keyType: String?, issuers: Array?): Array? { + return null + } + + override fun getServerAliases(keyType: String, issuers: Array?): Array? { + return null + } + + override fun chooseServerAlias(keyType: String, issuers: Array?, socket: Socket?): String? { + return null + } + } + + + private val trustManager + get() = + @RequiresApi(Build.VERSION_CODES.R) + object : X509ExtendedTrustManager() { + + @SuppressLint("TrustAllX509TrustManager") + override fun checkClientTrusted(chain: Array?, authType: String?, socket: Socket?) { + } + + @SuppressLint("TrustAllX509TrustManager") + override fun checkClientTrusted(chain: Array?, authType: String?, engine: SSLEngine?) { + } + + @SuppressLint("TrustAllX509TrustManager") + override fun checkClientTrusted(chain: Array?, authType: String?) { + } + + @SuppressLint("TrustAllX509TrustManager") + override fun checkServerTrusted(chain: Array?, authType: String?, socket: Socket?) { + } + + @SuppressLint("TrustAllX509TrustManager") + override fun checkServerTrusted(chain: Array?, authType: String?, engine: SSLEngine?) { + } + + @SuppressLint("TrustAllX509TrustManager") + override fun checkServerTrusted(chain: Array?, authType: String?) { + } + + override fun getAcceptedIssuers(): Array { + return emptyArray() + } + } + + @delegate:RequiresApi(Build.VERSION_CODES.R) + val sslContext: SSLContext by unsafeLazy { + val sslContext = SSLContext.getInstance("TLSv1.3") + sslContext.init(arrayOf(keyManager), arrayOf(trustManager), SecureRandom()) + sslContext + } +} + +interface AdbKeyStore { + + fun put(bytes: ByteArray) + + fun get(): ByteArray? +} + +class PreferenceAdbKeyStore(private val preference: SharedPreferences) : AdbKeyStore { + + private val preferenceKey = "adbkey" + + override fun put(bytes: ByteArray) { + preference.edit { putString(preferenceKey, String(Base64.encode(bytes, Base64.NO_WRAP))) } + } + + override fun get(): ByteArray? { + if (!preference.contains(preferenceKey)) return null + return Base64.decode(preference.getString(preferenceKey, null), Base64.NO_WRAP) + } +} + +const val ANDROID_PUBKEY_MODULUS_SIZE = 2048 / 8 +const val ANDROID_PUBKEY_MODULUS_SIZE_WORDS = ANDROID_PUBKEY_MODULUS_SIZE / 4 +const val RSAPublicKey_Size = 524 + +private fun BigInteger.toAdbEncoded(): IntArray { + // little-endian integer with padding zeros in the end + + val endcoded = IntArray(ANDROID_PUBKEY_MODULUS_SIZE_WORDS) + val r32 = BigInteger.ZERO.setBit(32) + + var tmp = this.add(BigInteger.ZERO) + for (i in 0 until ANDROID_PUBKEY_MODULUS_SIZE_WORDS) { + val out = tmp.divideAndRemainder(r32) + tmp = out[0] + endcoded[i] = out[1].toInt() + } + return endcoded +} + +private fun RSAPublicKey.adbEncoded(name: String): ByteArray { + // https://cs.android.com/android/platform/superproject/+/android-10.0.0_r30:system/core/libcrypto_utils/android_pubkey.c + + /* + typedef struct RSAPublicKey { + uint32_t modulus_size_words; // ANDROID_PUBKEY_MODULUS_SIZE + uint32_t n0inv; // n0inv = -1 / N[0] mod 2^32 + uint8_t modulus[ANDROID_PUBKEY_MODULUS_SIZE]; + uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE]; // rr = (2^(rsa_size)) ^ 2 mod N + uint32_t exponent; + } RSAPublicKey; + */ + + val r32 = BigInteger.ZERO.setBit(32) + val n0inv = modulus.remainder(r32).modInverse(r32).negate() + val r = BigInteger.ZERO.setBit(ANDROID_PUBKEY_MODULUS_SIZE * 8) + val rr = r.modPow(BigInteger.valueOf(2), modulus) + + val buffer = ByteBuffer.allocate(RSAPublicKey_Size).order(ByteOrder.LITTLE_ENDIAN) + buffer.putInt(ANDROID_PUBKEY_MODULUS_SIZE_WORDS) + buffer.putInt(n0inv.toInt()) + modulus.toAdbEncoded().forEach { buffer.putInt(it) } + rr.toAdbEncoded().forEach { buffer.putInt(it) } + buffer.putInt(publicExponent.toInt()) + + val base64Bytes = Base64.encode(buffer.array(), Base64.NO_WRAP) + val nameBytes = " $name\u0000".toByteArray() + val bytes = ByteArray(base64Bytes.size + nameBytes.size) + base64Bytes.copyInto(bytes) + nameBytes.copyInto(bytes, base64Bytes.size) + return bytes +} diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMdns.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMdns.kt new file mode 100644 index 0000000000..4cbddf52d1 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMdns.kt @@ -0,0 +1,135 @@ +package moe.shizuku.manager.adb + +import android.content.Context +import android.net.nsd.NsdManager +import android.net.nsd.NsdServiceInfo +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.lifecycle.MutableLiveData +import java.io.IOException +import java.net.InetSocketAddress +import java.net.NetworkInterface +import java.net.ServerSocket + +@RequiresApi(Build.VERSION_CODES.R) +class AdbMdns( + context: Context, private val serviceType: String, + private val port: MutableLiveData +) { + + private var registered = false + private var running = false + private var serviceName: String? = null + private val listener: DiscoveryListener + private val nsdManager: NsdManager = context.getSystemService(NsdManager::class.java) + + fun start() { + if (running) return + running = true + if (!registered) { + nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener) + } + } + + fun stop() { + if (!running) return + running = false + if (registered) { + nsdManager.stopServiceDiscovery(listener) + } + } + + private fun onDiscoveryStart() { + registered = true + } + + private fun onDiscoveryStop() { + registered = false + } + + private fun onServiceFound(info: NsdServiceInfo) { + nsdManager.resolveService(info, ResolveListener(this)) + } + + private fun onServiceLost(info: NsdServiceInfo) { + if (info.serviceName == serviceName) port.postValue(-1) + } + + private fun onServiceResolved(resolvedService: NsdServiceInfo) { + if (running && NetworkInterface.getNetworkInterfaces() + .asSequence() + .any { networkInterface -> + networkInterface.inetAddresses + .asSequence() + .any { resolvedService.host.hostAddress == it.hostAddress } + } + && isPortAvailable(resolvedService.port) + ) { + serviceName = resolvedService.serviceName + port.postValue(resolvedService.port) + } + } + + private fun isPortAvailable(port: Int) = try { + ServerSocket().use { + it.bind(InetSocketAddress("127.0.0.1", port), 1) + false + } + } catch (e: IOException) { + true + } + + internal class DiscoveryListener(private val adbMdns: AdbMdns) : NsdManager.DiscoveryListener { + override fun onDiscoveryStarted(serviceType: String) { + Log.v(TAG, "onDiscoveryStarted: $serviceType") + + adbMdns.onDiscoveryStart() + } + + override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) { + Log.v(TAG, "onStartDiscoveryFailed: $serviceType, $errorCode") + } + + override fun onDiscoveryStopped(serviceType: String) { + Log.v(TAG, "onDiscoveryStopped: $serviceType") + + adbMdns.onDiscoveryStop() + } + + override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) { + Log.v(TAG, "onStopDiscoveryFailed: $serviceType, $errorCode") + } + + override fun onServiceFound(serviceInfo: NsdServiceInfo) { + Log.v(TAG, "onServiceFound: ${serviceInfo.serviceName}") + + adbMdns.onServiceFound(serviceInfo) + } + + override fun onServiceLost(serviceInfo: NsdServiceInfo) { + Log.v(TAG, "onServiceLost: ${serviceInfo.serviceName}") + + adbMdns.onServiceLost(serviceInfo) + } + } + + internal class ResolveListener(private val adbMdns: AdbMdns) : NsdManager.ResolveListener { + override fun onResolveFailed(nsdServiceInfo: NsdServiceInfo, i: Int) {} + + override fun onServiceResolved(nsdServiceInfo: NsdServiceInfo) { + adbMdns.onServiceResolved(nsdServiceInfo) + } + + } + + companion object { + const val TLS_CONNECT = "_adb-tls-connect._tcp" + const val TLS_PAIRING = "_adb-tls-pairing._tcp" + const val TAG = "AdbMdns" + } + + init { + listener = DiscoveryListener(this) + } +} diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMessage.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMessage.kt new file mode 100644 index 0000000000..64b7b3a9f3 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMessage.kt @@ -0,0 +1,132 @@ +package moe.shizuku.manager.adb + +import moe.shizuku.manager.adb.AdbProtocol.A_AUTH +import moe.shizuku.manager.adb.AdbProtocol.A_CLSE +import moe.shizuku.manager.adb.AdbProtocol.A_CNXN +import moe.shizuku.manager.adb.AdbProtocol.A_OKAY +import moe.shizuku.manager.adb.AdbProtocol.A_OPEN +import moe.shizuku.manager.adb.AdbProtocol.A_STLS +import moe.shizuku.manager.adb.AdbProtocol.A_SYNC +import moe.shizuku.manager.adb.AdbProtocol.A_WRTE +import java.nio.ByteBuffer +import java.nio.ByteOrder + +class AdbMessage( + val command: Int, + val arg0: Int, + val arg1: Int, + val data_length: Int, + val data_crc32: Int, + val magic: Int, + val data: ByteArray? +) { + + constructor(command: Int, arg0: Int, arg1: Int, data: String) : this( + command, + arg0, + arg1, + "$data\u0000".toByteArray()) + + constructor(command: Int, arg0: Int, arg1: Int, data: ByteArray?) : this( + command, + arg0, + arg1, + data?.size ?: 0, + crc32(data), + (command.toLong() xor 0xFFFFFFFF).toInt(), + data) + + fun validate(): Boolean { + if (command != magic xor -0x1) return false + if (data_length != 0 && crc32(data) != data_crc32) return false + return true + } + + fun validateOrThrow() { + if (!validate()) throw IllegalArgumentException("bad message ${this.toStringShort()}") + } + + fun toByteArray(): ByteArray { + val length = HEADER_LENGTH + (data?.size ?: 0) + return ByteBuffer.allocate(length).apply { + order(ByteOrder.LITTLE_ENDIAN) + putInt(command) + putInt(arg0) + putInt(arg1) + putInt(data_length) + putInt(data_crc32) + putInt(magic) + if (data != null) { + put(data) + } + }.array() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AdbMessage + + if (command != other.command) return false + if (arg0 != other.arg0) return false + if (arg1 != other.arg1) return false + if (data_length != other.data_length) return false + if (data_crc32 != other.data_crc32) return false + if (magic != other.magic) return false + if (data != null) { + if (other.data == null) return false + if (!data.contentEquals(other.data)) return false + } else if (other.data != null) return false + + return true + } + + override fun hashCode(): Int { + var result = command + result = 31 * result + arg0 + result = 31 * result + arg1 + result = 31 * result + data_length + result = 31 * result + data_crc32 + result = 31 * result + magic + result = 31 * result + (data?.contentHashCode() ?: 0) + return result + } + + override fun toString(): String { + return "AdbMessage(${toStringShort()})" + } + + fun toStringShort(): String { + val commandString = when (command) { + A_SYNC -> "A_SYNC" + A_CNXN -> "A_CNXN" + A_AUTH -> "A_AUTH" + A_OPEN -> "A_OPEN" + A_OKAY -> "A_OKAY" + A_CLSE -> "A_CLSE" + A_WRTE -> "A_WRTE" + A_STLS -> "A_STLS" + else -> command.toString() + } + return "command=$commandString, arg0=$arg0, arg1=$arg1, data_length=$data_length, data_crc32=$data_crc32, magic=$magic, data=${data?.contentToString()}" + } + + companion object { + + const val HEADER_LENGTH = 24 + + + private fun crc32(data: ByteArray?): Int { + if (data == null) return 0 + var res = 0 + for (b in data) { + if (b >= 0) + res += b + else + res += b + 256 + } + return res + } + } +} diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt new file mode 100644 index 0000000000..c03370d5f8 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt @@ -0,0 +1,316 @@ +package moe.shizuku.manager.adb + +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.android.org.conscrypt.Conscrypt +import java.io.Closeable +import java.io.DataInputStream +import java.io.DataOutputStream +import java.net.Socket +import java.nio.ByteBuffer +import java.nio.ByteOrder +import javax.net.ssl.SSLSocket + +private const val TAG = "AdbPairClient" + +private const val kCurrentKeyHeaderVersion = 1.toByte() +private const val kMinSupportedKeyHeaderVersion = 1.toByte() +private const val kMaxSupportedKeyHeaderVersion = 1.toByte() +private const val kMaxPeerInfoSize = 8192 +private const val kMaxPayloadSize = kMaxPeerInfoSize * 2 + +private const val kExportedKeyLabel = "adb-label\u0000" +private const val kExportedKeySize = 64 + +private const val kPairingPacketHeaderSize = 6 + +private class PeerInfo( + val type: Byte, + data: ByteArray) { + + val data = ByteArray(kMaxPeerInfoSize - 1) + + init { + data.copyInto(this.data, 0, 0, data.size.coerceAtMost(kMaxPeerInfoSize - 1)) + } + + enum class Type(val value: Byte) { + ADB_RSA_PUB_KEY(0.toByte()), + ADB_DEVICE_GUID(0.toByte()), + } + + fun writeTo(buffer: ByteBuffer) { + buffer.run { + put(type) + put(data) + } + + Log.d(TAG, "write PeerInfo ${toStringShort()}") + } + + override fun toString(): String { + return "PeerInfo(${toStringShort()})" + } + + fun toStringShort(): String { + return "type=$type, data=${data.contentToString()}" + } + + companion object { + + fun readFrom(buffer: ByteBuffer): PeerInfo { + val type = buffer.get() + val data = ByteArray(kMaxPeerInfoSize - 1) + buffer.get(data) + return PeerInfo(type, data) + } + } +} + +private class PairingPacketHeader( + val version: Byte, + val type: Byte, + val payload: Int) { + + enum class Type(val value: Byte) { + SPAKE2_MSG(0.toByte()), + PEER_INFO(1.toByte()) + } + + fun writeTo(buffer: ByteBuffer) { + buffer.run { + put(version) + put(type) + putInt(payload) + } + + Log.d(TAG, "write PairingPacketHeader ${toStringShort()}") + } + + override fun toString(): String { + return "PairingPacketHeader(${toStringShort()})" + } + + fun toStringShort(): String { + return "version=${version.toInt()}, type=${type.toInt()}, payload=$payload" + } + + companion object { + + fun readFrom(buffer: ByteBuffer): PairingPacketHeader? { + val version = buffer.get() + val type = buffer.get() + val payload = buffer.int + + if (version < kMinSupportedKeyHeaderVersion || version > kMaxSupportedKeyHeaderVersion) { + Log.e(TAG, "PairingPacketHeader version mismatch (us=$kCurrentKeyHeaderVersion them=${version})") + return null + } + if (type != Type.SPAKE2_MSG.value && type != Type.PEER_INFO.value) { + Log.e(TAG, "Unknown PairingPacket type=${type}") + return null + } + if (payload <= 0 || payload > kMaxPayloadSize) { + Log.e(TAG, "header payload not within a safe payload size (size=${payload})") + return null + } + + val header = PairingPacketHeader(version, type, payload) + Log.d(TAG, "read PairingPacketHeader ${header.toStringShort()}") + return header + } + } +} + +private class PairingContext private constructor(private val nativePtr: Long) { + + val msg: ByteArray + + init { + msg = nativeMsg(nativePtr) + } + + fun initCipher(theirMsg: ByteArray) = nativeInitCipher(nativePtr, theirMsg) + + fun encrypt(`in`: ByteArray) = nativeEncrypt(nativePtr, `in`) + + fun decrypt(`in`: ByteArray) = nativeDecrypt(nativePtr, `in`) + + fun destroy() = nativeDestroy(nativePtr) + + private external fun nativeMsg(nativePtr: Long): ByteArray + + private external fun nativeInitCipher(nativePtr: Long, theirMsg: ByteArray): Boolean + + private external fun nativeEncrypt(nativePtr: Long, inbuf: ByteArray): ByteArray? + + private external fun nativeDecrypt(nativePtr: Long, inbuf: ByteArray): ByteArray? + + private external fun nativeDestroy(nativePtr: Long) + + companion object { + + fun create(password: ByteArray): PairingContext? { + val nativePtr = nativeConstructor(true, password) + return if (nativePtr != 0L) PairingContext(nativePtr) else null + } + + @JvmStatic + private external fun nativeConstructor(isClient: Boolean, password: ByteArray): Long + } +} + +@RequiresApi(Build.VERSION_CODES.R) +class AdbPairingClient(private val host: String, private val port: Int, private val pairCode: String, private val key: AdbKey) : Closeable { + + private enum class State { + Ready, + ExchangingMsgs, + ExchangingPeerInfo, + Stopped + } + + private lateinit var socket: Socket + private lateinit var inputStream: DataInputStream + private lateinit var outputStream: DataOutputStream + + private val peerInfo: PeerInfo = PeerInfo(PeerInfo.Type.ADB_RSA_PUB_KEY.value, key.adbPublicKey) + private lateinit var pairingContext: PairingContext + private var state: State = State.Ready + + fun start(): Boolean { + setupTlsConnection() + + state = State.ExchangingMsgs + + if (!doExchangeMsgs()) { + state = State.Stopped + return false + } + + state = State.ExchangingPeerInfo + + if (!doExchangePeerInfo()) { + state = State.Stopped + return false + } + + state = State.Stopped + return true + } + + private fun setupTlsConnection() { + socket = Socket(host, port) + socket.tcpNoDelay = true + + val sslContext = key.sslContext + val sslSocket = sslContext.socketFactory.createSocket(socket, host, port, true) as SSLSocket + sslSocket.startHandshake() + Log.d(TAG, "Handshake succeeded.") + + inputStream = DataInputStream(sslSocket.inputStream) + outputStream = DataOutputStream(sslSocket.outputStream) + + val pairCodeBytes = pairCode.toByteArray() + val keyMaterial = Conscrypt.exportKeyingMaterial(sslSocket, kExportedKeyLabel, null, kExportedKeySize) + val passwordBytes = ByteArray(pairCode.length + keyMaterial.size) + pairCodeBytes.copyInto(passwordBytes) + keyMaterial.copyInto(passwordBytes, pairCodeBytes.size) + + val pairingContext = PairingContext.create(passwordBytes) + checkNotNull(pairingContext) { "Unable to create PairingContext." } + this.pairingContext = pairingContext + } + + private fun createHeader(type: PairingPacketHeader.Type, payloadSize: Int): PairingPacketHeader { + return PairingPacketHeader(kCurrentKeyHeaderVersion, type.value, payloadSize) + } + + private fun readHeader(): PairingPacketHeader? { + val bytes = ByteArray(kPairingPacketHeaderSize) + inputStream.readFully(bytes) + val buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN) + return PairingPacketHeader.readFrom(buffer) + } + + private fun writeHeader(header: PairingPacketHeader, payload: ByteArray) { + val buffer = ByteBuffer.allocate(kPairingPacketHeaderSize).order(ByteOrder.BIG_ENDIAN) + header.writeTo(buffer) + + outputStream.write(buffer.array()) + outputStream.write(payload) + Log.d(TAG, "write payload, size=${payload.size}") + } + + private fun doExchangeMsgs(): Boolean { + val msg = pairingContext.msg + val size = msg.size + + val ourHeader = createHeader(PairingPacketHeader.Type.SPAKE2_MSG, size) + writeHeader(ourHeader, msg) + + val theirHeader = readHeader() ?: return false + if (theirHeader.type != PairingPacketHeader.Type.SPAKE2_MSG.value) return false + + val theirMessage = ByteArray(theirHeader.payload) + inputStream.readFully(theirMessage) + + if (!pairingContext.initCipher(theirMessage)) return false + return true + } + + private fun doExchangePeerInfo(): Boolean { + val buf = ByteBuffer.allocate(kMaxPeerInfoSize).order(ByteOrder.BIG_ENDIAN) + peerInfo.writeTo(buf) + + val outbuf = pairingContext.encrypt(buf.array()) ?: return false + + val ourHeader = createHeader(PairingPacketHeader.Type.PEER_INFO, outbuf.size) + writeHeader(ourHeader, outbuf) + + val theirHeader = readHeader() ?: return false + if (theirHeader.type != PairingPacketHeader.Type.PEER_INFO.value) return false + + val theirMessage = ByteArray(theirHeader.payload) + inputStream.readFully(theirMessage) + + val decrypted = pairingContext.decrypt(theirMessage) ?: throw AdbInvalidPairingCodeException() + if (decrypted.size != kMaxPeerInfoSize) { + Log.e(TAG, "Got size=${decrypted.size} PeerInfo.size=$kMaxPeerInfoSize") + return false + } + val theirPeerInfo = PeerInfo.readFrom(ByteBuffer.wrap(decrypted)) + Log.d(TAG, theirPeerInfo.toString()) + return true + } + + override fun close() { + try { + inputStream.close() + } catch (e: Throwable) { + } + try { + outputStream.close() + } catch (e: Throwable) { + } + try { + socket.close() + } catch (e: Exception) { + } + + if (state != State.Ready) { + pairingContext.destroy() + } + } + + companion object { + + init { + System.loadLibrary("adb") + } + + @JvmStatic + external fun available(): Boolean + } +} diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbProtocol.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbProtocol.kt new file mode 100644 index 0000000000..20d7b00ba1 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbProtocol.kt @@ -0,0 +1,22 @@ +package moe.shizuku.manager.adb + +object AdbProtocol { + + const val A_SYNC = 0x434e5953 + const val A_CNXN = 0x4e584e43 + const val A_AUTH = 0x48545541 + const val A_OPEN = 0x4e45504f + const val A_OKAY = 0x59414b4f + const val A_CLSE = 0x45534c43 + const val A_WRTE = 0x45545257 + const val A_STLS = 0x534C5453 + + const val A_VERSION = 0x01000000 + const val A_MAXDATA = 4096 + + const val A_STLS_VERSION = 0x01000000 + + const val ADB_AUTH_TOKEN = 1 + const val ADB_AUTH_SIGNATURE = 2 + const val ADB_AUTH_RSAPUBLICKEY = 3 +} \ No newline at end of file From f26c045db6c988bc64ac3ee8e77abbea744463d1 Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 21:27:42 +0200 Subject: [PATCH 58/95] #1394 pairing with ADB works --- app/src/main/AndroidManifest.xml | 9 + .../sds100/keymapper/ActivityViewModel.kt | 13 +- .../sds100/keymapper/BaseMainActivity.kt | 164 ++++++++ build.gradle | 6 +- nativelib/build.gradle.kts | 19 +- .../sds100/keymapper/nativelib/adb/AdbKey.kt | 171 +++++--- .../nativelib/adb/AdbPairingClient.kt | 43 +- .../nativelib/adb/AdbPairingService.kt | 374 ++++++++++++++++++ .../sds100/keymapper/nativelib/ktx/Context.kt | 21 + .../sds100/keymapper/nativelib/ktx/Log.kt | 24 ++ .../keymapper/nativelib/starter/Starter.kt | 139 +++++++ nativelib/src/main/res/raw/start.sh | 51 +++ nativelib/src/main/res/values/strings.xml | 169 ++++++++ systemstubs/build.gradle.kts | 13 +- .../com/android/org/conscrypt/Conscrypt.java | 28 ++ 15 files changed, 1156 insertions(+), 88 deletions(-) create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingService.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Context.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Log.kt create mode 100644 nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt create mode 100644 nativelib/src/main/res/raw/start.sh create mode 100644 nativelib/src/main/res/values/strings.xml create mode 100644 systemstubs/src/main/java/com/android/org/conscrypt/Conscrypt.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c19777221d..89b7e04548 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,6 +22,8 @@ + + @@ -321,5 +323,12 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> + + + \ No newline at end of file diff --git a/app/src/main/java/io/github/sds100/keymapper/ActivityViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/ActivityViewModel.kt index 8fefdddbe4..fc7f019271 100644 --- a/app/src/main/java/io/github/sds100/keymapper/ActivityViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/ActivityViewModel.kt @@ -1,5 +1,8 @@ package io.github.sds100.keymapper +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope @@ -9,7 +12,14 @@ import io.github.sds100.keymapper.util.ui.PopupViewModel import io.github.sds100.keymapper.util.ui.PopupViewModelImpl import io.github.sds100.keymapper.util.ui.ResourceProvider import io.github.sds100.keymapper.util.ui.ViewModelHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +import moe.shizuku.manager.adb.AdbClient +import moe.shizuku.manager.adb.AdbKey +import moe.shizuku.manager.adb.AdbKeyException +import moe.shizuku.manager.adb.PreferenceAdbKeyStore +import moe.shizuku.manager.starter.Starter /** * Created by sds100 on 23/07/2021. @@ -38,7 +48,6 @@ class ActivityViewModel( private val resourceProvider: ResourceProvider, ) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = - ActivityViewModel(resourceProvider) as T + override fun create(modelClass: Class): T = ActivityViewModel(resourceProvider) as T } } diff --git a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt index 98cf6232c0..1fafab413d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt @@ -1,17 +1,26 @@ package io.github.sds100.keymapper +import android.app.AppOpsManager +import android.app.ForegroundServiceStartNotAllowedException import android.content.BroadcastReceiver +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.ServiceConnection import android.content.res.Configuration import android.net.Uri +import android.os.Build import android.os.Bundle +import android.os.IBinder +import android.util.Log import android.view.MotionEvent +import android.widget.Toast import androidx.activity.SystemBarStyle import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.activity.viewModels +import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.compose.ui.graphics.toArgb import androidx.core.content.ContextCompat @@ -23,6 +32,7 @@ import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.withStateAtLeast import androidx.navigation.findNavController +import androidx.preference.PreferenceManager import com.anggrayudi.storage.extension.openInputStream import com.anggrayudi.storage.extension.openOutputStream import com.anggrayudi.storage.extension.toDocumentFile @@ -30,6 +40,8 @@ import io.github.sds100.keymapper.Constants.PACKAGE_NAME import io.github.sds100.keymapper.compose.ComposeColors import io.github.sds100.keymapper.databinding.ActivityMainBinding import io.github.sds100.keymapper.mappings.keymaps.trigger.RecordTriggerController +import io.github.sds100.keymapper.nativelib.IEvdevService +import io.github.sds100.keymapper.nativelib.adb.AdbPairingService import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter import io.github.sds100.keymapper.system.files.FileUtils import io.github.sds100.keymapper.system.inputevents.MyMotionEvent @@ -38,9 +50,15 @@ import io.github.sds100.keymapper.system.permissions.RequestPermissionDelegate import io.github.sds100.keymapper.util.launchRepeatOnLifecycle import io.github.sds100.keymapper.util.ui.showPopups import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import moe.shizuku.manager.adb.AdbClient +import moe.shizuku.manager.adb.AdbKey +import moe.shizuku.manager.adb.AdbKeyException +import moe.shizuku.manager.adb.PreferenceAdbKeyStore +import moe.shizuku.manager.starter.Starter import timber.log.Timber /** @@ -109,6 +127,19 @@ abstract class BaseMainActivity : AppCompatActivity() { } } + private val evdevServiceConnection: ServiceConnection = object : ServiceConnection { + override fun onServiceConnected(componentName: ComponentName, binder: IBinder) { + val service = IEvdevService.Stub.asInterface(binder) + + lifecycleScope.launch(Dispatchers.Default) { + Timber.e("RECEIVED FROM EVDEV ${service.sendEvent()}") + } + } + + override fun onServiceDisconnected(componentName: ComponentName) { + } + } + override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() enableEdgeToEdge( @@ -169,6 +200,35 @@ abstract class BaseMainActivity : AppCompatActivity() { ContextCompat.RECEIVER_EXPORTED, ) } + + // See demo.DemoActivity in the Shizuku-API repository. + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + startPairingService() +// startAdb("127.0.0.1", 41849) + } + +// val userServiceArgs = +// UserServiceArgs( +// ComponentName( +// BuildConfig.APPLICATION_ID, +// EvdevService::class.java.getName(), +// ), +// ) +// .daemon(false) +// .processNameSuffix("service") +// .debuggable(BuildConfig.DEBUG) +// .version(BuildConfig.VERSION_CODE) +// +// try { +// if (Shizuku.getVersion() < 10) { +// Timber.e("requires Shizuku API 10") +// } else { +// Shizuku.bindUserService(userServiceArgs, evdevServiceConnection) +// } +// } catch (tr: Throwable) { +// tr.printStackTrace() +// } } override fun onResume() { @@ -225,4 +285,108 @@ abstract class BaseMainActivity : AppCompatActivity() { originalFileUri = fileUri saveFileLauncher.launch(fileName) } + + private val sb = StringBuilder() + + fun postResult(throwable: Throwable? = null) { + if (throwable == null) { + Timber.e(sb.toString()) + } else { + Timber.e(throwable) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun startPairingService() { + val intent = AdbPairingService.startIntent(this) + try { + startForegroundService(intent) + } catch (e: Throwable) { + Timber.e("startForegroundService", e) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + e is ForegroundServiceStartNotAllowedException + ) { + val mode = getSystemService(AppOpsManager::class.java) + .noteOpNoThrow( + "android:start_foreground", + android.os.Process.myUid(), + packageName, + null, + null, + ) + if (mode == AppOpsManager.MODE_ERRORED) { + Toast.makeText( + this, + "OP_START_FOREGROUND is denied. What are you doing?", + Toast.LENGTH_LONG, + ).show() + } + startService(intent) + } + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun startAdb(host: String, port: Int) { + sb.append("Starting with wireless adb...").append('\n').append('\n') + postResult() + + GlobalScope.launch(Dispatchers.IO) { + val key = try { + AdbKey( + PreferenceAdbKeyStore(PreferenceManager.getDefaultSharedPreferences(this@BaseMainActivity)), + "shizuku", + ) + } catch (e: Throwable) { + e.printStackTrace() + sb.append('\n').append(Log.getStackTraceString(e)) + + postResult(AdbKeyException(e)) + return@launch + } + + AdbClient(host, port, key).runCatching { + connect() + shellCommand(Starter.sdcardCommand) { + sb.append(String(it)) + postResult() + } + close() + }.onFailure { + it.printStackTrace() + + sb.append('\n').append(Log.getStackTraceString(it)) + postResult(it) + } + + /* Adb on MIUI Android 11 has no permission to access Android/data. + Before MIUI Android 12, we can temporarily use /data/user_de. + After that, is better to implement "adb push" and push files directly to /data/local/tmp. + */ + if (sb.contains("/Android/data/${BuildConfig.APPLICATION_ID}/start.sh: Permission denied")) { + sb.append('\n') + .appendLine("adb have no permission to access Android/data, how could this possible ?!") + .appendLine("try /data/user_de instead...") + .appendLine() + postResult() + + Starter.writeDataFiles(application, true) + + AdbClient(host, port, key).runCatching { + connect() + shellCommand(Starter.dataCommand) { + sb.append(String(it)) + postResult() + } + close() + }.onFailure { + it.printStackTrace() + + sb.append('\n').append(Log.getStackTraceString(it)) + postResult(it) + } + } + } + } } diff --git a/build.gradle b/build.gradle index e17e83582b..74e6f8a1da 100644 --- a/build.gradle +++ b/build.gradle @@ -13,15 +13,14 @@ buildscript { def nav_version = '2.6.0' classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" - classpath 'com.android.tools.build:gradle:8.9.1' + classpath 'com.android.tools.build:gradle:8.10.0' classpath "org.jlleitschuh.gradle:ktlint-gradle:12.1.0" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.1.0" classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.1.0-1.0.28" classpath "androidx.room:androidx.room.gradle.plugin:2.6.1" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath "dev.rikka.tools.refine:gradle-plugin:4.4.0" } } @@ -38,4 +37,3 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir } - diff --git a/nativelib/build.gradle.kts b/nativelib/build.gradle.kts index 6f7f438c9d..0047dc4b95 100644 --- a/nativelib/build.gradle.kts +++ b/nativelib/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.library") id("org.jetbrains.kotlin.android") +// id("dev.rikka.tools.refine") } android { @@ -15,8 +16,9 @@ android { externalNativeBuild { cmake { - // This is required by Rikka's library: https://github.com/RikkaW/libcxx-prefab - arguments("-DANDROID_STL=none") + // -DANDROID_STL=none is required by Rikka's library: https://github.com/RikkaW/libcxx-prefab + // -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON is required to get the app running on the Android 15 emulator. This is related to the new 16kB page size support. + arguments("-DANDROID_STL=none", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON") } } } @@ -59,6 +61,8 @@ android { } dependencies { +// compileOnly(project(":systemstubs")) + implementation("org.conscrypt:conscrypt-android:2.5.2") implementation("androidx.core:core-ktx:1.16.0") implementation("androidx.appcompat:appcompat:1.7.0") implementation("com.google.android.material:material:1.12.0") @@ -70,11 +74,12 @@ dependencies { implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("me.zhanghai.android.appiconloader:appiconloader:1.5.0") implementation("dev.rikka.rikkax.core:core-ktx:1.4.1") - implementation("dev.rikka.hidden:compat:4.3.3") - compileOnly("dev.rikka.hidden:stub:4.3.3") - -// annotationProcessor("dev.rikka.tools.refine:annotation-processor:4.4.0") -// compileOnly("dev.rikka.tools.refine:annotation:4.4.0") +// implementation("dev.rikka.hidden:compat:4.3.3") +// compileOnly("dev.rikka.hidden:stub:4.3.3") +// +// implementation("dev.rikka.tools.refine:runtime:4.3.0") +// annotationProcessor("dev.rikka.tools.refine:annotation-processor:4.3.0") +// compileOnly("dev.rikka.tools.refine:annotation:4.3.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.2.1") diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt index 102082dd9d..058bfb28bb 100644 --- a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt @@ -13,13 +13,20 @@ import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.cert.X509v3CertificateBuilder import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder +import org.conscrypt.Conscrypt import rikka.core.ktx.unsafeLazy import java.io.ByteArrayInputStream import java.math.BigInteger import java.net.Socket import java.nio.ByteBuffer import java.nio.ByteOrder -import java.security.* +import java.security.Key +import java.security.KeyFactory +import java.security.KeyPairGenerator +import java.security.KeyStore +import java.security.Principal +import java.security.PrivateKey +import java.security.SecureRandom import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.security.interfaces.RSAPrivateKey @@ -27,7 +34,8 @@ import java.security.interfaces.RSAPublicKey import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.RSAKeyGenParameterSpec import java.security.spec.RSAPublicKeySpec -import java.util.* +import java.util.Date +import java.util.Locale import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.spec.GCMParameterSpec @@ -38,6 +46,7 @@ import javax.net.ssl.X509ExtendedTrustManager private const val TAG = "AdbKey" +@RequiresApi(Build.VERSION_CODES.M) class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { companion object { @@ -50,25 +59,26 @@ class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { private const val TAG_SIZE_IN_BYTES = 16 private val PADDING = byteArrayOf( - 0x00, 0x01, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0x00, - 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, - 0x04, 0x14) + 0x00, 0x01, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0x00, + 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, + 0x04, 0x14, + ) } private val encryptionKey: Key @@ -78,22 +88,29 @@ class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { private val certificate: X509Certificate init { - this.encryptionKey = getOrCreateEncryptionKey() ?: error("Failed to generate encryption key with AndroidKeyManager.") + this.encryptionKey = getOrCreateEncryptionKey() + ?: error("Failed to generate encryption key with AndroidKeyManager.") this.privateKey = getOrCreatePrivateKey() - this.publicKey = KeyFactory.getInstance("RSA").generatePublic(RSAPublicKeySpec(privateKey.modulus, RSAKeyGenParameterSpec.F4)) as RSAPublicKey + this.publicKey = KeyFactory.getInstance("RSA").generatePublic( + RSAPublicKeySpec( + privateKey.modulus, + RSAKeyGenParameterSpec.F4, + ), + ) as RSAPublicKey val signer = JcaContentSignerBuilder("SHA256withRSA").build(privateKey) - val x509Certificate = X509v3CertificateBuilder(X500Name("CN=00"), - BigInteger.ONE, - Date(0), - Date(2461449600 * 1000), - Locale.ROOT, - X500Name("CN=00"), - SubjectPublicKeyInfo.getInstance(publicKey.encoded) + val x509Certificate = X509v3CertificateBuilder( + X500Name("CN=00"), + BigInteger.ONE, + Date(0), + Date(2461449600 * 1000), + Locale.ROOT, + X500Name("CN=00"), + SubjectPublicKeyInfo.getInstance(publicKey.encoded), ).build(signer) this.certificate = CertificateFactory.getInstance("X.509") - .generateCertificate(ByteArrayInputStream(x509Certificate.encoded)) as X509Certificate + .generateCertificate(ByteArrayInputStream(x509Certificate.encoded)) as X509Certificate Log.d(TAG, privateKey.toString()) } @@ -102,17 +119,22 @@ class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { publicKey.adbEncoded(name) } + @RequiresApi(Build.VERSION_CODES.M) private fun getOrCreateEncryptionKey(): Key? { val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE) keyStore.load(null) return keyStore.getKey(ENCRYPTION_KEY_ALIAS, null) ?: run { - val parameterSpec = KeyGenParameterSpec.Builder(ENCRYPTION_KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .setKeySize(256) - .build() - val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE) + val parameterSpec = KeyGenParameterSpec.Builder( + ENCRYPTION_KEY_ALIAS, + KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT, + ) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(256) + .build() + val keyGenerator = + KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE) keyGenerator.init(parameterSpec) keyGenerator.generateKey() } @@ -154,7 +176,8 @@ class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { val plaintext = decrypt(ciphertext, aad) val keyFactory = KeyFactory.getInstance("RSA") - privateKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(plaintext)) as RSAPrivateKey + privateKey = + keyFactory.generatePrivate(PKCS8EncodedKeySpec(plaintext)) as RSAPrivateKey } catch (e: Exception) { } } @@ -183,8 +206,15 @@ class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { get() = object : X509ExtendedKeyManager() { private val alias = "key" - override fun chooseClientAlias(keyTypes: Array, issuers: Array?, socket: Socket?): String? { - Log.d(TAG, "chooseClientAlias: keyType=${keyTypes.contentToString()}, issuers=${issuers?.contentToString()}") + override fun chooseClientAlias( + keyTypes: Array, + issuers: Array?, + socket: Socket?, + ): String? { + Log.d( + TAG, + "chooseClientAlias: keyType=${keyTypes.contentToString()}, issuers=${issuers?.contentToString()}", + ) for (keyType in keyTypes) { if (keyType == "RSA") return alias } @@ -201,47 +231,78 @@ class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { return if (alias == this.alias) privateKey else null } - override fun getClientAliases(keyType: String?, issuers: Array?): Array? { + override fun getClientAliases( + keyType: String?, + issuers: Array?, + ): Array? { return null } - override fun getServerAliases(keyType: String, issuers: Array?): Array? { + override fun getServerAliases( + keyType: String, + issuers: Array?, + ): Array? { return null } - override fun chooseServerAlias(keyType: String, issuers: Array?, socket: Socket?): String? { + override fun chooseServerAlias( + keyType: String, + issuers: Array?, + socket: Socket?, + ): String? { return null } } - private val trustManager get() = @RequiresApi(Build.VERSION_CODES.R) object : X509ExtendedTrustManager() { @SuppressLint("TrustAllX509TrustManager") - override fun checkClientTrusted(chain: Array?, authType: String?, socket: Socket?) { + override fun checkClientTrusted( + chain: Array?, + authType: String?, + socket: Socket?, + ) { } @SuppressLint("TrustAllX509TrustManager") - override fun checkClientTrusted(chain: Array?, authType: String?, engine: SSLEngine?) { + override fun checkClientTrusted( + chain: Array?, + authType: String?, + engine: SSLEngine?, + ) { } @SuppressLint("TrustAllX509TrustManager") - override fun checkClientTrusted(chain: Array?, authType: String?) { + override fun checkClientTrusted( + chain: Array?, + authType: String?, + ) { } @SuppressLint("TrustAllX509TrustManager") - override fun checkServerTrusted(chain: Array?, authType: String?, socket: Socket?) { + override fun checkServerTrusted( + chain: Array?, + authType: String?, + socket: Socket?, + ) { } @SuppressLint("TrustAllX509TrustManager") - override fun checkServerTrusted(chain: Array?, authType: String?, engine: SSLEngine?) { + override fun checkServerTrusted( + chain: Array?, + authType: String?, + engine: SSLEngine?, + ) { } @SuppressLint("TrustAllX509TrustManager") - override fun checkServerTrusted(chain: Array?, authType: String?) { + override fun checkServerTrusted( + chain: Array?, + authType: String?, + ) { } override fun getAcceptedIssuers(): Array { @@ -251,8 +312,12 @@ class AdbKey(private val adbKeyStore: AdbKeyStore, name: String) { @delegate:RequiresApi(Build.VERSION_CODES.R) val sslContext: SSLContext by unsafeLazy { - val sslContext = SSLContext.getInstance("TLSv1.3") - sslContext.init(arrayOf(keyManager), arrayOf(trustManager), SecureRandom()) + val sslContext = SSLContext.getInstance("TLSv1.3", Conscrypt.newProvider()) + sslContext.init( + arrayOf(keyManager), + arrayOf(trustManager), + SecureRandom(), + ) sslContext } } @@ -308,7 +373,7 @@ private fun RSAPublicKey.adbEncoded(name: String): ByteArray { uint8_t rr[ANDROID_PUBKEY_MODULUS_SIZE]; // rr = (2^(rsa_size)) ^ 2 mod N uint32_t exponent; } RSAPublicKey; - */ + */ val r32 = BigInteger.ZERO.setBit(32) val n0inv = modulus.remainder(r32).modInverse(r32).negate() diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt index c03370d5f8..6f241fc526 100644 --- a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt @@ -3,7 +3,7 @@ package moe.shizuku.manager.adb import android.os.Build import android.util.Log import androidx.annotation.RequiresApi -import com.android.org.conscrypt.Conscrypt +import org.conscrypt.Conscrypt import java.io.Closeable import java.io.DataInputStream import java.io.DataOutputStream @@ -26,8 +26,9 @@ private const val kExportedKeySize = 64 private const val kPairingPacketHeaderSize = 6 private class PeerInfo( - val type: Byte, - data: ByteArray) { + val type: Byte, + data: ByteArray, +) { val data = ByteArray(kMaxPeerInfoSize - 1) @@ -69,13 +70,14 @@ private class PeerInfo( } private class PairingPacketHeader( - val version: Byte, - val type: Byte, - val payload: Int) { + val version: Byte, + val type: Byte, + val payload: Int, +) { enum class Type(val value: Byte) { SPAKE2_MSG(0.toByte()), - PEER_INFO(1.toByte()) + PEER_INFO(1.toByte()), } fun writeTo(buffer: ByteBuffer) { @@ -104,15 +106,18 @@ private class PairingPacketHeader( val payload = buffer.int if (version < kMinSupportedKeyHeaderVersion || version > kMaxSupportedKeyHeaderVersion) { - Log.e(TAG, "PairingPacketHeader version mismatch (us=$kCurrentKeyHeaderVersion them=${version})") + Log.e( + TAG, + "PairingPacketHeader version mismatch (us=$kCurrentKeyHeaderVersion them=$version)", + ) return null } if (type != Type.SPAKE2_MSG.value && type != Type.PEER_INFO.value) { - Log.e(TAG, "Unknown PairingPacket type=${type}") + Log.e(TAG, "Unknown PairingPacket type=$type") return null } if (payload <= 0 || payload > kMaxPayloadSize) { - Log.e(TAG, "header payload not within a safe payload size (size=${payload})") + Log.e(TAG, "header payload not within a safe payload size (size=$payload)") return null } @@ -162,13 +167,18 @@ private class PairingContext private constructor(private val nativePtr: Long) { } @RequiresApi(Build.VERSION_CODES.R) -class AdbPairingClient(private val host: String, private val port: Int, private val pairCode: String, private val key: AdbKey) : Closeable { +class AdbPairingClient( + private val host: String, + private val port: Int, + private val pairCode: String, + private val key: AdbKey, +) : Closeable { private enum class State { Ready, ExchangingMsgs, ExchangingPeerInfo, - Stopped + Stopped, } private lateinit var socket: Socket @@ -205,6 +215,7 @@ class AdbPairingClient(private val host: String, private val port: Int, private socket.tcpNoDelay = true val sslContext = key.sslContext + val sslSocket = sslContext.socketFactory.createSocket(socket, host, port, true) as SSLSocket sslSocket.startHandshake() Log.d(TAG, "Handshake succeeded.") @@ -223,7 +234,10 @@ class AdbPairingClient(private val host: String, private val port: Int, private this.pairingContext = pairingContext } - private fun createHeader(type: PairingPacketHeader.Type, payloadSize: Int): PairingPacketHeader { + private fun createHeader( + type: PairingPacketHeader.Type, + payloadSize: Int, + ): PairingPacketHeader { return PairingPacketHeader(kCurrentKeyHeaderVersion, type.value, payloadSize) } @@ -275,7 +289,8 @@ class AdbPairingClient(private val host: String, private val port: Int, private val theirMessage = ByteArray(theirHeader.payload) inputStream.readFully(theirMessage) - val decrypted = pairingContext.decrypt(theirMessage) ?: throw AdbInvalidPairingCodeException() + val decrypted = + pairingContext.decrypt(theirMessage) ?: throw AdbInvalidPairingCodeException() if (decrypted.size != kMaxPeerInfoSize) { Log.e(TAG, "Got size=${decrypted.size} PeerInfo.size=$kMaxPeerInfoSize") return false diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingService.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingService.kt new file mode 100644 index 0000000000..3ccb27de7e --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingService.kt @@ -0,0 +1,374 @@ +package io.github.sds100.keymapper.nativelib.adb + +import android.annotation.TargetApi +import android.app.ForegroundServiceStartNotAllowedException +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.RemoteInput +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.preference.PreferenceManager +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import io.github.sds100.keymapper.nativelib.R +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import moe.shizuku.manager.adb.AdbInvalidPairingCodeException +import moe.shizuku.manager.adb.AdbKey +import moe.shizuku.manager.adb.AdbKeyException +import moe.shizuku.manager.adb.AdbMdns +import moe.shizuku.manager.adb.AdbPairingClient +import moe.shizuku.manager.adb.PreferenceAdbKeyStore +import moe.shizuku.manager.ktx.TAG +import rikka.core.ktx.unsafeLazy +import java.net.ConnectException + +@TargetApi(Build.VERSION_CODES.R) +class AdbPairingService : Service() { + + companion object { + + const val notificationChannel = "adb_pairing" + + private const val tag = "AdbPairingService" + + private const val notificationId = 1 + private const val replyRequestId = 1 + private const val stopRequestId = 2 + private const val retryRequestId = 3 + private const val startAction = "start" + private const val stopAction = "stop" + private const val replyAction = "reply" + private const val remoteInputResultKey = "paring_code" + private const val portKey = "paring_code" + + fun startIntent(context: Context): Intent { + return Intent(context, AdbPairingService::class.java).setAction(startAction) + } + + private fun stopIntent(context: Context): Intent { + return Intent(context, AdbPairingService::class.java).setAction(stopAction) + } + + private fun replyIntent(context: Context, port: Int): Intent { + return Intent(context, AdbPairingService::class.java).setAction(replyAction) + .putExtra(portKey, port) + } + } + + private val handler = Handler(Looper.getMainLooper()) + private val port = MutableLiveData() + private var adbMdns: AdbMdns? = null + + private val observer = Observer { port -> + Log.i(tag, "Pairing service port: $port") + + // Since the service could be killed before user finishing input, + // we need to put the port into Intent + val notification = createInputNotification(port) + + getSystemService(NotificationManager::class.java).notify(notificationId, notification) + } + + private var started = false + + override fun onCreate() { + super.onCreate() + + getSystemService(NotificationManager::class.java).createNotificationChannel( + NotificationChannel( + notificationChannel, + "ADB Pairing", + NotificationManager.IMPORTANCE_HIGH, + ).apply { + setSound(null, null) + setShowBadge(false) + setAllowBubbles(false) + }, + ) + + Log.e(TAG, "Create notification channel") + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + val notification = when (intent?.action) { + startAction -> { + onStart() + } + + replyAction -> { + val code = + RemoteInput.getResultsFromIntent(intent)?.getCharSequence(remoteInputResultKey) + ?: "" + val port = intent.getIntExtra(portKey, -1) + if (port != -1) { + onInput(code.toString(), port) + } else { + onStart() + } + } + + stopAction -> { + stopForeground(STOP_FOREGROUND_REMOVE) + null + } + + else -> { + return START_NOT_STICKY + } + } + if (notification != null) { + try { + startForeground(notificationId, notification) + } catch (e: Throwable) { + Log.e(tag, "startForeground failed", e) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + e is ForegroundServiceStartNotAllowedException + ) { + getSystemService(NotificationManager::class.java).notify( + notificationId, + notification, + ) + } + } + } + return START_REDELIVER_INTENT + } + + private fun startSearch() { + if (started) return + started = true + adbMdns = AdbMdns(this, AdbMdns.TLS_PAIRING, port).apply { start() } + + if (Looper.myLooper() == Looper.getMainLooper()) { + port.observeForever(observer) + } else { + handler.post { port.observeForever(observer) } + } + } + + private fun stopSearch() { + if (!started) return + started = false + adbMdns?.stop() + + if (Looper.myLooper() == Looper.getMainLooper()) { + port.removeObserver(observer) + } else { + handler.post { port.removeObserver(observer) } + } + } + + override fun onDestroy() { + super.onDestroy() + stopSearch() + } + + private fun onStart(): Notification { + startSearch() + return searchingNotification + } + + private fun onInput(code: String, port: Int): Notification { + GlobalScope.launch(Dispatchers.IO) { + val host = "127.0.0.1" + + val key = try { + AdbKey( + PreferenceAdbKeyStore(PreferenceManager.getDefaultSharedPreferences(this@AdbPairingService)), + "shizuku", + ) + } catch (e: Throwable) { + e.printStackTrace() + return@launch + } + + AdbPairingClient(host, port, code, key).runCatching { + start() + }.onFailure { + handleResult(false, it) + }.onSuccess { + handleResult(it, null) + } + } + + return workingNotification + } + + private fun handleResult(success: Boolean, exception: Throwable?) { + stopForeground(STOP_FOREGROUND_REMOVE) + + val title: String + val text: String? + + if (success) { + Log.i(tag, "Pair succeed") + + title = getString(R.string.notification_adb_pairing_succeed_title) + text = getString(R.string.notification_adb_pairing_succeed_text) + + stopSearch() + } else { + title = getString(R.string.notification_adb_pairing_failed_title) + + text = when (exception) { + is ConnectException -> { + getString(R.string.cannot_connect_port) + } + + is AdbInvalidPairingCodeException -> { + getString(R.string.paring_code_is_wrong) + } + + is AdbKeyException -> { + getString(R.string.adb_error_key_store) + } + + else -> { + exception?.let { Log.getStackTraceString(it) } + } + } + + if (exception != null) { + Log.w(tag, "Pair failed", exception) + } else { + Log.w(tag, "Pair failed") + } + } + + getSystemService(NotificationManager::class.java).notify( + notificationId, + Notification.Builder(this, notificationChannel) + .setSmallIcon(R.drawable.ic_instant_app_badge) + .setContentTitle(title) + .setContentText(text) + /*.apply { + if (!success) { + addAction(retryNotificationAction) + } + }*/ + .build(), + ) + } + + private val stopNotificationAction by unsafeLazy { + val pendingIntent = PendingIntent.getService( + this, + stopRequestId, + stopIntent(this), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_IMMUTABLE + } else { + 0 + }, + ) + + Notification.Action.Builder( + null, + getString(R.string.notification_adb_pairing_stop_searching), + pendingIntent, + ) + .build() + } + + private val retryNotificationAction by unsafeLazy { + val pendingIntent = PendingIntent.getService( + this, + retryRequestId, + startIntent(this), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_IMMUTABLE + } else { + 0 + }, + ) + + Notification.Action.Builder( + null, + getString(R.string.notification_adb_pairing_retry), + pendingIntent, + ) + .build() + } + + private val replyNotificationAction by unsafeLazy { + val remoteInput = RemoteInput.Builder(remoteInputResultKey).run { + setLabel(getString(R.string.dialog_adb_pairing_paring_code)) + build() + } + + val pendingIntent = PendingIntent.getForegroundService( + this, + replyRequestId, + replyIntent(this, -1), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + } else { + PendingIntent.FLAG_UPDATE_CURRENT + }, + ) + + Notification.Action.Builder( + null, + getString(R.string.notification_adb_pairing_input_paring_code), + pendingIntent, + ) + .addRemoteInput(remoteInput) + .build() + } + + private fun replyNotificationAction(port: Int): Notification.Action { + // Ensure pending intent is created + val action = replyNotificationAction + + PendingIntent.getForegroundService( + this, + replyRequestId, + replyIntent(this, port), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + } else { + PendingIntent.FLAG_UPDATE_CURRENT + }, + ) + + return action + } + + private val searchingNotification by unsafeLazy { + Notification.Builder(this, notificationChannel) + .setSmallIcon(R.drawable.ic_instant_app_badge) + .setContentTitle("Searching") + .addAction(stopNotificationAction) + .build() + } + + private fun createInputNotification(port: Int): Notification { + return Notification.Builder(this, notificationChannel) + .setSmallIcon(R.drawable.ic_instant_app_badge) + .setContentTitle(getString(R.string.notification_adb_pairing_service_found_title)) + .addAction(replyNotificationAction(port)) + .build() + } + + private val workingNotification by unsafeLazy { + Notification.Builder(this, notificationChannel) + .setSmallIcon(R.drawable.ic_instant_app_badge) + .setContentTitle(getString(R.string.notification_adb_pairing_working_title)) + .build() + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } +} diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Context.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Context.kt new file mode 100644 index 0000000000..2aa64bdb15 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Context.kt @@ -0,0 +1,21 @@ +package moe.shizuku.manager.ktx + +import android.content.Context +import android.os.Build +import android.os.UserManager + +fun Context.createDeviceProtectedStorageContextCompat(): Context { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + createDeviceProtectedStorageContext() + } else { + this + } +} + +fun Context.createDeviceProtectedStorageContextCompatWhenLocked(): Context { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getSystemService(UserManager::class.java)?.isUserUnlocked != true) { + createDeviceProtectedStorageContext() + } else { + this + } +} \ No newline at end of file diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Log.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Log.kt new file mode 100644 index 0000000000..8b09cde125 --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Log.kt @@ -0,0 +1,24 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package moe.shizuku.manager.ktx + +import android.util.Log + +inline val T.TAG: String + get() = + T::class.java.simpleName.let { + if (it.isBlank()) throw IllegalStateException("tag is empty") + if (it.length > 23) it.substring(0, 23) else it + } + +inline fun T.logv(message: String, throwable: Throwable? = null) = logv(TAG, message, throwable) +inline fun T.logi(message: String, throwable: Throwable? = null) = logi(TAG, message, throwable) +inline fun T.logw(message: String, throwable: Throwable? = null) = logw(TAG, message, throwable) +inline fun T.logd(message: String, throwable: Throwable? = null) = logd(TAG, message, throwable) +inline fun T.loge(message: String, throwable: Throwable? = null) = loge(TAG, message, throwable) + +inline fun T.logv(tag: String, message: String, throwable: Throwable? = null) = Log.v(tag, message, throwable) +inline fun T.logi(tag: String, message: String, throwable: Throwable? = null) = Log.i(tag, message, throwable) +inline fun T.logw(tag: String, message: String, throwable: Throwable? = null) = Log.w(tag, message, throwable) +inline fun T.logd(tag: String, message: String, throwable: Throwable? = null) = Log.d(tag, message, throwable) +inline fun T.loge(tag: String, message: String, throwable: Throwable? = null) = Log.e(tag, message, throwable) \ No newline at end of file diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt new file mode 100644 index 0000000000..57dadc829e --- /dev/null +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt @@ -0,0 +1,139 @@ +package moe.shizuku.manager.starter + +import android.content.Context +import android.os.Build +import android.os.UserManager +import android.system.ErrnoException +import android.system.Os +import android.util.Log +import androidx.annotation.RequiresApi +import io.github.sds100.keymapper.nativelib.R +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import moe.shizuku.manager.adb.AdbClient +import moe.shizuku.manager.adb.AdbKey +import moe.shizuku.manager.adb.AdbKeyException +import moe.shizuku.manager.adb.PreferenceAdbKeyStore +import moe.shizuku.manager.ktx.createDeviceProtectedStorageContextCompat +import moe.shizuku.manager.ktx.logd +import moe.shizuku.manager.ktx.loge +import rikka.core.os.FileUtils +import java.io.BufferedReader +import java.io.ByteArrayInputStream +import java.io.DataInputStream +import java.io.File +import java.io.FileOutputStream +import java.io.FileWriter +import java.io.IOException +import java.io.InputStreamReader +import java.io.PrintWriter +import java.util.zip.ZipFile + +@RequiresApi(Build.VERSION_CODES.M) +object Starter { + + private var commandInternal = arrayOfNulls(2) + + val dataCommand get() = commandInternal[0]!! + + val sdcardCommand get() = commandInternal[1]!! + + val adbCommand: String + get() = "adb shell $sdcardCommand" + + fun writeSdcardFiles(context: Context) { + if (commandInternal[1] != null) { + logd("already written") + return + } + + val um = context.getSystemService(UserManager::class.java)!! + val unlocked = Build.VERSION.SDK_INT < 24 || um.isUserUnlocked + if (!unlocked) { + throw IllegalStateException("User is locked") + } + + val filesDir = context.getExternalFilesDir(null) + ?: throw IOException("getExternalFilesDir() returns null") + val dir = filesDir.parentFile ?: throw IOException("$filesDir parentFile returns null") + val starter = copyStarter(context, File(dir, "starter")) + val sh = writeScript(context, File(dir, "start.sh"), starter) + commandInternal[1] = "sh $sh" + logd(commandInternal[1]!!) + } + + fun writeDataFiles(context: Context, permission: Boolean = false) { + if (commandInternal[0] != null && !permission) { + logd("already written") + return + } + + val dir = context.createDeviceProtectedStorageContextCompat().filesDir?.parentFile ?: return + + if (permission) { + try { + Os.chmod(dir.absolutePath, 457 /* 0711 */) + } catch (e: ErrnoException) { + e.printStackTrace() + } + } + + try { + val starter = copyStarter(context, File(dir, "starter")) + val sh = writeScript(context, File(dir, "start.sh"), starter) + commandInternal[0] = "sh $sh --apk=${context.applicationInfo.sourceDir}" + logd(commandInternal[0]!!) + + if (permission) { + try { + Os.chmod(starter, 420 /* 0644 */) + } catch (e: ErrnoException) { + e.printStackTrace() + } + try { + Os.chmod(sh, 420 /* 0644 */) + } catch (e: ErrnoException) { + e.printStackTrace() + } + } + } catch (e: IOException) { + loge("write files", e) + } + } + + private fun copyStarter(context: Context, out: File): String { + val so = "lib/${Build.SUPPORTED_ABIS[0]}/libshizuku.so" + val ai = context.applicationInfo + + val fos = FileOutputStream(out) + val apk = ZipFile(ai.sourceDir) + val entries = apk.entries() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() ?: break + if (entry.name != so) continue + + val buf = ByteArray(entry.size.toInt()) + val dis = DataInputStream(apk.getInputStream(entry)) + dis.readFully(buf) + FileUtils.copy(ByteArrayInputStream(buf), fos) + break + } + return out.absolutePath + } + + private fun writeScript(context: Context, out: File, starter: String): String { + if (!out.exists()) { + out.createNewFile() + } + val `is` = BufferedReader(InputStreamReader(context.resources.openRawResource(R.raw.start))) + val os = PrintWriter(FileWriter(out)) + var line: String? + while (`is`.readLine().also { line = it } != null) { + os.println(line!!.replace("%%%STARTER_PATH%%%", starter)) + } + os.flush() + os.close() + return out.absolutePath + } +} diff --git a/nativelib/src/main/res/raw/start.sh b/nativelib/src/main/res/raw/start.sh new file mode 100644 index 0000000000..25d46f8717 --- /dev/null +++ b/nativelib/src/main/res/raw/start.sh @@ -0,0 +1,51 @@ +#!/system/bin/sh + +SOURCE_PATH="%%%STARTER_PATH%%%" +STARTER_PATH="/data/local/tmp/shizuku_starter" + +echo "info: start.sh begin" + +recreate_tmp() { + echo "info: /data/local/tmp is possible broken, recreating..." + rm -rf /data/local/tmp + mkdir -p /data/local/tmp +} + +broken_tmp() { + echo "fatal: /data/local/tmp is broken, please try reboot the device or manually recreate it..." + exit 1 +} + +if [ -f "$SOURCE_PATH" ]; then + echo "info: attempt to copy starter from $SOURCE_PATH to $STARTER_PATH" + rm -f $STARTER_PATH + + cp "$SOURCE_PATH" $STARTER_PATH + res=$? + if [ $res -ne 0 ]; then + recreate_tmp + cp "$SOURCE_PATH" $STARTER_PATH + + res=$? + if [ $res -ne 0 ]; then + broken_tmp + fi + fi + + chmod 700 $STARTER_PATH + chown 2000 $STARTER_PATH + chgrp 2000 $STARTER_PATH +fi + +if [ -f $STARTER_PATH ]; then + echo "info: exec $STARTER_PATH" + $STARTER_PATH "$1" + result=$? + if [ ${result} -ne 0 ]; then + echo "info: shizuku_starter exit with non-zero value $result" + else + echo "info: shizuku_starter exit with 0" + fi +else + echo "Starter file not exist, please open Shizuku and try again." +fi diff --git a/nativelib/src/main/res/values/strings.xml b/nativelib/src/main/res/values/strings.xml new file mode 100644 index 0000000000..5e469e7d09 --- /dev/null +++ b/nativelib/src/main/res/values/strings.xml @@ -0,0 +1,169 @@ + + Shizuku + + + + + + %1$s is running + %1$s is not running + Version %2$s, %1$s + Start again to update to version %3$s]]> + + + + read the help.]]> + Read help + View command + %1$s

* There are some other considerations, please confirm that you have read the help first.]]>
+ Copy + Send + + + + Please view the step-by-step guide first.]]> + + Step-by-step guide + + + Searching for wireless debugging service + Please enable \"Wireless debugging\" in \"Developer options\". \"Wireless debugging\" is automatically disabled when network changes.\n\nNote, do not disable \"Developer options\" or \"USB debugging\", or Shizuku will be stopped. + Please try to disable and enable \"Wireless debugging\" if it keeps searching. + Port + Port is an integer ranging from 1 to 65535. + Pairing + Pair with device + Searching for pairing service + Pairing code + Pairing code is wrong. + Can\'t connect to wireless debugging service. + Wireless debugging is not enabled.\nNote, before Android 11, to enable wireless debugging, computer connection is a must. + Please start paring by the following steps: \"Developer Options\" - \"Wireless debugging\" - \"Pairing device using pairing code\".\n\nAfter the pairing process starts, you will able to input the pairing code. + Please enter split-screen (multi-window) mode first. + The system requires the pairing dialog always visible, using split-screen mode is the only way to let this app and system dialog visible at the same time. + Unable to generate key for wireless debugging.\nThis may be because KeyStore mechanism of this device is broken. + Please go through the pairing step first. + Developer options + Notification options + Pair Shizuku with your device + A notification from Shizuku will help you complete the pairing. + Enter \"Developer options\" - \"Wireless debugging\". Tap \"Pair device with pairing code\", you will see a six-digit code. + Enter the code in the notification to complete pairing. + The pairing process needs you to interact with a notification from Shizuku. Please allow Shizuku to post notifications. + MIUI users may need to switch notification style to \"Android\" from \"Notification\" - \"Notification shade\" in system settings. + Otherwise, you may not able to enter paring code from the notification. + Please note, left part of the \"Wireless debugging\" option is clickable, tapping it will open a new page. Only turing on the switch on the right is incorrect. + Back to Shizuku and start Shizuku. + Shizuku needs to access local network. It is controlled by the network permission. + Some systems (such as MIUI) disallow apps to access the network when they are not visible, even if the app uses foreground service as standard. Please disable battery optimization features for Shizuku on such systems. + + + + You can refer to %s.]]> + + Start + Restart + + + Application management + + + + + + Apps that has requested or declared Shizuku will show here. + + + Learn Shizuku + Learn how to develop with Shizuku + + + You need to take an extra step + Your device manufacturer has restricted adb permissions and apps using Shizuku will not work properly.\n\nUsually, this limitation can be lifted by adjusting some options in \"Developer options\". Please read the help for details on how to do this.\n\nYou may need to restart Shizuku for the operation to take effect. + + + The permission of adb is limited + There may be a solution for your system in this document.]]> + * requires Shizuku runs with root + + + Use Shizuku in terminal apps + Run commands through Shizuku in terminal apps you like + First, Export files to any where you want. You will find two files, %1$s and %2$s. + If there are files with the same name in the selected folder, they will be deleted.\n\nThe export function uses SAF (Storage Access Framework). It\'s reported that MIUI breaks the functions of SAF. If you are using MIUI, you may have to extract the file from Shizuku\'s apk or download from GitHub. + Export files + Then, use any text editor to open and edit %1$s. + For example, if you want to use Shizuku in %1$s, you should replace %2$s with %3$s (%4$s is the package name of %1$s). + Finally, move the files to somewhere where your terminal app can access, you will be able to use %1$s to run commands through Shizuku. + Some tips: grant execute permission to %1$s and add it to %2$s, you will able to use %1$s directly. + About the detailed usage %1$s, tap to view the document. + ]]> + + + Settings + Language + Appearance + Black night theme + Use the pure black theme if night mode is enabled + Startup + Translation contributors + Participate in translation + Help us translate %s into your language + Start on boot (root) + For rooted devices, Shizuku is able to start automatically on boot + Use system theme color + + + About + + + + Stop Shizuku + Shizuku service will be stopped. + + + Service start status + Shizuku service is starting… + Start Shizuku service failed. + Failed to request root permission. + Working… + Wireless debugging pairing + Enter pairing code + Searching for pairing service + Pairing service found + Stop searching + Pairing in progress + Pairing successful + You can start Shizuku service now. + Pairing failed + Retry + + + Shizuku + @string/permission_label + access Shizuku + Allow the app to use Shizuku. + %1$s to %2$s?]]> + Allow all the time + "Deny" + + + Starter + Starting root shell… + Can\'t start service because root permission is not granted or this device is not rooted. + + + Can\'t start browser + %s\nhas been copied to clipboard. + %s has been copied to clipboard.]]> + + + %1$s does not support modern Shizuku + Please ask the developer of %1$s to update.]]> + %1$s is requesting legacy Shizuku + %1$s has modern Shizuku support, but it\'s requesting legacy Shizuku. This could because Shizuku is not running, please check in Shizuku app.

Legacy Shizuku has been deprecated since March 2019.]]> + Open Shizuku + + diff --git a/systemstubs/build.gradle.kts b/systemstubs/build.gradle.kts index 3e715ee3ff..319ee62af5 100644 --- a/systemstubs/build.gradle.kts +++ b/systemstubs/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.library") id("org.jetbrains.kotlin.android") + id("dev.rikka.tools.refine") } android { @@ -9,18 +10,11 @@ android { defaultConfig { minSdk = 21 - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") } buildTypes { release { isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro", - ) } create("debug_release") { @@ -36,4 +30,7 @@ android { } dependencies { -} + annotationProcessor("dev.rikka.tools.refine:annotation-processor:4.3.0") + compileOnly("dev.rikka.tools.refine:annotation:4.3.0") + implementation("androidx.annotation:annotation-jvm:1.9.1") +} \ No newline at end of file diff --git a/systemstubs/src/main/java/com/android/org/conscrypt/Conscrypt.java b/systemstubs/src/main/java/com/android/org/conscrypt/Conscrypt.java new file mode 100644 index 0000000000..ebd43657e9 --- /dev/null +++ b/systemstubs/src/main/java/com/android/org/conscrypt/Conscrypt.java @@ -0,0 +1,28 @@ +package com.android.org.conscrypt; + +import androidx.annotation.RequiresApi; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSocket; + +@RequiresApi(29) +public class Conscrypt { + + /** + * Exports a value derived from the TLS master secret as described in RFC 5705. + * + * @param label the label to use in calculating the exported value. This must be + * an ASCII-only string. + * @param context the application-specific context value to use in calculating the + * exported value. This may be {@code null} to use no application context, which is + * treated differently than an empty byte array. + * @param length the number of bytes of keying material to return. + * @return a value of the specified length, or {@code null} if the handshake has not yet + * completed or the connection has been closed. + * @throws SSLException if the value could not be exported. + */ + public static byte[] exportKeyingMaterial(SSLSocket socket, String label, byte[] context, + int length) throws SSLException { + throw new RuntimeException("STUB"); + } +} From 01f2c29cc8ce925db7011ce1a97dfe7f5d54c8d3 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 12 May 2025 13:10:54 +0200 Subject: [PATCH 59/95] #1394 our own shizuku implementation to start the Evdev service is working --- .../sds100/keymapper/BaseMainActivity.kt | 36 ++++++++++++++- nativelib/build.gradle.kts | 10 +++-- nativelib/src/main/cpp/nativelib.cpp | 2 +- nativelib/src/main/cpp/starter.cpp | 44 ++++++++++++++----- .../keymapper/nativelib/EvdevService.kt | 16 +++++-- .../keymapper/nativelib/starter/Starter.kt | 19 ++++---- nativelib/src/main/res/raw/start.sh | 2 +- .../java/android/ddm/DdmHandleAppName.java | 8 ++++ 8 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 systemstubs/src/main/java/android/ddm/DdmHandleAppName.java diff --git a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt index 1fafab413d..bad00cbea4 100644 --- a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt @@ -9,12 +9,14 @@ import android.content.Intent import android.content.IntentFilter import android.content.ServiceConnection import android.content.res.Configuration +import android.graphics.Typeface import android.net.Uri import android.os.Build import android.os.Bundle import android.os.IBinder import android.util.Log import android.view.MotionEvent +import android.widget.TextView import android.widget.Toast import androidx.activity.SystemBarStyle import androidx.activity.enableEdgeToEdge @@ -36,6 +38,7 @@ import androidx.preference.PreferenceManager import com.anggrayudi.storage.extension.openInputStream import com.anggrayudi.storage.extension.openOutputStream import com.anggrayudi.storage.extension.toDocumentFile +import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.github.sds100.keymapper.Constants.PACKAGE_NAME import io.github.sds100.keymapper.compose.ComposeColors import io.github.sds100.keymapper.databinding.ActivityMainBinding @@ -54,6 +57,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import moe.shizuku.manager.adb.AdbClient import moe.shizuku.manager.adb.AdbKey import moe.shizuku.manager.adb.AdbKeyException @@ -204,8 +208,8 @@ abstract class BaseMainActivity : AppCompatActivity() { // See demo.DemoActivity in the Shizuku-API repository. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - startPairingService() -// startAdb("127.0.0.1", 41849) +// startPairingService() + startAdb("127.0.0.1", 35051) } // val userServiceArgs = @@ -327,8 +331,36 @@ abstract class BaseMainActivity : AppCompatActivity() { } } + @RequiresApi(Build.VERSION_CODES.M) + private fun writeStarterFiles() { + lifecycleScope.launch(Dispatchers.IO) { + try { + Starter.writeSdcardFiles(applicationContext) + } catch (e: Throwable) { + withContext(Dispatchers.Main) { + MaterialAlertDialogBuilder(this@BaseMainActivity) + .setTitle("Cannot write files") + .setMessage(Log.getStackTraceString(e)) + .setPositiveButton(android.R.string.ok, null) + .create() + .apply { + setOnShowListener { + this.findViewById(android.R.id.message)!!.apply { + typeface = Typeface.MONOSPACE + setTextIsSelectable(true) + } + } + } + .show() + } + } + } + } + @RequiresApi(Build.VERSION_CODES.M) private fun startAdb(host: String, port: Int) { + writeStarterFiles() + sb.append("Starting with wireless adb...").append('\n').append('\n') postResult() diff --git a/nativelib/build.gradle.kts b/nativelib/build.gradle.kts index 0047dc4b95..8c3f037c53 100644 --- a/nativelib/build.gradle.kts +++ b/nativelib/build.gradle.kts @@ -17,7 +17,7 @@ android { externalNativeBuild { cmake { // -DANDROID_STL=none is required by Rikka's library: https://github.com/RikkaW/libcxx-prefab - // -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON is required to get the app running on the Android 15 emulator. This is related to the new 16kB page size support. + // -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON is required to get the app running on the Android 15. This is related to the new 16kB page size support. arguments("-DANDROID_STL=none", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON") } } @@ -48,8 +48,12 @@ android { packaging { jniLibs { useLegacyPackaging = false + + // This is required on Android 15. Otherwise a java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH/DT_GNU_HASH error is thrown. + keepDebugSymbols.add("**/*.so") } } + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 @@ -61,8 +65,8 @@ android { } dependencies { -// compileOnly(project(":systemstubs")) - implementation("org.conscrypt:conscrypt-android:2.5.2") + compileOnly(project(":systemstubs")) + implementation("org.conscrypt:conscrypt-android:2.5.3") implementation("androidx.core:core-ktx:1.16.0") implementation("androidx.appcompat:appcompat:1.7.0") implementation("com.google.android.material:material:1.12.0") diff --git a/nativelib/src/main/cpp/nativelib.cpp b/nativelib/src/main/cpp/nativelib.cpp index 6175a9dbb5..5553cad66f 100644 --- a/nativelib/src/main/cpp/nativelib.cpp +++ b/nativelib/src/main/cpp/nativelib.cpp @@ -12,7 +12,7 @@ extern "C" JNIEXPORT jstring JNICALL Java_io_github_sds100_keymapper_nativelib_EvdevService_stringFromJNI(JNIEnv *env, jobject /* this */) { - char *input_file_path = "/dev/input/event3"; + char *input_file_path = "/dev/input/event12"; struct libevdev *dev = NULL; int fd; int rc = 1; diff --git a/nativelib/src/main/cpp/starter.cpp b/nativelib/src/main/cpp/starter.cpp index bd9bb865bb..372c154e79 100644 --- a/nativelib/src/main/cpp/starter.cpp +++ b/nativelib/src/main/cpp/starter.cpp @@ -30,9 +30,9 @@ #define EXIT_FATAL_KILL 9 #define EXIT_FATAL_BINDER_BLOCKED_BY_SELINUX 10 -#define PACKAGE_NAME "moe.shizuku.privileged.api" +#define PACKAGE_NAME "io.github.sds100.keymapper.debug" #define SERVER_NAME "shizuku_server" -#define SERVER_CLASS_PATH "rikka.shizuku.server.ShizukuService" +#define SERVER_CLASS_PATH "io.github.sds100.keymapper.nativelib.EvdevService" #if defined(__arm__) #define ABI "armeabi-v7a" @@ -44,8 +44,9 @@ #define ABI "arm64-v8a" #endif -static void run_server(const char *dex_path, const char *main_class, const char *process_name) { - if (setenv("CLASSPATH", dex_path, true)) { +static void run_server(const char *apk_path, const char *lib_path, const char *main_class, + const char *process_name) { + if (setenv("CLASSPATH", apk_path, true)) { LOGE("can't set CLASSPATH\n"); exit(EXIT_FATAL_SET_CLASSPATH); } @@ -91,12 +92,9 @@ v_current = (uintptr_t) v + v_size - sizeof(char *); \ #define ARG_PUSH_DEBUG_ONLY(v, arg) #endif - char lib_path[PATH_MAX]{0}; - snprintf(lib_path, PATH_MAX, "%s!/lib/%s", dex_path, ABI); - ARG(argv) ARG_PUSH(argv, "/system/bin/app_process") - ARG_PUSH_FMT(argv, "-Djava.class.path=%s", dex_path) + ARG_PUSH_FMT(argv, "-Djava.class.path=%s", apk_path) ARG_PUSH_FMT(argv, "-Dshizuku.library.path=%s", lib_path) ARG_PUSH_DEBUG_VM_PARAMS(argv) ARG_PUSH(argv, "/system/bin") @@ -112,10 +110,12 @@ v_current = (uintptr_t) v + v_size - sizeof(char *); \ } } -static void start_server(const char *path, const char *main_class, const char *process_name) { +static void start_server(const char *apk_path, const char *lib_path, const char *main_class, + const char *process_name) { + if (daemon(false, false) == 0) { LOGD("child"); - run_server(path, main_class, process_name); + run_server(apk_path, lib_path, main_class, process_name); } else { perrorf("fatal: can't fork\n"); exit(EXIT_FATAL_FORK); @@ -169,12 +169,20 @@ char *context = nullptr; int starter_main(int argc, char *argv[]) { char *apk_path = nullptr; + char *lib_path = nullptr; + + // Get the apk path from the program arguments. This gets the path by setting the + // start of the apk path array to after the "--apk=" by offsetting by 6 characters. for (int i = 0; i < argc; ++i) { if (strncmp(argv[i], "--apk=", 6) == 0) { apk_path = argv[i] + 6; + } else if (strncmp(argv[i], "--lib=", 6) == 0) { + lib_path = argv[i] + 6; } } + printf("info: apk path = %s\n", apk_path); + int uid = getuid(); if (uid != 0 && uid != 2000) { perrorf("fatal: run Shizuku from non root nor adb user (uid=%d).\n", uid); @@ -242,7 +250,8 @@ int starter_main(int argc, char *argv[]) { if (kill(pid, SIGKILL) == 0) printf("info: killed %d (%s)\n", pid, name); else if (errno == EPERM) { - perrorf("fatal: can't kill %d, please try to stop existing Shizuku from app first.\n", pid); + perrorf("fatal: can't kill %d, please try to stop existing Shizuku from app first.\n", + pid); exit(EXIT_FATAL_KILL); } else { printf("warn: failed to kill %d (%s)\n", pid, name); @@ -254,6 +263,11 @@ int starter_main(int argc, char *argv[]) { fflush(stdout); } + if (access(lib_path, R_OK) == 0) { + printf("info: use lib path from argv\n"); + fflush(stdout); + } + if (!apk_path) { auto f = popen("pm path " PACKAGE_NAME, "r"); if (f) { @@ -272,7 +286,13 @@ int starter_main(int argc, char *argv[]) { exit(EXIT_FATAL_PM_PATH); } + if (!lib_path) { + perrorf("fatal: can't get path of native libraries\n"); + exit(EXIT_FATAL_PM_PATH); + } + printf("info: apk path is %s\n", apk_path); + printf("info: lib path is %s\n", lib_path); if (access(apk_path, R_OK) != 0) { perrorf("fatal: can't access manager %s\n", apk_path); exit(EXIT_FATAL_PM_PATH); @@ -281,7 +301,7 @@ int starter_main(int argc, char *argv[]) { printf("info: starting server...\n"); fflush(stdout); LOGD("start_server"); - start_server(apk_path, SERVER_CLASS_PATH, SERVER_NAME); + start_server(apk_path, lib_path, SERVER_CLASS_PATH, SERVER_NAME); exit(EXIT_SUCCESS); } diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/EvdevService.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/EvdevService.kt index 661fcff715..1b76d10c55 100644 --- a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/EvdevService.kt +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/EvdevService.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.nativelib +import android.ddm.DdmHandleAppName import android.system.Os import android.util.Log import kotlin.system.exitProcess @@ -17,13 +18,20 @@ class EvdevService : IEvdevService.Stub() { external fun stringFromJNI(): String companion object { - // Used to load the 'nativelib' library on application startup. - init { - System.loadLibrary("nativelib") + private const val TAG: String = "EvdevService" + + @JvmStatic + fun main(args: Array) { + DdmHandleAppName.setAppName("keymapper_evdev", 0) + EvdevService() } } - private val TAG: String = "EvdevService" + init { + Log.e(TAG, "SYSTEM PROPERTY ${System.getProperty("shizuku.library.path")}") + System.load("${System.getProperty("shizuku.library.path")}/libnativelib.so") + stringFromJNI() + } override fun destroy() { Log.i(TAG, "destroy") diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt index 57dadc829e..83cb7718d4 100644 --- a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt +++ b/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt @@ -5,16 +5,8 @@ import android.os.Build import android.os.UserManager import android.system.ErrnoException import android.system.Os -import android.util.Log import androidx.annotation.RequiresApi import io.github.sds100.keymapper.nativelib.R -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import moe.shizuku.manager.adb.AdbClient -import moe.shizuku.manager.adb.AdbKey -import moe.shizuku.manager.adb.AdbKeyException -import moe.shizuku.manager.adb.PreferenceAdbKeyStore import moe.shizuku.manager.ktx.createDeviceProtectedStorageContextCompat import moe.shizuku.manager.ktx.logd import moe.shizuku.manager.ktx.loge @@ -59,7 +51,10 @@ object Starter { val dir = filesDir.parentFile ?: throw IOException("$filesDir parentFile returns null") val starter = copyStarter(context, File(dir, "starter")) val sh = writeScript(context, File(dir, "start.sh"), starter) - commandInternal[1] = "sh $sh" + val apkPath = context.applicationInfo.sourceDir + val libPath = context.applicationInfo.nativeLibraryDir + + commandInternal[1] = "sh $sh --apk=$apkPath --lib=$libPath" logd(commandInternal[1]!!) } @@ -82,7 +77,11 @@ object Starter { try { val starter = copyStarter(context, File(dir, "starter")) val sh = writeScript(context, File(dir, "start.sh"), starter) - commandInternal[0] = "sh $sh --apk=${context.applicationInfo.sourceDir}" + + val apkPath = context.applicationInfo.sourceDir + val libPath = context.applicationInfo.nativeLibraryDir + + commandInternal[0] = "sh $sh --apk=$apkPath --lib=$libPath" logd(commandInternal[0]!!) if (permission) { diff --git a/nativelib/src/main/res/raw/start.sh b/nativelib/src/main/res/raw/start.sh index 25d46f8717..815483b755 100644 --- a/nativelib/src/main/res/raw/start.sh +++ b/nativelib/src/main/res/raw/start.sh @@ -39,7 +39,7 @@ fi if [ -f $STARTER_PATH ]; then echo "info: exec $STARTER_PATH" - $STARTER_PATH "$1" + $STARTER_PATH "$1" "$2" result=$? if [ ${result} -ne 0 ]; then echo "info: shizuku_starter exit with non-zero value $result" diff --git a/systemstubs/src/main/java/android/ddm/DdmHandleAppName.java b/systemstubs/src/main/java/android/ddm/DdmHandleAppName.java new file mode 100644 index 0000000000..d1b7c37864 --- /dev/null +++ b/systemstubs/src/main/java/android/ddm/DdmHandleAppName.java @@ -0,0 +1,8 @@ +package android.ddm; + +public class DdmHandleAppName { + + public static void setAppName(String name, int userId) { + throw new RuntimeException("STUB"); + } +} From 433936a3a2422e5ddec21d31c45a8d98f4e29bb1 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 12 May 2025 16:22:05 +0200 Subject: [PATCH 60/95] write changelog for 3.1.1 --- CHANGELOG.md | 2 +- app/src/main/assets/whats-new.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d190e184a1..b57ad351a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [3.1.1](https://github.com/sds100/KeyMapper/releases/tag/v3.1.1) -#### TO BE RELEASED +#### 12 May 2025 ## Added diff --git a/app/src/main/assets/whats-new.txt b/app/src/main/assets/whats-new.txt index d2b07cf8ea..aa0d3355cb 100644 --- a/app/src/main/assets/whats-new.txt +++ b/app/src/main/assets/whats-new.txt @@ -4,6 +4,8 @@ Fix for Minecraft 1.21.80! 🔎 Action to interact with app elements. +Other bug fixes. + == 3.0 features == 🫧 Floating Buttons: you can create custom on-screen buttons to trigger key maps. From 030ea75273aac39a844ae4185f80b56b49f80c8c Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 12 May 2025 14:22:39 +0000 Subject: [PATCH 61/95] New Crowdin translations by GitHub Action --- app/src/main/res/values-tr/strings.xml | 22 +++++++- .../metadata/android/ar/full_description.txt | 55 +++++++++++++++++++ .../metadata/android/ar/short_description.txt | 1 + fastlane/metadata/android/ar/title.txt | 1 + .../android/cs_CZ/full_description.txt | 55 +++++++++++++++++++ .../android/cs_CZ/short_description.txt | 1 + fastlane/metadata/android/cs_CZ/title.txt | 1 + .../android/de_DE/full_description.txt | 55 +++++++++++++++++++ .../android/de_DE/short_description.txt | 1 + fastlane/metadata/android/de_DE/title.txt | 1 + .../android/es_ES/full_description.txt | 55 +++++++++++++++++++ .../android/es_ES/short_description.txt | 1 + fastlane/metadata/android/es_ES/title.txt | 1 + .../android/fr_FR/full_description.txt | 55 +++++++++++++++++++ .../android/fr_FR/short_description.txt | 1 + fastlane/metadata/android/fr_FR/title.txt | 1 + .../android/hu_HU/full_description.txt | 55 +++++++++++++++++++ .../android/hu_HU/short_description.txt | 1 + fastlane/metadata/android/hu_HU/title.txt | 1 + .../android/id_ID/full_description.txt | 55 +++++++++++++++++++ .../android/id_ID/short_description.txt | 1 + fastlane/metadata/android/id_ID/title.txt | 1 + .../android/ka_GE/full_description.txt | 55 +++++++++++++++++++ .../android/ka_GE/short_description.txt | 1 + fastlane/metadata/android/ka_GE/title.txt | 1 + .../android/ko_KR/full_description.txt | 55 +++++++++++++++++++ .../android/ko_KR/short_description.txt | 1 + fastlane/metadata/android/ko_KR/title.txt | 1 + .../android/pl_PL/full_description.txt | 55 +++++++++++++++++++ .../android/pl_PL/short_description.txt | 1 + fastlane/metadata/android/pl_PL/title.txt | 1 + .../android/pt_BR/full_description.txt | 55 +++++++++++++++++++ .../android/pt_BR/short_description.txt | 1 + fastlane/metadata/android/pt_BR/title.txt | 1 + .../android/ru_RU/full_description.txt | 55 +++++++++++++++++++ .../android/ru_RU/short_description.txt | 1 + fastlane/metadata/android/ru_RU/title.txt | 1 + .../metadata/android/sk/full_description.txt | 55 +++++++++++++++++++ .../metadata/android/sk/short_description.txt | 1 + fastlane/metadata/android/sk/title.txt | 1 + .../android/tr_TR/short_description.txt | 2 +- .../metadata/android/uk/full_description.txt | 55 +++++++++++++++++++ .../metadata/android/uk/short_description.txt | 1 + fastlane/metadata/android/uk/title.txt | 1 + .../metadata/android/vi/full_description.txt | 55 +++++++++++++++++++ .../metadata/android/vi/short_description.txt | 1 + fastlane/metadata/android/vi/title.txt | 1 + .../android/zh_CN/full_description.txt | 55 +++++++++++++++++++ .../android/zh_CN/short_description.txt | 1 + fastlane/metadata/android/zh_CN/title.txt | 1 + .../android/zh_TW/full_description.txt | 55 +++++++++++++++++++ .../android/zh_TW/short_description.txt | 1 + fastlane/metadata/android/zh_TW/title.txt | 1 + 53 files changed, 990 insertions(+), 3 deletions(-) create mode 100644 fastlane/metadata/android/ar/full_description.txt create mode 100644 fastlane/metadata/android/ar/short_description.txt create mode 100644 fastlane/metadata/android/ar/title.txt create mode 100644 fastlane/metadata/android/cs_CZ/full_description.txt create mode 100644 fastlane/metadata/android/cs_CZ/short_description.txt create mode 100644 fastlane/metadata/android/cs_CZ/title.txt create mode 100644 fastlane/metadata/android/de_DE/full_description.txt create mode 100644 fastlane/metadata/android/de_DE/short_description.txt create mode 100644 fastlane/metadata/android/de_DE/title.txt create mode 100644 fastlane/metadata/android/es_ES/full_description.txt create mode 100644 fastlane/metadata/android/es_ES/short_description.txt create mode 100644 fastlane/metadata/android/es_ES/title.txt create mode 100644 fastlane/metadata/android/fr_FR/full_description.txt create mode 100644 fastlane/metadata/android/fr_FR/short_description.txt create mode 100644 fastlane/metadata/android/fr_FR/title.txt create mode 100644 fastlane/metadata/android/hu_HU/full_description.txt create mode 100644 fastlane/metadata/android/hu_HU/short_description.txt create mode 100644 fastlane/metadata/android/hu_HU/title.txt create mode 100644 fastlane/metadata/android/id_ID/full_description.txt create mode 100644 fastlane/metadata/android/id_ID/short_description.txt create mode 100644 fastlane/metadata/android/id_ID/title.txt create mode 100644 fastlane/metadata/android/ka_GE/full_description.txt create mode 100644 fastlane/metadata/android/ka_GE/short_description.txt create mode 100644 fastlane/metadata/android/ka_GE/title.txt create mode 100644 fastlane/metadata/android/ko_KR/full_description.txt create mode 100644 fastlane/metadata/android/ko_KR/short_description.txt create mode 100644 fastlane/metadata/android/ko_KR/title.txt create mode 100644 fastlane/metadata/android/pl_PL/full_description.txt create mode 100644 fastlane/metadata/android/pl_PL/short_description.txt create mode 100644 fastlane/metadata/android/pl_PL/title.txt create mode 100644 fastlane/metadata/android/pt_BR/full_description.txt create mode 100644 fastlane/metadata/android/pt_BR/short_description.txt create mode 100644 fastlane/metadata/android/pt_BR/title.txt create mode 100644 fastlane/metadata/android/ru_RU/full_description.txt create mode 100644 fastlane/metadata/android/ru_RU/short_description.txt create mode 100644 fastlane/metadata/android/ru_RU/title.txt create mode 100644 fastlane/metadata/android/sk/full_description.txt create mode 100644 fastlane/metadata/android/sk/short_description.txt create mode 100644 fastlane/metadata/android/sk/title.txt create mode 100644 fastlane/metadata/android/uk/full_description.txt create mode 100644 fastlane/metadata/android/uk/short_description.txt create mode 100644 fastlane/metadata/android/uk/title.txt create mode 100644 fastlane/metadata/android/vi/full_description.txt create mode 100644 fastlane/metadata/android/vi/short_description.txt create mode 100644 fastlane/metadata/android/vi/title.txt create mode 100644 fastlane/metadata/android/zh_CN/full_description.txt create mode 100644 fastlane/metadata/android/zh_CN/short_description.txt create mode 100644 fastlane/metadata/android/zh_CN/title.txt create mode 100644 fastlane/metadata/android/zh_TW/full_description.txt create mode 100644 fastlane/metadata/android/zh_TW/short_description.txt create mode 100644 fastlane/metadata/android/zh_TW/title.txt diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 90b10fd127..2e9e7dfb88 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -21,6 +21,7 @@ Erişilebilirlik servisini yeniden başlat Paylaş Burada hiçbir şey yok! + Key Mapper hiçbir etkileşim algılamadı. Ek öğeler göstermeyi deneyin. Tekrarlamayı durdur… Tetikleyici bırakıldığında Tetikleyici tekrar basıldığında @@ -41,8 +42,8 @@ Koordinat seçimi tamamlandı Key Mapper günlüğü Neler Yeni - Ses dosyası, Key Mapper’ın özel veri klasörüne kopyalanacak; bu, dosya taşınsa veya silinse bile eylemlerinizin çalışmaya devam edeceği anlamına gelir. Ayrıca, tuş eşlemelerinizle birlikte zip klasöründe yedeklenecektir. - Kaydedilen ses dosyalarını ayarlardan silebilirsiniz. + Sistem zil sesini kullanabilir veya özel bir ses dosyası seçebilirsiniz.\n\nÖzel ses dosyası, Key Mapper\'ın özel veri klasörüne kopyalanacak; bu, dosya taşınsa veya silinse bile işlemlerinizin çalışmaya devam edeceği anlamına gelir. Ayrıca, anahtar haritalarınızla birlikte zip klasöründe yedeklenecektir. +Kaydedilmiş ses dosyalarını ayarlardan silebilirsiniz. Eşleştirilmiş cihaz bulunamadı. Bluetooth açık mı? Kısayol olarak kullanmak için bir tuş eşlemesine dokunun. Tuş eşleme kısayolu oluştur @@ -71,6 +72,7 @@ Erişilebilirlik servisi etkin! Tuş eşlemeleriniz çalışmalı. Ekstra günlük kaydı açık! Bir sorunu düzeltmeye çalışmıyorsanız bunu kapatın. Kapat + Daha iyi ekran mesajları, daha fazla işlem ve hizmet güncellemeleri için bildirimleri açın. Hakkında %s uygulamasını aç @@ -87,6 +89,7 @@ %s, %d parmakla %d/%d koordinatlarında %dpx sıkıştırma mesafesiyle %dms içinde (%s) %s numarayı ara Ses çal: %s + Bilinmeyen sesi oynat Seçenekler: @@ -319,6 +322,7 @@ Bayrakları ayarla Sınır yok Ses dosyası seç + Sistem zil sesini seç Eylemi düzenle Eylemi değiştir @@ -777,6 +781,15 @@ Bir uygulama için geri sar %s için geri sar Tüm medya uygulamaları geri sarmayı desteklemez. Örn. Google Play Music. + Medyayı durdur + Bir uygulama için medyayı durdur + %s için medyayı durdur + Medyayı ileri sar + Bir uygulama için medyayı ileri sar + %s için medyayı ileri sar + Medyayı geri sar + Bir uygulama için medyayı geri sar + %s için medyayı geri sar Geri dön Ana ekrana git Son uygulamaları aç @@ -872,6 +885,7 @@ %d öğe seçildi %d öğe seçildi + Etkileşim kuracağınız uygulamayı seçin Tekrar kaydet Uygulama öğesini seçin Tuş haritanızın etkileşime geçmesini istediğiniz öğeyi seçin. @@ -880,6 +894,7 @@ Olası etkileşimler UI öğesiyle nasıl etkileşim kurmak istediğinizi seçin. Etkileşim türünü filtrele + Ek öğeleri göster Herhangi biri Dokun Dokun ve basılı tut @@ -892,7 +907,9 @@ Etkileşim detayları Açıklama Uygulama + Metin/içerik açıklaması Sınıf adı + Araç ipucu/öneri Kaynak kimliğini görüntüle Özgün kimlik Etkileşim türü @@ -1000,6 +1017,7 @@ Kilidi aç (%s) Kullan Yükleniyor… + Satın alındı! Fiyatı tekrar almayı dene Satın alma iptal edildi. Bu, yalnızca Google Play\'den Key Mapper indirilerek satın alınabilen ücretli bir özellik gerektiriyor. diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/ar/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ar/short_description.txt b/fastlane/metadata/android/ar/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/ar/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ar/title.txt b/fastlane/metadata/android/ar/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/ar/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/full_description.txt b/fastlane/metadata/android/cs_CZ/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/cs_CZ/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/short_description.txt b/fastlane/metadata/android/cs_CZ/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/cs_CZ/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/title.txt b/fastlane/metadata/android/cs_CZ/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/cs_CZ/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/full_description.txt b/fastlane/metadata/android/de_DE/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/de_DE/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/short_description.txt b/fastlane/metadata/android/de_DE/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/de_DE/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/title.txt b/fastlane/metadata/android/de_DE/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/de_DE/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/full_description.txt b/fastlane/metadata/android/es_ES/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/es_ES/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/short_description.txt b/fastlane/metadata/android/es_ES/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/es_ES/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/title.txt b/fastlane/metadata/android/es_ES/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/es_ES/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/full_description.txt b/fastlane/metadata/android/fr_FR/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/fr_FR/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/short_description.txt b/fastlane/metadata/android/fr_FR/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/fr_FR/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/title.txt b/fastlane/metadata/android/fr_FR/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/fr_FR/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/full_description.txt b/fastlane/metadata/android/hu_HU/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/hu_HU/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/short_description.txt b/fastlane/metadata/android/hu_HU/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/hu_HU/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/title.txt b/fastlane/metadata/android/hu_HU/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/hu_HU/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/full_description.txt b/fastlane/metadata/android/id_ID/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/id_ID/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/short_description.txt b/fastlane/metadata/android/id_ID/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/id_ID/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/title.txt b/fastlane/metadata/android/id_ID/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/id_ID/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/full_description.txt b/fastlane/metadata/android/ka_GE/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/ka_GE/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/short_description.txt b/fastlane/metadata/android/ka_GE/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/ka_GE/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/title.txt b/fastlane/metadata/android/ka_GE/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/ka_GE/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/full_description.txt b/fastlane/metadata/android/ko_KR/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/ko_KR/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/short_description.txt b/fastlane/metadata/android/ko_KR/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/ko_KR/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/title.txt b/fastlane/metadata/android/ko_KR/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/ko_KR/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/full_description.txt b/fastlane/metadata/android/pl_PL/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/pl_PL/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/short_description.txt b/fastlane/metadata/android/pl_PL/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/pl_PL/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/title.txt b/fastlane/metadata/android/pl_PL/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/pl_PL/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/full_description.txt b/fastlane/metadata/android/pt_BR/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/pt_BR/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/short_description.txt b/fastlane/metadata/android/pt_BR/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/pt_BR/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/title.txt b/fastlane/metadata/android/pt_BR/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/pt_BR/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/full_description.txt b/fastlane/metadata/android/ru_RU/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/ru_RU/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/short_description.txt b/fastlane/metadata/android/ru_RU/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/ru_RU/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/title.txt b/fastlane/metadata/android/ru_RU/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/ru_RU/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/sk/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/sk/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/sk/title.txt b/fastlane/metadata/android/sk/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/sk/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/tr_TR/short_description.txt b/fastlane/metadata/android/tr_TR/short_description.txt index 11ab9493ec..9dbca48bd4 100644 --- a/fastlane/metadata/android/tr_TR/short_description.txt +++ b/fastlane/metadata/android/tr_TR/short_description.txt @@ -1 +1 @@ -HER ŞEY için kısayollar oluşturun! \ No newline at end of file +HER ŞEY için kısayollar oluşturun! Ses, güç, klavye veya kayan düğmeleri yeniden atayın! \ No newline at end of file diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/uk/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/uk/short_description.txt b/fastlane/metadata/android/uk/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/uk/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/uk/title.txt b/fastlane/metadata/android/uk/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/uk/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/vi/full_description.txt b/fastlane/metadata/android/vi/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/vi/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/vi/short_description.txt b/fastlane/metadata/android/vi/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/vi/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/vi/title.txt b/fastlane/metadata/android/vi/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/vi/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/full_description.txt b/fastlane/metadata/android/zh_CN/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/zh_CN/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/short_description.txt b/fastlane/metadata/android/zh_CN/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/zh_CN/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/title.txt b/fastlane/metadata/android/zh_CN/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/zh_CN/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/full_description.txt b/fastlane/metadata/android/zh_TW/full_description.txt new file mode 100644 index 0000000000..6863b48341 --- /dev/null +++ b/fastlane/metadata/android/zh_TW/full_description.txt @@ -0,0 +1,55 @@ +Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! + +Key Mapper supports a huge variety of buttons and keys*: + +- ALL your phone buttons (volume AND side key) +- Game controllers (D-pad, ABXY, and most others) +- Keyboards +- Headsets and headphones +- Fingerprint sensor + +Not enough keys? Design your own on-screen button layouts and remap those just like real keys! + + +What shortcuts can I make? +-------------------------- + +With over 100 individual actions, the sky is the limit. +Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. + + +How much control do I have? +--------------------------- + +TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. + +ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. + +CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. + +* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. + +Not currently supported: + - Mouse buttons + - Joysticks and triggers (LT,RT) on gamepads + + +Security and accessibility services +--------------------------- + +This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. + +By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. + +It will NOT collect any user data or connect to the internet to send any data anywhere. + +Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. + +Come say hi in our Discord community! +www.keymapper.club + +See the code for yourself! (Open source) +code.keymapper.club + +Read the documentation: +docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/short_description.txt b/fastlane/metadata/android/zh_TW/short_description.txt new file mode 100644 index 0000000000..ecaa2a662d --- /dev/null +++ b/fastlane/metadata/android/zh_TW/short_description.txt @@ -0,0 +1 @@ +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/title.txt b/fastlane/metadata/android/zh_TW/title.txt new file mode 100644 index 0000000000..9810cafe1e --- /dev/null +++ b/fastlane/metadata/android/zh_TW/title.txt @@ -0,0 +1 @@ +Key Mapper & Floating Buttons \ No newline at end of file From 10c0fe22c4e05c86dd01cce8cdf2213f56d8e00e Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 12 May 2025 16:27:44 +0200 Subject: [PATCH 62/95] fix: shorten turkish translation of short description --- fastlane/metadata/android/tr_TR/short_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/tr_TR/short_description.txt b/fastlane/metadata/android/tr_TR/short_description.txt index 9dbca48bd4..11ab9493ec 100644 --- a/fastlane/metadata/android/tr_TR/short_description.txt +++ b/fastlane/metadata/android/tr_TR/short_description.txt @@ -1 +1 @@ -HER ŞEY için kısayollar oluşturun! Ses, güç, klavye veya kayan düğmeleri yeniden atayın! \ No newline at end of file +HER ŞEY için kısayollar oluşturun! \ No newline at end of file From 91562d9feaebcd16b6e6de808f40d04c2b9632ee Mon Sep 17 00:00:00 2001 From: sds100 Date: Sat, 10 May 2025 16:44:07 +0200 Subject: [PATCH 63/95] fix tests --- .../actions/GetActionFailedUseCaseTest.kt | 93 +++++++++---------- .../actions/PerformActionsUseCaseTest.kt | 1 + 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt index 91aad88f8d..608de9dcfa 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/GetActionFailedUseCaseTest.kt @@ -53,6 +53,7 @@ class GetActionFailedUseCaseTest { cameraAdapter = mock(), soundsManager = mock(), shizukuAdapter = mockShizukuAdapter, + ringtoneAdapter = mock(), ) } @@ -60,58 +61,56 @@ class GetActionFailedUseCaseTest { * #776 */ @Test - fun `don't show Shizuku errors if a compatible ime is selected`() = - testScope.runTest { - // GIVEN - whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } - whenever(mockInputMethodAdapter.chosenIme).then { - MutableStateFlow( - ImeInfo( - id = "ime_id", - packageName = "io.github.sds100.keymapper.inputmethod.latin", - label = "Key Mapper GUI Keyboard", - isEnabled = true, - isChosen = true, - ), - ) - } - - val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) - - // WHEN - val error = useCase.actionErrorSnapshot.first().getError(action) - - // THEN - assertThat(error, nullValue()) + fun `don't show Shizuku errors if a compatible ime is selected`() = testScope.runTest { + // GIVEN + whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } + whenever(mockInputMethodAdapter.chosenIme).then { + MutableStateFlow( + ImeInfo( + id = "ime_id", + packageName = "io.github.sds100.keymapper.inputmethod.latin", + label = "Key Mapper GUI Keyboard", + isEnabled = true, + isChosen = true, + ), + ) } + val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) + + // WHEN + val error = useCase.actionErrorSnapshot.first().getError(action) + + // THEN + assertThat(error, nullValue()) + } + /** * #776 */ @Test - fun `show Shizuku errors if a compatible ime is not selected and Shizuku is installed`() = - testScope.runTest { - // GIVEN - whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } - whenever(mockShizukuAdapter.isStarted).then { MutableStateFlow(false) } - - whenever(mockInputMethodAdapter.chosenIme).then { - MutableStateFlow( - ImeInfo( - id = "ime_id", - packageName = "io.gboard", - label = "Gboard", - isEnabled = true, - isChosen = true, - ), - ) - } - - val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) - // WHEN - val error = useCase.actionErrorSnapshot.first().getError(action) - - // THEN - assertThat(error, `is`(Error.ShizukuNotStarted)) + fun `show Shizuku errors if a compatible ime is not selected and Shizuku is installed`() = testScope.runTest { + // GIVEN + whenever(mockShizukuAdapter.isInstalled).then { MutableStateFlow(true) } + whenever(mockShizukuAdapter.isStarted).then { MutableStateFlow(false) } + + whenever(mockInputMethodAdapter.chosenIme).then { + MutableStateFlow( + ImeInfo( + id = "ime_id", + packageName = "io.gboard", + label = "Gboard", + isEnabled = true, + isChosen = true, + ), + ) } + + val action = ActionData.InputKeyEvent(keyCode = KeyEvent.KEYCODE_VOLUME_DOWN) + // WHEN + val error = useCase.actionErrorSnapshot.first().getError(action) + + // THEN + assertThat(error, `is`(Error.ShizukuNotStarted)) + } } diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt index 69b71487f1..a08354e869 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt @@ -86,6 +86,7 @@ class PerformActionsUseCaseTest { shizukuInputEventInjector = mock(), permissionAdapter = mock(), notificationReceiverAdapter = mock(), + ringtoneAdapter = mock() ) } From a8cf612b7cc702c548739639547ad4c34443b61e Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 12 May 2025 16:28:42 +0200 Subject: [PATCH 64/95] style: fix --- .../sds100/keymapper/actions/PerformActionsUseCaseTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt index a08354e869..d2c9df0c1e 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt @@ -86,7 +86,7 @@ class PerformActionsUseCaseTest { shizukuInputEventInjector = mock(), permissionAdapter = mock(), notificationReceiverAdapter = mock(), - ringtoneAdapter = mock() + ringtoneAdapter = mock(), ) } From ea897e23daea2b3d71aee234c8ff6717ef07171e Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 12 May 2025 17:17:39 +0200 Subject: [PATCH 65/95] #1637 give option to never show notification permission alert again --- .../io/github/sds100/keymapper/data/Keys.kt | 3 +++ .../home/ShowHomeScreenAlertsUseCase.kt | 17 +++++++++--- .../mappings/keymaps/KeyMapListViewModel.kt | 26 ++++++++++++++++--- .../permissions/RequestPermissionDelegate.kt | 19 +++++++++++++- app/src/main/res/values/strings.xml | 5 ++++ 5 files changed, 62 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt index 06ca3f1fec..f4befce2c4 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt @@ -93,4 +93,7 @@ object Keys { * Whether the user viewed the advanced triggers. */ val viewedAdvancedTriggers = booleanPreferencesKey("key_viewed_advanced_triggers") + + val neverShowNotificationPermissionAlert = + booleanPreferencesKey("key_never_show_notification_permission_alert") } diff --git a/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt index edbadbe221..044420acb1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt @@ -8,6 +8,7 @@ import io.github.sds100.keymapper.system.accessibility.ServiceState import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map /** @@ -53,12 +54,21 @@ class ShowHomeScreenAlertsUseCaseImpl( preferences.set(Keys.log, false) } - override val isNotificationPermissionGranted: Flow = - permissions.isGrantedFlow(Permission.POST_NOTIFICATIONS) + override val showNotificationPermissionAlert: Flow = + combine( + permissions.isGrantedFlow(Permission.POST_NOTIFICATIONS), + preferences.get(Keys.neverShowNotificationPermissionAlert).map { it ?: false }, + ) { isGranted, neverShow -> + !isGranted && !neverShow + } override fun requestNotificationPermission() { permissions.request(Permission.POST_NOTIFICATIONS) } + + override fun neverShowNotificationPermissionAlert() { + preferences.set(Keys.neverShowNotificationPermissionAlert, true) + } } interface ShowHomeScreenAlertsUseCase { @@ -76,6 +86,7 @@ interface ShowHomeScreenAlertsUseCase { val isLoggingEnabled: Flow fun disableLogging() - val isNotificationPermissionGranted: Flow + val showNotificationPermissionAlert: Flow fun requestNotificationPermission() + fun neverShowNotificationPermissionAlert() } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt index 1037bec89f..040a10bcab 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt @@ -153,8 +153,8 @@ class KeyMapListViewModel( showAlertsUseCase.accessibilityServiceState, showAlertsUseCase.hideAlerts, showAlertsUseCase.isLoggingEnabled, - showAlertsUseCase.isNotificationPermissionGranted, - ) { isBatteryOptimised, serviceState, isHidden, isLoggingEnabled, isNotificationPermissionGranted -> + showAlertsUseCase.showNotificationPermissionAlert, + ) { isBatteryOptimised, serviceState, isHidden, isLoggingEnabled, showNotificationPermissionAlert -> if (isHidden) { return@combine emptyList() } @@ -189,7 +189,7 @@ class KeyMapListViewModel( ) } // don't show a success message for this - if (!isNotificationPermissionGranted) { + if (showNotificationPermissionAlert) { add( HomeWarningListItem( ID_NOTIFICATION_PERMISSION_DENIED_LIST_ITEM, @@ -683,11 +683,29 @@ class KeyMapListViewModel( ID_BATTERY_OPTIMISATION_LIST_ITEM -> showAlertsUseCase.disableBatteryOptimisation() ID_LOGGING_ENABLED_LIST_ITEM -> showAlertsUseCase.disableLogging() - ID_NOTIFICATION_PERMISSION_DENIED_LIST_ITEM -> showAlertsUseCase.requestNotificationPermission() + ID_NOTIFICATION_PERMISSION_DENIED_LIST_ITEM -> showNotificationPermissionAlertDialog() } } } + private suspend fun showNotificationPermissionAlertDialog() { + val dialog = PopupUi.Dialog( + title = getString(R.string.dialog_title_request_notification_permission), + message = getText(R.string.dialog_message_request_notification_permission), + positiveButtonText = getString(R.string.pos_turn_on), + negativeButtonText = getString(R.string.neg_no_thanks), + neutralButtonText = getString(R.string.pos_never_show_again), + ) + + val dialogResponse = showPopup("notification_permission_alert", dialog) + + if (dialogResponse == DialogResponse.POSITIVE) { + showAlertsUseCase.requestNotificationPermission() + } else if (dialogResponse == DialogResponse.NEUTRAL) { + showAlertsUseCase.neverShowNotificationPermissionAlert() + } + } + fun onTogglePausedClick() { coroutineScope.launch { if (pauseKeyMaps.isPaused.first()) { diff --git a/app/src/main/java/io/github/sds100/keymapper/system/permissions/RequestPermissionDelegate.kt b/app/src/main/java/io/github/sds100/keymapper/system/permissions/RequestPermissionDelegate.kt index d6aee9226c..e3f4b247bb 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/permissions/RequestPermissionDelegate.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/permissions/RequestPermissionDelegate.kt @@ -11,6 +11,7 @@ import android.provider.Settings import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat import androidx.navigation.NavController import io.github.sds100.keymapper.Constants import io.github.sds100.keymapper.NavAppDirections @@ -98,7 +99,23 @@ class RequestPermissionDelegate( requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) Permission.POST_NOTIFICATIONS -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + val showRationale = ActivityCompat.shouldShowRequestPermissionRationale( + activity, + Manifest.permission.POST_NOTIFICATIONS, + ) + + // The system will say you have to show a rationale if the user previously + // denied the permission. Therefore, the permission dialog will not show and so + // open the notification settings to turn it on manually. + if (showRationale) { + Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { + putExtra(Settings.EXTRA_APP_PACKAGE, Constants.PACKAGE_NAME) + + activity.startActivity(this) + } + } else { + requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ddd3cb1615..a209c603bf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -510,6 +510,9 @@ Unrecognized key code The pressed button was not recognized by the input system. In the past Key Mapper detected such buttons as one and the same. Currently the app tries to distinguish the button based on the scan code, which should be more unique. However, this is a makeshift incomplete solution, which doesn\'t guarantee uniqueness. + Turn on notifications + Some actions and options need this permission to work. You can also get notified when there is important news about the app. + Done Guide Guide @@ -522,11 +525,13 @@ Discard changes Save Understood + Turn on Turn off Cancel Don\'t show again Keep editing + No thanks Hide Online guide From 71a1961f8fa8f5f48832a8547565b471cd42ad81 Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 12 May 2025 17:18:13 +0200 Subject: [PATCH 66/95] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 51a39df92c..f68983b405 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.1 -VERSION_CODE=116 +VERSION_CODE=117 VERSION_NUM=0 \ No newline at end of file From a0cd4685be4c25ba901b453cd80e03fd36f628ed Mon Sep 17 00:00:00 2001 From: sds100 Date: Mon, 12 May 2025 17:22:41 +0200 Subject: [PATCH 67/95] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index f68983b405..04dc99c438 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.1 -VERSION_CODE=117 +VERSION_CODE=118 VERSION_NUM=0 \ No newline at end of file From aa5cfcc15c7db446351959e707525703fe74e130 Mon Sep 17 00:00:00 2001 From: sds100 Date: Tue, 13 May 2025 13:52:12 +0200 Subject: [PATCH 68/95] chore: bump version to 3.1.2 --- app/version.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/version.properties b/app/version.properties index 04dc99c438..2752506b43 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ -VERSION_NAME=3.1.1 -VERSION_CODE=118 +VERSION_NAME=3.1.2 +VERSION_CODE=119 VERSION_NUM=0 \ No newline at end of file From 1a7f09c086b68f971827cd78b78b4cd17ba8b589 Mon Sep 17 00:00:00 2001 From: sds100 Date: Tue, 13 May 2025 16:13:43 +0200 Subject: [PATCH 69/95] chore: update libraries --- app/build.gradle | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 13f120a407..459ece98c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -167,7 +167,7 @@ dependencies { def room_version = "2.7.1" def coroutinesVersion = "1.9.0" - def nav_version = '2.8.9' + def nav_version = '2.9.0' def epoxy_version = "4.6.2" def splitties_version = "3.0.0" def multidex_version = "2.0.1" @@ -192,7 +192,7 @@ dependencies { implementation "dev.rikka.shizuku:api:$shizuku_version" implementation "dev.rikka.shizuku:provider:$shizuku_version" implementation "org.lsposed.hiddenapibypass:hiddenapibypass:4.3" - proImplementation 'com.revenuecat.purchases:purchases:8.17.0' + proImplementation 'com.revenuecat.purchases:purchases:8.17.1' proImplementation "com.airbnb.android:lottie-compose:6.6.3" implementation("com.squareup.okhttp3:okhttp:4.12.0") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") @@ -211,9 +211,9 @@ dependencies { implementation "androidx.activity:activity-ktx:1.10.1" implementation "androidx.fragment:fragment-ktx:1.8.6" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.7" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.0" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.9.0" + implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.9.0" implementation "androidx.room:room-ktx:$room_version" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" @@ -228,12 +228,12 @@ dependencies { implementation "androidx.datastore:datastore-preferences:1.2.0-alpha02" implementation "androidx.core:core-splashscreen:1.0.1" implementation "androidx.activity:activity-compose:1.10.1" - implementation "androidx.navigation:navigation-compose:2.8.9" - implementation "androidx.navigation:navigation-fragment-compose:2.8.9" + implementation "androidx.navigation:navigation-compose:2.9.0" + implementation "androidx.navigation:navigation-fragment-compose:2.9.0" ksp "androidx.room:room-compiler:$room_version" // Compose - Dependency composeBom = platform('androidx.compose:compose-bom-beta:2025.04.01') + Dependency composeBom = platform('androidx.compose:compose-bom-beta:2025.05.00') implementation composeBom implementation 'androidx.compose.foundation:foundation' implementation "androidx.compose.ui:ui-android" From 5ec90eed7d870d26629179062e414c108dcad621 Mon Sep 17 00:00:00 2001 From: sds100 Date: Tue, 13 May 2025 16:27:35 +0200 Subject: [PATCH 70/95] fix: floating button sliders weren't rounding properly. This is due to a Material components bug I think because the float value coming from onValueChange in the Slider is slightly wrong. It is 59.99996 instead of 60. --- .../main/java/io/github/sds100/keymapper/util/StringUtils.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/util/StringUtils.kt b/app/src/main/java/io/github/sds100/keymapper/util/StringUtils.kt index 0a3335c89d..0e68e5e666 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/StringUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/StringUtils.kt @@ -1,6 +1,7 @@ package io.github.sds100.keymapper.util import androidx.annotation.IntRange +import kotlin.math.roundToInt /** * Created by sds100 on 24/04/2021. @@ -45,5 +46,5 @@ fun String.getWordBoundaries(@IntRange(from = 0L) cursorPosition: Int): Pair Date: Tue, 13 May 2025 16:51:36 +0200 Subject: [PATCH 71/95] #1686 fix: do not draw behind horizontal system bars or display cut outs --- CHANGELOG.md | 8 ++++++++ .../keymapper/actions/ChooseActionFragment.kt | 15 +++++++++++++-- .../uielement/InteractUiElementFragment.kt | 14 +++++++++++++- .../constraints/ChooseConstraintFragment.kt | 15 +++++++++++++-- .../github/sds100/keymapper/home/HomeFragment.kt | 13 +++++++++++++ .../sds100/keymapper/home/HomeKeyMapListScreen.kt | 5 +---- .../io/github/sds100/keymapper/home/HomeScreen.kt | 10 +--------- .../sds100/keymapper/home/KeyMapListAppBar.kt | 5 ----- .../mappings/keymaps/ConfigKeyMapFragment.kt | 7 ++++++- 9 files changed, 68 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b57ad351a1..d029abf02a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [3.1.2](https://github.com/sds100/KeyMapper/releases/tag/v3.1.2) + +#### TO BE RELEASED + +## Bug fixes + +- #1686 (more fixes) do not show some screens behind system bars on the left/right side of the device. + ## [3.1.1](https://github.com/sds100/KeyMapper/releases/tag/v3.1.1) #### 12 May 2025 diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionFragment.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionFragment.kt index 7abaeb4976..8bc9c8f483 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionFragment.kt @@ -4,7 +4,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.add +import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.os.bundleOf @@ -25,7 +32,6 @@ import io.github.sds100.keymapper.util.ui.showPopups import io.github.sds100.keymapper.util.viewLifecycleScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class ChooseActionFragment : Fragment() { @@ -73,7 +79,12 @@ class ChooseActionFragment : Fragment() { setContent { KeyMapperTheme { ChooseActionScreen( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .windowInsetsPadding( + WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal) + .add(WindowInsets.displayCutout.only(sides = WindowInsetsSides.Horizontal)), + ), viewModel = viewModel, onNavigateBack = findNavController()::navigateUp, ) diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementFragment.kt b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementFragment.kt index ddde228691..c8f0f24c10 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/uielement/InteractUiElementFragment.kt @@ -4,7 +4,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.add +import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.os.bundleOf @@ -71,7 +78,12 @@ class InteractUiElementFragment : Fragment() { setContent { KeyMapperTheme { InteractUiElementScreen( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .windowInsetsPadding( + WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal) + .add(WindowInsets.displayCutout.only(sides = WindowInsetsSides.Horizontal)), + ), viewModel = viewModel, navigateBack = findNavController()::navigateUp, ) diff --git a/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintFragment.kt b/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintFragment.kt index 4d694b4b6b..727bd91534 100644 --- a/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/constraints/ChooseConstraintFragment.kt @@ -4,7 +4,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.add +import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.os.bundleOf @@ -25,7 +32,6 @@ import io.github.sds100.keymapper.util.ui.showPopups import io.github.sds100.keymapper.util.viewLifecycleScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class ChooseConstraintFragment : Fragment() { @@ -73,7 +79,12 @@ class ChooseConstraintFragment : Fragment() { setContent { KeyMapperTheme { ChooseConstraintScreen( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize() + .windowInsetsPadding( + WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal) + .add(WindowInsets.displayCutout.only(sides = WindowInsetsSides.Horizontal)), + ), viewModel = viewModel, onNavigateBack = findNavController()::navigateUp, ) diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeFragment.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeFragment.kt index 52b2a6f59d..5ba4a8c508 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeFragment.kt @@ -4,6 +4,14 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.add +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -58,6 +66,11 @@ class HomeFragment : Fragment() { setContent { KeyMapperTheme { HomeScreen( + modifier = Modifier + .windowInsetsPadding( + WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal) + .add(WindowInsets.displayCutout.only(sides = WindowInsetsSides.Horizontal)), + ), viewModel = homeViewModel, onSettingsClick = { findNavController().navigate(NavAppDirections.toSettingsFragment()) diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt index c169e8c47d..dc46702f69 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing -import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.rememberLazyListState @@ -173,9 +172,7 @@ fun HomeKeyMapListScreen( }, listContent = { KeyMapList( - modifier = Modifier - .windowInsetsPadding(WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal)) - .animateContentSize(), + modifier = Modifier.animateContentSize(), lazyListState = rememberLazyListState(), listItems = state.listItems, footerText = stringResource(R.string.home_key_map_list_footer_text), diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt index 0381317498..b8ffce2f7a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeScreen.kt @@ -7,14 +7,9 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox import androidx.compose.material3.Icon @@ -103,10 +98,7 @@ private fun HomeScreen( val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination = navBackStackEntry?.destination - Column( - modifier // Only take the horizontal because the status bar is the same color as the app bar - .windowInsetsPadding(WindowInsets.displayCutout.only(WindowInsetsSides.Horizontal)), - ) { + Column(modifier) { Box(contentAlignment = Alignment.BottomCenter) { NavHost( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt index 460dce2aa3..01db036dd9 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt @@ -21,14 +21,10 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.windowInsetsPadding @@ -398,7 +394,6 @@ private fun RootGroupAppBar( Surface(color = appBarContainerColor) { GroupRow( modifier = Modifier - .windowInsetsPadding(WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal)) .padding(horizontal = 8.dp) .fillMaxWidth(), groups = state.subGroups, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt index 24d56ea5f8..942c175127 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt @@ -6,6 +6,8 @@ import android.view.View import android.view.ViewGroup import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.add +import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.systemBars @@ -71,7 +73,10 @@ class ConfigKeyMapFragment : Fragment() { KeyMapperTheme { ConfigKeyMapScreen( modifier = Modifier - .windowInsetsPadding(WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal)) + .windowInsetsPadding( + WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal) + .add(WindowInsets.displayCutout.only(sides = WindowInsetsSides.Horizontal)), + ) .fillMaxSize(), viewModel = viewModel, navigateBack = findNavController()::navigateUp, From ccadd85d006d1324bccde13b13cf81658464df45 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 10:50:12 +0200 Subject: [PATCH 72/95] #1394 examples for opening developer options and highlighting the build number and wireless debugging options --- .../sds100/keymapper/BaseMainActivity.kt | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt index bad00cbea4..ad4bb25701 100644 --- a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt @@ -14,6 +14,7 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.os.IBinder +import android.provider.Settings import android.util.Log import android.view.MotionEvent import android.widget.TextView @@ -27,6 +28,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.ui.graphics.toArgb import androidx.core.content.ContextCompat import androidx.core.content.IntentCompat +import androidx.core.os.bundleOf import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.databinding.DataBindingUtil import androidx.lifecycle.Lifecycle @@ -205,11 +207,41 @@ abstract class BaseMainActivity : AppCompatActivity() { ) } + // Go to build number. +// Intent(Settings.ACTION_DEVICE_INFO_SETTINGS).apply { +// val EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key" +// val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args" +// +// putExtra(EXTRA_FRAGMENT_ARG_KEY, "build_number") +// val bundle = bundleOf(EXTRA_FRAGMENT_ARG_KEY to "build_number") +// putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle) +// +// startActivity(this) +// } + + // Highlight wireless debugging setting + Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS).apply { + // See SubSettingLauncher in the Android Settings code for how to + // launch a specific fragment. + // https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/core/SubSettingLauncher.java +// val EXTRA_SHOW_FRAGMENT = ":settings:show_fragment" +// putExtra(EXTRA_SHOW_FRAGMENT, "com.android.settings.development.WirelessDebuggingFragment") +// putExtra(":settings:source_metrics", 1831) + + val EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key" + val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args" + + putExtra(EXTRA_FRAGMENT_ARG_KEY, "toggle_adb_wireless") + val bundle = bundleOf(EXTRA_FRAGMENT_ARG_KEY to "toggle_adb_wireless") + putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle) + startActivity(this) + } + // See demo.DemoActivity in the Shizuku-API repository. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // startPairingService() - startAdb("127.0.0.1", 35051) +// startAdb("127.0.0.1", 35051) } // val userServiceArgs = From 2407d1ceff11f26a95014ea564391cfa4e46c325 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 15:50:09 +0200 Subject: [PATCH 73/95] #1466 demo tap target on home screen to create a key map --- app/build.gradle | 1 + .../keymapper/home/HomeKeyMapListScreen.kt | 246 +++++++++++------- 2 files changed, 150 insertions(+), 97 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 459ece98c8..be0a881da5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,6 +196,7 @@ dependencies { proImplementation "com.airbnb.android:lottie-compose:6.6.3" implementation("com.squareup.okhttp3:okhttp:4.12.0") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") + implementation 'com.canopas.intro-showcase-view:introshowcaseview:2.0.1' // splitties implementation "com.louiscad.splitties:splitties-bitflags:$splitties_version" diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt index dc46702f69..ac16b13189 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt @@ -11,9 +11,13 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing @@ -25,11 +29,14 @@ import androidx.compose.material.icons.automirrored.outlined.ArrowForward import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.FlashlightOn import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -52,6 +59,8 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.canopas.lib.showcase.IntroShowcase +import com.canopas.lib.showcase.component.ShowcaseStyle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.backup.ImportExportState import io.github.sds100.keymapper.backup.RestoreType @@ -151,107 +160,150 @@ fun HomeKeyMapListScreen( var keyMapListBottomPadding by remember { mutableStateOf(100.dp) } - HomeKeyMapListScreen( - modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - snackbarState = snackbarState, - floatingActionButton = { - AnimatedVisibility( - state.appBarState !is KeyMapAppBarState.Selecting, - enter = fadeIn() + slideInHorizontally(initialOffsetX = { it }), - exit = fadeOut() + slideOutHorizontally(targetOffsetX = { it }), - ) { - CollapsableFloatingActionButton( - modifier = Modifier - .padding(bottom = fabBottomPadding) - .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.End)), - onClick = viewModel::onNewKeyMapClick, - showText = viewModel.showFabText, - text = stringResource(R.string.home_fab_new_key_map), - ) - } - }, - listContent = { - KeyMapList( - modifier = Modifier.animateContentSize(), - lazyListState = rememberLazyListState(), - listItems = state.listItems, - footerText = stringResource(R.string.home_key_map_list_footer_text), - isSelectable = state.appBarState is KeyMapAppBarState.Selecting, - onClickKeyMap = viewModel::onKeyMapCardClick, - onLongClickKeyMap = viewModel::onKeyMapCardLongClick, - onSelectedChange = viewModel::onKeyMapSelectedChanged, - onFixClick = viewModel::onFixClick, - onTriggerErrorClick = viewModel::onFixTriggerError, - bottomListPadding = keyMapListBottomPadding, - ) - }, - appBarContent = { - KeyMapListAppBar( - state = state.appBarState, - scrollBehavior = scrollBehavior, - onSettingsClick = onSettingsClick, - onAboutClick = onAboutClick, - onSortClick = { viewModel.showSortBottomSheet = true }, - onHelpClick = { uriHandler.openUriSafe(ctx, helpUrl) }, - onExportClick = viewModel::onExportClick, - onImportClick = { importFileLauncher.launch(FileUtils.MIME_TYPE_ALL) }, - onInputMethodPickerClick = viewModel::showInputMethodPicker, - onTogglePausedClick = viewModel::onTogglePausedClick, - onFixWarningClick = viewModel::onFixWarningClick, - onBackClick = { - if (!viewModel.onBackClick()) { - finishActivity() - } - }, - onSelectAllClick = viewModel::onSelectAllClick, - onNewGroupClick = viewModel::onNewGroupClick, - onRenameGroupClick = viewModel::onRenameGroupClick, - onEditGroupNameClick = viewModel::onEditGroupNameClick, - onGroupClick = viewModel::onGroupClick, - onDeleteGroupClick = viewModel::onDeleteGroupClick, - onNewConstraintClick = viewModel::onNewGroupConstraintClick, - onRemoveConstraintClick = viewModel::onRemoveGroupConstraintClick, - onConstraintModeChanged = viewModel::onGroupConstraintModeChanged, - onFixConstraintClick = viewModel::onFixClick, - ) - }, - selectionBottomSheet = { - AnimatedVisibility( - visible = state.appBarState is KeyMapAppBarState.Selecting, - enter = slideInVertically { it }, - exit = slideOutVertically { it }, - ) { - val selectionState = (state.appBarState as? KeyMapAppBarState.Selecting) - ?: KeyMapAppBarState.Selecting( - selectionCount = 0, - selectedKeyMapsEnabled = SelectedKeyMapsEnabled.NONE, - isAllSelected = false, - groups = emptyList(), - breadcrumbs = emptyList(), - showThisGroup = false, + IntroShowcase( + showIntroShowCase = true, + onShowCaseCompleted = {}, + dismissOnClickOutside = true, + ) { + HomeKeyMapListScreen( + modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + snackbarState = snackbarState, + floatingActionButton = { + AnimatedVisibility( + state.appBarState !is KeyMapAppBarState.Selecting, + enter = fadeIn() + slideInHorizontally(initialOffsetX = { it }), + exit = fadeOut() + slideOutHorizontally(targetOffsetX = { it }), + ) { + CollapsableFloatingActionButton( + modifier = Modifier + .padding(bottom = fabBottomPadding) + .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.End)) + .introShowCaseTarget( + index = 0, + style = ShowcaseStyle( + backgroundColor = MaterialTheme.colorScheme.primary, + backgroundAlpha = 0.99f, + ), + ) { + Column { + Text( + text = "Create your first key map!", + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.titleLarge, + ) + + Spacer(Modifier.height(16.dp)) + Text( + text = "A key map is a rule to tell your device what to do when a button is pressed.", + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.bodyMedium, + ) + Spacer(Modifier.height(16.dp)) + + OutlinedButton( + onClick = {}, + // Set border color to white + border = BorderStroke( + width = 1.dp, + color = MaterialTheme.colorScheme.onPrimary, + ), + ) { + Text( + text = "Skip tutorial", + color = MaterialTheme.colorScheme.onPrimary, + ) + } + } + }, + onClick = viewModel::onNewKeyMapClick, + showText = viewModel.showFabText, + text = stringResource(R.string.home_fab_new_key_map), ) - - SelectionBottomSheet( - modifier = Modifier.onSizeChanged { size -> - keyMapListBottomPadding = - ((size.height.dp / 2) - 100.dp).coerceAtLeast(0.dp) + } + }, + listContent = { + KeyMapList( + modifier = Modifier.animateContentSize(), + lazyListState = rememberLazyListState(), + listItems = state.listItems, + footerText = stringResource(R.string.home_key_map_list_footer_text), + isSelectable = state.appBarState is KeyMapAppBarState.Selecting, + onClickKeyMap = viewModel::onKeyMapCardClick, + onLongClickKeyMap = viewModel::onKeyMapCardLongClick, + onSelectedChange = viewModel::onKeyMapSelectedChanged, + onFixClick = viewModel::onFixClick, + onTriggerErrorClick = viewModel::onFixTriggerError, + bottomListPadding = keyMapListBottomPadding, + ) + }, + appBarContent = { + KeyMapListAppBar( + state = state.appBarState, + scrollBehavior = scrollBehavior, + onSettingsClick = onSettingsClick, + onAboutClick = onAboutClick, + onSortClick = { viewModel.showSortBottomSheet = true }, + onHelpClick = { uriHandler.openUriSafe(ctx, helpUrl) }, + onExportClick = viewModel::onExportClick, + onImportClick = { importFileLauncher.launch(FileUtils.MIME_TYPE_ALL) }, + onInputMethodPickerClick = viewModel::showInputMethodPicker, + onTogglePausedClick = viewModel::onTogglePausedClick, + onFixWarningClick = viewModel::onFixWarningClick, + onBackClick = { + if (!viewModel.onBackClick()) { + finishActivity() + } }, - enabled = selectionState.selectionCount > 0, - groups = selectionState.groups, - breadcrumbs = selectionState.breadcrumbs, - selectedKeyMapsEnabled = selectionState.selectedKeyMapsEnabled, - onEnabledKeyMapsChange = viewModel::onEnabledKeyMapsChange, - onDuplicateClick = viewModel::onDuplicateSelectedKeyMapsClick, - onExportClick = viewModel::onExportSelectedKeyMaps, - onDeleteClick = { showDeleteDialog = true }, - onGroupClick = viewModel::onSelectionGroupClick, + onSelectAllClick = viewModel::onSelectAllClick, onNewGroupClick = viewModel::onNewGroupClick, - showThisGroup = selectionState.showThisGroup, - onThisGroupClick = viewModel::onMoveToThisGroupClick, + onRenameGroupClick = viewModel::onRenameGroupClick, + onEditGroupNameClick = viewModel::onEditGroupNameClick, + onGroupClick = viewModel::onGroupClick, + onDeleteGroupClick = viewModel::onDeleteGroupClick, + onNewConstraintClick = viewModel::onNewGroupConstraintClick, + onRemoveConstraintClick = viewModel::onRemoveGroupConstraintClick, + onConstraintModeChanged = viewModel::onGroupConstraintModeChanged, + onFixConstraintClick = viewModel::onFixClick, ) - } - }, - ) + }, + selectionBottomSheet = { + AnimatedVisibility( + visible = state.appBarState is KeyMapAppBarState.Selecting, + enter = slideInVertically { it }, + exit = slideOutVertically { it }, + ) { + val selectionState = (state.appBarState as? KeyMapAppBarState.Selecting) + ?: KeyMapAppBarState.Selecting( + selectionCount = 0, + selectedKeyMapsEnabled = SelectedKeyMapsEnabled.NONE, + isAllSelected = false, + groups = emptyList(), + breadcrumbs = emptyList(), + showThisGroup = false, + ) + + SelectionBottomSheet( + modifier = Modifier.onSizeChanged { size -> + keyMapListBottomPadding = + ((size.height.dp / 2) - 100.dp).coerceAtLeast(0.dp) + }, + enabled = selectionState.selectionCount > 0, + groups = selectionState.groups, + breadcrumbs = selectionState.breadcrumbs, + selectedKeyMapsEnabled = selectionState.selectedKeyMapsEnabled, + onEnabledKeyMapsChange = viewModel::onEnabledKeyMapsChange, + onDuplicateClick = viewModel::onDuplicateSelectedKeyMapsClick, + onExportClick = viewModel::onExportSelectedKeyMaps, + onDeleteClick = { showDeleteDialog = true }, + onGroupClick = viewModel::onSelectionGroupClick, + onNewGroupClick = viewModel::onNewGroupClick, + showThisGroup = selectionState.showThisGroup, + onThisGroupClick = viewModel::onMoveToThisGroupClick, + ) + } + }, + ) + } } @Composable From 119dd8b56b97e2d1dc6ec79e5b11a3644cb03519 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 16:28:08 +0200 Subject: [PATCH 74/95] refactor: move files to better package names --- .../keymaps/detection/KeyMapController.kt | 20 +++---- .../AccessibilityServiceController.kt | 6 +-- .../trigger/AdvancedTriggersBottomSheet.kt | 2 +- .../AssistantTriggerSetupBottomSheet.kt | 2 +- .../trigger/ConfigTriggerViewModel.kt | 10 ++-- app/src/main/AndroidManifest.xml | 2 +- .../sds100/keymapper/BaseMainActivity.kt | 2 +- .../github/sds100/keymapper/KeyMapperApp.kt | 2 +- .../github/sds100/keymapper/ServiceLocator.kt | 2 +- .../io/github/sds100/keymapper/UseCases.kt | 14 ++--- .../github/sds100/keymapper/actions/Action.kt | 2 +- .../keymapper/actions/ActionUiHelper.kt | 2 +- .../sds100/keymapper/actions/ActionsScreen.kt | 4 +- .../actions/ConfigActionsViewModel.kt | 6 +-- .../sds100/keymapper/backup/BackupManager.kt | 2 +- .../constraints/ConfigConstraintsViewModel.kt | 4 +- .../constraints/ConstraintsScreen.kt | 4 +- .../data/repositories/RoomKeyMapRepository.kt | 2 +- .../keymapper/home/HomeKeyMapListScreen.kt | 12 ++--- .../sds100/keymapper/home/HomeViewModel.kt | 8 +-- .../sds100/keymapper/home/KeyMapListAppBar.kt | 2 +- .../home/ShowHomeScreenAlertsUseCase.kt | 2 +- .../{mappings => keymaps}/ClickType.kt | 2 +- .../keymaps/ConfigKeyMapFragment.kt | 2 +- .../keymaps/ConfigKeyMapOptionsViewModel.kt | 2 +- .../keymaps/ConfigKeyMapScreen.kt | 4 +- .../keymaps/ConfigKeyMapUseCase.kt | 40 +++++++------- .../keymaps/ConfigKeyMapViewModel.kt | 9 ++-- .../keymaps/CreateKeyMapShortcutActivity.kt | 2 +- .../keymaps/CreateKeyMapShortcutScreen.kt | 6 +-- .../keymaps/CreateKeyMapShortcutUseCase.kt | 2 +- .../keymaps/CreateKeyMapShortcutViewModel.kt | 6 +-- .../keymaps/DisplayKeyMapUseCase.kt | 6 +-- .../FingerprintGestureType.kt | 2 +- .../FingerprintGesturesSupportedUseCase.kt | 2 +- .../GetDefaultKeyMapOptionsUseCase.kt | 2 +- .../{mappings => }/keymaps/KeyMap.kt | 17 +++--- .../keymaps/KeyMapAppBarState.kt | 2 +- .../{mappings => }/keymaps/KeyMapGroup.kt | 2 +- .../keymaps/KeyMapListItemCreator.kt | 32 ++++++------ .../keymaps/KeyMapListScreen.kt | 6 +-- .../{mappings => }/keymaps/KeyMapListState.kt | 4 +- .../keymaps/KeyMapListViewModel.kt | 13 +++-- .../keymaps/KeyMapOptionsScreen.kt | 2 +- .../keymaps/KeyMapRepository.kt | 2 +- .../keymaps/ListKeyMapsUseCase.kt | 2 +- .../PauseKeyMapsUseCase.kt | 2 +- .../{mappings => }/keymaps/ShortcutModel.kt | 2 +- .../{mappings => }/keymaps/ShortcutRow.kt | 4 +- .../SimpleMappingController.kt | 5 +- .../keymaps/detection/DetectKeyMapModel.kt | 4 +- .../keymaps/detection/DetectKeyMapsUseCase.kt | 10 ++-- .../DetectScreenOffKeyEventsController.kt | 2 +- .../detection/DpadMotionEventTracker.kt | 2 +- .../ParallelTriggerActionPerformer.kt | 2 +- .../SequenceTriggerActionPerformer.kt | 2 +- .../TriggerKeyMapFromOtherAppsController.kt | 6 +-- .../keymapper/sorting/SortKeyMapsUseCase.kt | 5 +- .../comparators/KeyMapActionsComparator.kt | 2 +- .../KeyMapConstraintsComparator.kt | 2 +- .../comparators/KeyMapOptionsComparator.kt | 2 +- .../comparators/KeyMapTriggerComparator.kt | 2 +- .../BaseAccessibilityServiceController.kt | 18 +++---- .../accessibility/MyAccessibilityService.kt | 4 +- .../inputmethod/AutoSwitchImeController.kt | 2 +- .../notifications/NotificationController.kt | 2 +- .../trigger/AssistantTriggerKey.kt | 4 +- .../trigger/AssistantTriggerType.kt | 2 +- .../trigger/BaseConfigTriggerViewModel.kt | 20 +++---- .../trigger/ChooseTriggerKeyDeviceModel.kt | 2 +- .../trigger/FingerprintTriggerKey.kt | 6 +-- .../keymaps => }/trigger/FloatingButtonKey.kt | 4 +- .../keymaps => }/trigger/KeyCodeTriggerKey.kt | 9 ++-- .../trigger/KeyEventDetectionSource.kt | 2 +- .../trigger/KeyMapListItemModel.kt | 2 +- .../trigger/RecordTriggerButtonRow.kt | 2 +- .../trigger/RecordTriggerState.kt | 2 +- .../trigger/RecordTriggerUseCase.kt | 4 +- .../keymaps => }/trigger/RecordedKey.kt | 2 +- .../trigger/SetupGuiKeyboardBottomSheet.kt | 2 +- .../trigger/SetupGuiKeyboardState.kt | 2 +- .../trigger/SetupGuiKeyboardUseCase.kt | 2 +- .../{mappings/keymaps => }/trigger/Trigger.kt | 17 ++++-- .../keymaps => }/trigger/TriggerError.kt | 2 +- .../trigger/TriggerErrorSnapshot.kt | 15 ++++-- .../keymaps => }/trigger/TriggerKey.kt | 4 +- .../keymaps => }/trigger/TriggerKeyDevice.kt | 2 +- .../trigger/TriggerKeyListItem.kt | 6 +-- .../trigger/TriggerKeyOptionsBottomSheet.kt | 6 +-- .../trigger/TriggerKeyShortcut.kt | 2 +- .../keymaps => }/trigger/TriggerMode.kt | 6 +-- .../keymaps => }/trigger/TriggerScreen.kt | 8 +-- .../io/github/sds100/keymapper/util/Inject.kt | 10 ++-- .../sds100/keymapper/util/ServiceEvent.kt | 2 +- .../keymapper/util/ui/compose/CompactChip.kt | 2 +- .../main/res/navigation/nav_config_keymap.xml | 2 +- .../sds100/keymapper/BackupManagerTest.kt | 2 +- .../keymapper/ConfigKeyMapUseCaseTest.kt | 31 ++++++----- .../keymaps/DpadMotionEventTrackerTest.kt | 4 +- .../keymaps/KeyMapControllerTest.kt | 52 +++++++++---------- .../ProcessKeyMapGroupsForDetectionTest.kt | 7 +-- ...riggerKeyMapFromOtherAppsControllerTest.kt | 9 ++-- .../sds100/keymapper/util/KeyMapUtils.kt | 15 +++--- 103 files changed, 322 insertions(+), 310 deletions(-) rename app/src/free/java/io/github/sds100/keymapper/{mappings => }/keymaps/detection/KeyMapController.kt (98%) rename app/src/free/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/AdvancedTriggersBottomSheet.kt (98%) rename app/src/free/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/AssistantTriggerSetupBottomSheet.kt (70%) rename app/src/free/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/ConfigTriggerViewModel.kt (74%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => keymaps}/ClickType.kt (73%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/ConfigKeyMapFragment.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/ConfigKeyMapOptionsViewModel.kt (99%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/ConfigKeyMapScreen.kt (99%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/ConfigKeyMapUseCase.kt (96%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/ConfigKeyMapViewModel.kt (92%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/CreateKeyMapShortcutActivity.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/CreateKeyMapShortcutScreen.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/CreateKeyMapShortcutUseCase.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/CreateKeyMapShortcutViewModel.kt (97%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/DisplayKeyMapUseCase.kt (97%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => keymaps}/FingerprintGestureType.kt (69%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => keymaps}/FingerprintGesturesSupportedUseCase.kt (95%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => keymaps}/GetDefaultKeyMapOptionsUseCase.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMap.kt (90%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMapAppBarState.kt (96%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMapGroup.kt (82%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMapListItemCreator.kt (91%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMapListScreen.kt (99%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMapListState.kt (56%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMapListViewModel.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMapOptionsScreen.kt (99%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/KeyMapRepository.kt (93%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/ListKeyMapsUseCase.kt (99%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => keymaps}/PauseKeyMapsUseCase.kt (96%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/ShortcutModel.kt (76%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/ShortcutRow.kt (97%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => keymaps}/SimpleMappingController.kt (97%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/detection/DetectKeyMapModel.kt (61%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/detection/DetectKeyMapsUseCase.kt (96%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/detection/DetectScreenOffKeyEventsController.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/detection/DpadMotionEventTracker.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/detection/ParallelTriggerActionPerformer.kt (99%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/detection/SequenceTriggerActionPerformer.kt (95%) rename app/src/main/java/io/github/sds100/keymapper/{mappings => }/keymaps/detection/TriggerKeyMapFromOtherAppsController.kt (88%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/AssistantTriggerKey.kt (96%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/AssistantTriggerType.kt (90%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/BaseConfigTriggerViewModel.kt (97%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/ChooseTriggerKeyDeviceModel.kt (72%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/FingerprintTriggerKey.kt (94%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/FloatingButtonKey.kt (95%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/KeyCodeTriggerKey.kt (95%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/KeyEventDetectionSource.kt (58%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/KeyMapListItemModel.kt (92%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/RecordTriggerButtonRow.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/RecordTriggerState.kt (85%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/RecordTriggerUseCase.kt (96%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/RecordedKey.kt (74%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/SetupGuiKeyboardBottomSheet.kt (99%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/SetupGuiKeyboardState.kt (85%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/SetupGuiKeyboardUseCase.kt (97%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/Trigger.kt (95%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerError.kt (91%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerErrorSnapshot.kt (83%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerKey.kt (89%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerKeyDevice.kt (95%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerKeyListItem.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerKeyOptionsBottomSheet.kt (98%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerKeyShortcut.kt (61%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerMode.kt (82%) rename app/src/main/java/io/github/sds100/keymapper/{mappings/keymaps => }/trigger/TriggerScreen.kt (99%) rename app/src/test/java/io/github/sds100/keymapper/{mappings => keymaps}/keymaps/DpadMotionEventTrackerTest.kt (98%) rename app/src/test/java/io/github/sds100/keymapper/{mappings => keymaps}/keymaps/KeyMapControllerTest.kt (98%) rename app/src/test/java/io/github/sds100/keymapper/{mappings => keymaps}/keymaps/ProcessKeyMapGroupsForDetectionTest.kt (97%) rename app/src/test/java/io/github/sds100/keymapper/{mappings => keymaps}/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt (92%) diff --git a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/detection/KeyMapController.kt b/app/src/free/java/io/github/sds100/keymapper/keymaps/detection/KeyMapController.kt similarity index 98% rename from app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/detection/KeyMapController.kt rename to app/src/free/java/io/github/sds100/keymapper/keymaps/detection/KeyMapController.kt index 1b0703f604..5857156523 100644 --- a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/detection/KeyMapController.kt +++ b/app/src/free/java/io/github/sds100/keymapper/keymaps/detection/KeyMapController.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.detection +package io.github.sds100.keymapper.keymaps.detection import android.view.KeyEvent import androidx.collection.SparseArrayCompat @@ -13,18 +13,18 @@ import io.github.sds100.keymapper.constraints.DetectConstraintsUseCase import io.github.sds100.keymapper.constraints.isSatisfied import io.github.sds100.keymapper.data.PreferenceDefaults import io.github.sds100.keymapper.data.entities.ActionEntity -import io.github.sds100.keymapper.mappings.ClickType -import io.github.sds100.keymapper.mappings.FingerprintGestureType -import io.github.sds100.keymapper.mappings.keymaps.trigger.FingerprintTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyCodeTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyEventDetectionSource -import io.github.sds100.keymapper.mappings.keymaps.trigger.Trigger -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKeyDevice -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerMode +import io.github.sds100.keymapper.keymaps.ClickType +import io.github.sds100.keymapper.keymaps.FingerprintGestureType import io.github.sds100.keymapper.system.inputevents.InputEventUtils import io.github.sds100.keymapper.system.inputevents.MyKeyEvent import io.github.sds100.keymapper.system.inputevents.MyMotionEvent +import io.github.sds100.keymapper.trigger.FingerprintTriggerKey +import io.github.sds100.keymapper.trigger.KeyCodeTriggerKey +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource +import io.github.sds100.keymapper.trigger.Trigger +import io.github.sds100.keymapper.trigger.TriggerKey +import io.github.sds100.keymapper.trigger.TriggerKeyDevice +import io.github.sds100.keymapper.trigger.TriggerMode import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.InputEventType import io.github.sds100.keymapper.util.Result diff --git a/app/src/free/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt b/app/src/free/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt index 8e0fb9dda5..44c0fcc963 100644 --- a/app/src/free/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt +++ b/app/src/free/java/io/github/sds100/keymapper/system/accessibility/AccessibilityServiceController.kt @@ -4,9 +4,9 @@ import io.github.sds100.keymapper.actions.PerformActionsUseCase import io.github.sds100.keymapper.constraints.DetectConstraintsUseCase import io.github.sds100.keymapper.data.repositories.AccessibilityNodeRepository import io.github.sds100.keymapper.data.repositories.PreferenceRepository -import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCase -import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase -import io.github.sds100.keymapper.mappings.keymaps.detection.DetectKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCase +import io.github.sds100.keymapper.keymaps.PauseKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapsUseCase import io.github.sds100.keymapper.reroutekeyevents.RerouteKeyEventsUseCase import io.github.sds100.keymapper.system.devices.DevicesAdapter import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter diff --git a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AdvancedTriggersBottomSheet.kt b/app/src/free/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt similarity index 98% rename from app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AdvancedTriggersBottomSheet.kt rename to app/src/free/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt index 3d6731ccdf..50319d11b2 100644 --- a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AdvancedTriggersBottomSheet.kt +++ b/app/src/free/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row diff --git a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerSetupBottomSheet.kt b/app/src/free/java/io/github/sds100/keymapper/trigger/AssistantTriggerSetupBottomSheet.kt similarity index 70% rename from app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerSetupBottomSheet.kt rename to app/src/free/java/io/github/sds100/keymapper/trigger/AssistantTriggerSetupBottomSheet.kt index 3730a02096..8c3c7cbd59 100644 --- a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerSetupBottomSheet.kt +++ b/app/src/free/java/io/github/sds100/keymapper/trigger/AssistantTriggerSetupBottomSheet.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import androidx.compose.runtime.Composable diff --git a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ConfigTriggerViewModel.kt b/app/src/free/java/io/github/sds100/keymapper/trigger/ConfigTriggerViewModel.kt similarity index 74% rename from app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ConfigTriggerViewModel.kt rename to app/src/free/java/io/github/sds100/keymapper/trigger/ConfigTriggerViewModel.kt index cb30491821..89a8da1b07 100644 --- a/app/src/free/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ConfigTriggerViewModel.kt +++ b/app/src/free/java/io/github/sds100/keymapper/trigger/ConfigTriggerViewModel.kt @@ -1,9 +1,9 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger -import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCase -import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCase -import io.github.sds100.keymapper.mappings.keymaps.CreateKeyMapShortcutUseCase -import io.github.sds100.keymapper.mappings.keymaps.DisplayKeyMapUseCase +import io.github.sds100.keymapper.keymaps.ConfigKeyMapUseCase +import io.github.sds100.keymapper.keymaps.CreateKeyMapShortcutUseCase +import io.github.sds100.keymapper.keymaps.DisplayKeyMapUseCase +import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCase import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.purchasing.PurchasingManager import io.github.sds100.keymapper.util.ui.ResourceProvider diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c19777221d..1f8d932dfb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -126,7 +126,7 @@ diff --git a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt index 98cf6232c0..8aed150c4f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt @@ -29,12 +29,12 @@ import com.anggrayudi.storage.extension.toDocumentFile import io.github.sds100.keymapper.Constants.PACKAGE_NAME import io.github.sds100.keymapper.compose.ComposeColors import io.github.sds100.keymapper.databinding.ActivityMainBinding -import io.github.sds100.keymapper.mappings.keymaps.trigger.RecordTriggerController import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter import io.github.sds100.keymapper.system.files.FileUtils import io.github.sds100.keymapper.system.inputevents.MyMotionEvent import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter import io.github.sds100.keymapper.system.permissions.RequestPermissionDelegate +import io.github.sds100.keymapper.trigger.RecordTriggerController import io.github.sds100.keymapper.util.launchRepeatOnLifecycle import io.github.sds100.keymapper.util.ui.showPopups import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt index 4019a372cc..5519d434ce 100644 --- a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt +++ b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt @@ -16,7 +16,6 @@ import io.github.sds100.keymapper.actions.uielement.InteractUiElementController import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.entities.LogEntryEntity import io.github.sds100.keymapper.logging.KeyMapperLoggingTree -import io.github.sds100.keymapper.mappings.keymaps.trigger.RecordTriggerController import io.github.sds100.keymapper.purchasing.PurchasingManagerImpl import io.github.sds100.keymapper.settings.ThemeUtils import io.github.sds100.keymapper.shizuku.ShizukuAdapterImpl @@ -55,6 +54,7 @@ import io.github.sds100.keymapper.system.root.SuAdapterImpl import io.github.sds100.keymapper.system.url.AndroidOpenUrlAdapter import io.github.sds100.keymapper.system.vibrator.AndroidVibratorAdapter import io.github.sds100.keymapper.system.volume.AndroidVolumeAdapter +import io.github.sds100.keymapper.trigger.RecordTriggerController import io.github.sds100.keymapper.util.ui.ResourceProviderImpl import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.collectLatest diff --git a/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt b/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt index 4151e715e2..030d056d9a 100755 --- a/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt @@ -20,8 +20,8 @@ import io.github.sds100.keymapper.data.repositories.RoomGroupRepository import io.github.sds100.keymapper.data.repositories.RoomKeyMapRepository import io.github.sds100.keymapper.data.repositories.RoomLogRepository import io.github.sds100.keymapper.data.repositories.SettingsPreferenceRepository +import io.github.sds100.keymapper.keymaps.ConfigKeyMapUseCaseController import io.github.sds100.keymapper.logging.LogRepository -import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCaseController import io.github.sds100.keymapper.purchasing.PurchasingManagerImpl import io.github.sds100.keymapper.shizuku.ShizukuAdapter import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter diff --git a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt index 3950710c45..dcc8d93507 100644 --- a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt +++ b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt @@ -9,13 +9,13 @@ import io.github.sds100.keymapper.constraints.DetectConstraintsUseCaseImpl import io.github.sds100.keymapper.constraints.GetConstraintErrorUseCaseImpl import io.github.sds100.keymapper.floating.ListFloatingLayoutsUseCase import io.github.sds100.keymapper.floating.ListFloatingLayoutsUseCaseImpl -import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCaseImpl -import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCaseImpl -import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCase -import io.github.sds100.keymapper.mappings.keymaps.CreateKeyMapShortcutUseCaseImpl -import io.github.sds100.keymapper.mappings.keymaps.DisplayKeyMapUseCase -import io.github.sds100.keymapper.mappings.keymaps.DisplayKeyMapUseCaseImpl -import io.github.sds100.keymapper.mappings.keymaps.detection.DetectKeyMapsUseCaseImpl +import io.github.sds100.keymapper.keymaps.ConfigKeyMapUseCase +import io.github.sds100.keymapper.keymaps.CreateKeyMapShortcutUseCaseImpl +import io.github.sds100.keymapper.keymaps.DisplayKeyMapUseCase +import io.github.sds100.keymapper.keymaps.DisplayKeyMapUseCaseImpl +import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCaseImpl +import io.github.sds100.keymapper.keymaps.PauseKeyMapsUseCaseImpl +import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapsUseCaseImpl import io.github.sds100.keymapper.onboarding.OnboardingUseCaseImpl import io.github.sds100.keymapper.reroutekeyevents.RerouteKeyEventsUseCaseImpl import io.github.sds100.keymapper.shizuku.ShizukuInputEventInjector diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt b/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt index b5a2a5db4e..e8e1050ea7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/Action.kt @@ -3,7 +3,7 @@ package io.github.sds100.keymapper.actions import io.github.sds100.keymapper.data.entities.ActionEntity import io.github.sds100.keymapper.data.entities.EntityExtra import io.github.sds100.keymapper.data.entities.getData -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.util.success import io.github.sds100.keymapper.util.then import io.github.sds100.keymapper.util.valueOrNull diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt index 77515ce344..723084996b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUiHelper.kt @@ -6,7 +6,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Android import io.github.sds100.keymapper.R import io.github.sds100.keymapper.actions.pinchscreen.PinchScreenType -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.system.camera.CameraLens import io.github.sds100.keymapper.system.devices.InputDeviceUtils import io.github.sds100.keymapper.system.display.OrientationUtils diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt index 6e4126b876..e53596658e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionsScreen.kt @@ -39,8 +39,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel -import io.github.sds100.keymapper.mappings.keymaps.ShortcutRow +import io.github.sds100.keymapper.keymaps.ShortcutModel +import io.github.sds100.keymapper.keymaps.ShortcutRow import io.github.sds100.keymapper.system.camera.CameraLens import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.ui.LinkType diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt index f501900c70..dfe362ff0b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ConfigActionsViewModel.kt @@ -1,9 +1,9 @@ package io.github.sds100.keymapper.actions import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCase -import io.github.sds100.keymapper.mappings.keymaps.KeyMap -import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel +import io.github.sds100.keymapper.keymaps.ConfigKeyMapUseCase +import io.github.sds100.keymapper.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.ShortcutModel import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.util.Error diff --git a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt index d56aaf4d19..bb82002476 100644 --- a/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt +++ b/app/src/main/java/io/github/sds100/keymapper/backup/BackupManager.kt @@ -44,7 +44,7 @@ import io.github.sds100.keymapper.data.repositories.FloatingLayoutRepository import io.github.sds100.keymapper.data.repositories.GroupRepository import io.github.sds100.keymapper.data.repositories.PreferenceRepository import io.github.sds100.keymapper.data.repositories.RepositoryUtils -import io.github.sds100.keymapper.mappings.keymaps.KeyMapRepository +import io.github.sds100.keymapper.keymaps.KeyMapRepository import io.github.sds100.keymapper.system.files.FileAdapter import io.github.sds100.keymapper.system.files.IFile import io.github.sds100.keymapper.util.DefaultDispatcherProvider diff --git a/app/src/main/java/io/github/sds100/keymapper/constraints/ConfigConstraintsViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/constraints/ConfigConstraintsViewModel.kt index 534a4c4978..f4512555ab 100644 --- a/app/src/main/java/io/github/sds100/keymapper/constraints/ConfigConstraintsViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/constraints/ConfigConstraintsViewModel.kt @@ -3,8 +3,8 @@ package io.github.sds100.keymapper.constraints import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCase -import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel +import io.github.sds100.keymapper.keymaps.ConfigKeyMapUseCase +import io.github.sds100.keymapper.keymaps.ShortcutModel import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.State diff --git a/app/src/main/java/io/github/sds100/keymapper/constraints/ConstraintsScreen.kt b/app/src/main/java/io/github/sds100/keymapper/constraints/ConstraintsScreen.kt index bb2b78a354..8c5d50b219 100644 --- a/app/src/main/java/io/github/sds100/keymapper/constraints/ConstraintsScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/constraints/ConstraintsScreen.kt @@ -39,8 +39,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel -import io.github.sds100.keymapper.mappings.keymaps.ShortcutRow +import io.github.sds100.keymapper.keymaps.ShortcutModel +import io.github.sds100.keymapper.keymaps.ShortcutRow import io.github.sds100.keymapper.system.camera.CameraLens import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.drawable diff --git a/app/src/main/java/io/github/sds100/keymapper/data/repositories/RoomKeyMapRepository.kt b/app/src/main/java/io/github/sds100/keymapper/data/repositories/RoomKeyMapRepository.kt index 2a0f1737a2..fb2ff2759d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/repositories/RoomKeyMapRepository.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/repositories/RoomKeyMapRepository.kt @@ -5,7 +5,7 @@ import io.github.sds100.keymapper.data.db.dao.KeyMapDao import io.github.sds100.keymapper.data.entities.FingerprintMapEntity import io.github.sds100.keymapper.data.entities.KeyMapEntity import io.github.sds100.keymapper.data.migration.fingerprintmaps.FingerprintToKeyMapMigration -import io.github.sds100.keymapper.mappings.keymaps.KeyMapRepository +import io.github.sds100.keymapper.keymaps.KeyMapRepository import io.github.sds100.keymapper.util.DefaultDispatcherProvider import io.github.sds100.keymapper.util.DispatcherProvider import io.github.sds100.keymapper.util.State diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt index dc46702f69..d64c2753cb 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt @@ -58,14 +58,14 @@ import io.github.sds100.keymapper.backup.RestoreType import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.constraints.ConstraintMode import io.github.sds100.keymapper.groups.GroupListItemModel -import io.github.sds100.keymapper.mappings.keymaps.KeyMapAppBarState -import io.github.sds100.keymapper.mappings.keymaps.KeyMapList -import io.github.sds100.keymapper.mappings.keymaps.KeyMapListViewModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.DpadTriggerSetupBottomSheet -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerError +import io.github.sds100.keymapper.keymaps.KeyMapAppBarState +import io.github.sds100.keymapper.keymaps.KeyMapList +import io.github.sds100.keymapper.keymaps.KeyMapListViewModel import io.github.sds100.keymapper.sorting.SortBottomSheet import io.github.sds100.keymapper.system.files.FileUtils +import io.github.sds100.keymapper.trigger.DpadTriggerSetupBottomSheet +import io.github.sds100.keymapper.trigger.KeyMapListItemModel +import io.github.sds100.keymapper.trigger.TriggerError import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.ShareUtils import io.github.sds100.keymapper.util.State diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt index 636a9dea5a..873d9e2ec3 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt @@ -13,13 +13,13 @@ import io.github.sds100.keymapper.R import io.github.sds100.keymapper.backup.BackupRestoreMappingsUseCase import io.github.sds100.keymapper.floating.ListFloatingLayoutsUseCase import io.github.sds100.keymapper.floating.ListFloatingLayoutsViewModel -import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase -import io.github.sds100.keymapper.mappings.keymaps.KeyMapListViewModel -import io.github.sds100.keymapper.mappings.keymaps.ListKeyMapsUseCase -import io.github.sds100.keymapper.mappings.keymaps.trigger.SetupGuiKeyboardUseCase +import io.github.sds100.keymapper.keymaps.KeyMapListViewModel +import io.github.sds100.keymapper.keymaps.ListKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.PauseKeyMapsUseCase import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.sorting.SortKeyMapsUseCase import io.github.sds100.keymapper.system.inputmethod.ShowInputMethodPickerUseCase +import io.github.sds100.keymapper.trigger.SetupGuiKeyboardUseCase import io.github.sds100.keymapper.util.ui.DialogResponse import io.github.sds100.keymapper.util.ui.NavigationViewModel import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl diff --git a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt index 01db036dd9..ea4f429031 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/KeyMapListAppBar.kt @@ -100,7 +100,7 @@ import io.github.sds100.keymapper.groups.GroupBreadcrumbRow import io.github.sds100.keymapper.groups.GroupConstraintRow import io.github.sds100.keymapper.groups.GroupListItemModel import io.github.sds100.keymapper.groups.GroupRow -import io.github.sds100.keymapper.mappings.keymaps.KeyMapAppBarState +import io.github.sds100.keymapper.keymaps.KeyMapAppBarState import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.drawable import io.github.sds100.keymapper.util.ui.compose.ComposeChipModel diff --git a/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt index 044420acb1..1f21e50589 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/ShowHomeScreenAlertsUseCase.kt @@ -2,7 +2,7 @@ package io.github.sds100.keymapper.home import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.repositories.PreferenceRepository -import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.PauseKeyMapsUseCase import io.github.sds100.keymapper.system.accessibility.ServiceAdapter import io.github.sds100.keymapper.system.accessibility.ServiceState import io.github.sds100.keymapper.system.permissions.Permission diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/ClickType.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ClickType.kt similarity index 73% rename from app/src/main/java/io/github/sds100/keymapper/mappings/ClickType.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ClickType.kt index 4b0a201ca6..3a259bd68e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/ClickType.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ClickType.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings +package io.github.sds100.keymapper.keymaps /** * Created by sds100 on 21/02/2021. diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapFragment.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapFragment.kt index 942c175127..20e9fc683e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapFragment.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapOptionsViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapOptionsViewModel.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapOptionsViewModel.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapOptionsViewModel.kt index 2e37ea9002..0a9daa209b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapOptionsViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapOptionsViewModel.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.graphics.Color import android.graphics.drawable.Drawable diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt index 3814f879f5..c7d7ace279 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement @@ -58,7 +58,7 @@ import io.github.sds100.keymapper.R import io.github.sds100.keymapper.actions.ActionsScreen import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.constraints.ConstraintsScreen -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerScreen +import io.github.sds100.keymapper.trigger.TriggerScreen import io.github.sds100.keymapper.util.ui.compose.openUriSafe import kotlinx.coroutines.launch diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapUseCase.kt similarity index 96% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapUseCase.kt index 842264bfea..a53db4df8d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.database.sqlite.SQLiteConstraintException import io.github.sds100.keymapper.actions.Action @@ -13,24 +13,19 @@ import io.github.sds100.keymapper.data.repositories.FloatingButtonRepository import io.github.sds100.keymapper.data.repositories.FloatingLayoutRepository import io.github.sds100.keymapper.data.repositories.PreferenceRepository import io.github.sds100.keymapper.floating.FloatingButtonEntityMapper -import io.github.sds100.keymapper.mappings.ClickType -import io.github.sds100.keymapper.mappings.FingerprintGestureType -import io.github.sds100.keymapper.mappings.GetDefaultKeyMapOptionsUseCase -import io.github.sds100.keymapper.mappings.GetDefaultKeyMapOptionsUseCaseImpl -import io.github.sds100.keymapper.mappings.keymaps.trigger.AssistantTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.AssistantTriggerType -import io.github.sds100.keymapper.mappings.keymaps.trigger.FingerprintTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.FloatingButtonKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyCodeTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyEventDetectionSource -import io.github.sds100.keymapper.mappings.keymaps.trigger.Trigger -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKeyDevice -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerMode import io.github.sds100.keymapper.system.accessibility.ServiceAdapter import io.github.sds100.keymapper.system.devices.DevicesAdapter import io.github.sds100.keymapper.system.devices.InputDeviceUtils import io.github.sds100.keymapper.system.inputevents.InputEventUtils +import io.github.sds100.keymapper.trigger.AssistantTriggerKey +import io.github.sds100.keymapper.trigger.AssistantTriggerType +import io.github.sds100.keymapper.trigger.FingerprintTriggerKey +import io.github.sds100.keymapper.trigger.FloatingButtonKey +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource +import io.github.sds100.keymapper.trigger.Trigger +import io.github.sds100.keymapper.trigger.TriggerKey +import io.github.sds100.keymapper.trigger.TriggerKeyDevice +import io.github.sds100.keymapper.trigger.TriggerMode import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.ServiceEvent import io.github.sds100.keymapper.util.State @@ -368,7 +363,7 @@ class ConfigKeyMapUseCaseController( // Check whether the trigger already contains the key because if so // then it must be converted to a sequence trigger. val containsKey = trigger.keys - .mapNotNull { it as? KeyCodeTriggerKey } + .mapNotNull { it as? io.github.sds100.keymapper.trigger.KeyCodeTriggerKey } .any { keyToCompare -> keyToCompare.keyCode == keyCode && keyToCompare.device.isSameDevice(device) } @@ -380,7 +375,7 @@ class ConfigKeyMapUseCaseController( consumeKeyEvent = false } - val triggerKey = KeyCodeTriggerKey( + val triggerKey = io.github.sds100.keymapper.trigger.KeyCodeTriggerKey( keyCode = keyCode, device = device, clickType = clickType, @@ -454,7 +449,10 @@ class ConfigKeyMapUseCaseController( when (key) { // You can't mix assistant trigger types in a parallel trigger because there is no notion of a "down" key event, which means they can't be pressed at the same time is AssistantTriggerKey, is FingerprintTriggerKey -> 0 - is KeyCodeTriggerKey -> Pair(key.keyCode, key.device) + is io.github.sds100.keymapper.trigger.KeyCodeTriggerKey -> Pair( + key.keyCode, + key.device, + ) is FloatingButtonKey -> key.buttonUid } } @@ -554,7 +552,7 @@ class ConfigKeyMapUseCaseController( override fun setTriggerKeyDevice(keyUid: String, device: TriggerKeyDevice) { editTriggerKey(keyUid) { key -> - if (key is KeyCodeTriggerKey) { + if (key is io.github.sds100.keymapper.trigger.KeyCodeTriggerKey) { key.copy(device = device) } else { key @@ -564,7 +562,7 @@ class ConfigKeyMapUseCaseController( override fun setTriggerKeyConsumeKeyEvent(keyUid: String, consumeKeyEvent: Boolean) { editTriggerKey(keyUid) { key -> - if (key is KeyCodeTriggerKey) { + if (key is io.github.sds100.keymapper.trigger.KeyCodeTriggerKey) { key.copy(consumeEvent = consumeKeyEvent) } else { key @@ -790,7 +788,7 @@ class ConfigKeyMapUseCaseController( false } else { trigger.keys - .mapNotNull { it as? KeyCodeTriggerKey } + .mapNotNull { it as? io.github.sds100.keymapper.trigger.KeyCodeTriggerKey } .any { InputEventUtils.isDpadKeyCode(it.keyCode) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt similarity index 92% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapViewModel.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt index 010ba017b0..2ecc1417df 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ConfigKeyMapViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.os.Bundle import androidx.lifecycle.ViewModel @@ -8,12 +8,11 @@ import io.github.sds100.keymapper.actions.ConfigActionsViewModel import io.github.sds100.keymapper.actions.CreateActionUseCase import io.github.sds100.keymapper.actions.TestActionUseCase import io.github.sds100.keymapper.constraints.ConfigConstraintsViewModel -import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCase -import io.github.sds100.keymapper.mappings.keymaps.trigger.ConfigTriggerViewModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.RecordTriggerUseCase -import io.github.sds100.keymapper.mappings.keymaps.trigger.SetupGuiKeyboardUseCase import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.purchasing.PurchasingManager +import io.github.sds100.keymapper.trigger.ConfigTriggerViewModel +import io.github.sds100.keymapper.trigger.RecordTriggerUseCase +import io.github.sds100.keymapper.trigger.SetupGuiKeyboardUseCase import io.github.sds100.keymapper.ui.utils.getJsonSerializable import io.github.sds100.keymapper.ui.utils.putJsonSerializable import io.github.sds100.keymapper.util.dataOrNull diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutActivity.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutActivity.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutActivity.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutActivity.kt index 14a3006dad..48d0e35783 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutActivity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutActivity.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.os.Bundle import androidx.activity.SystemBarStyle diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutScreen.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutScreen.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutScreen.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutScreen.kt index ae124afada..2857e9e4bb 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutScreen.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent @@ -44,8 +44,8 @@ import io.github.sds100.keymapper.constraints.ConstraintMode import io.github.sds100.keymapper.groups.GroupBreadcrumbRow import io.github.sds100.keymapper.groups.GroupListItemModel import io.github.sds100.keymapper.groups.GroupRow -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerError +import io.github.sds100.keymapper.trigger.KeyMapListItemModel +import io.github.sds100.keymapper.trigger.TriggerError import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.drawable diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutUseCase.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutUseCase.kt index e910d77733..6d6fb7e9ed 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.content.Intent import android.graphics.drawable.Drawable diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutViewModel.kt similarity index 97% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutViewModel.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutViewModel.kt index 50cde0b7a6..bba8984ef3 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/CreateKeyMapShortcutViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/CreateKeyMapShortcutViewModel.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.content.Intent import android.graphics.Color @@ -15,8 +15,8 @@ import io.github.sds100.keymapper.constraints.ConstraintErrorSnapshot import io.github.sds100.keymapper.constraints.ConstraintMode import io.github.sds100.keymapper.constraints.ConstraintUiHelper import io.github.sds100.keymapper.groups.GroupListItemModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerErrorSnapshot +import io.github.sds100.keymapper.trigger.KeyMapListItemModel +import io.github.sds100.keymapper.trigger.TriggerErrorSnapshot import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.mapData import io.github.sds100.keymapper.util.ui.ResourceProvider diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/DisplayKeyMapUseCase.kt similarity index 97% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/DisplayKeyMapUseCase.kt index 8eee6ce3ff..b26f655037 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/DisplayKeyMapUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/DisplayKeyMapUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.graphics.drawable.Drawable import io.github.sds100.keymapper.actions.DisplayActionUseCase @@ -7,8 +7,6 @@ import io.github.sds100.keymapper.constraints.DisplayConstraintUseCase import io.github.sds100.keymapper.constraints.GetConstraintErrorUseCase import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.repositories.PreferenceRepository -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerError -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerErrorSnapshot import io.github.sds100.keymapper.purchasing.ProductId import io.github.sds100.keymapper.purchasing.PurchasingManager import io.github.sds100.keymapper.shizuku.ShizukuUtils @@ -19,6 +17,8 @@ import io.github.sds100.keymapper.system.inputmethod.KeyMapperImeHelper import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter +import io.github.sds100.keymapper.trigger.TriggerError +import io.github.sds100.keymapper.trigger.TriggerErrorSnapshot import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.State diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/FingerprintGestureType.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/FingerprintGestureType.kt similarity index 69% rename from app/src/main/java/io/github/sds100/keymapper/mappings/FingerprintGestureType.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/FingerprintGestureType.kt index 82a1ef2a1b..b0d65cc8a5 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/FingerprintGestureType.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/FingerprintGestureType.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings +package io.github.sds100.keymapper.keymaps enum class FingerprintGestureType { SWIPE_DOWN, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/FingerprintGesturesSupportedUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/FingerprintGesturesSupportedUseCase.kt similarity index 95% rename from app/src/main/java/io/github/sds100/keymapper/mappings/FingerprintGesturesSupportedUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/FingerprintGesturesSupportedUseCase.kt index ccd9ad8ae1..3052d0afce 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/FingerprintGesturesSupportedUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/FingerprintGesturesSupportedUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings +package io.github.sds100.keymapper.keymaps import android.os.Build import io.github.sds100.keymapper.data.Keys diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/GetDefaultKeyMapOptionsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/GetDefaultKeyMapOptionsUseCase.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/GetDefaultKeyMapOptionsUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/GetDefaultKeyMapOptionsUseCase.kt index ee88466da6..db17acfe03 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/GetDefaultKeyMapOptionsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/GetDefaultKeyMapOptionsUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.PreferenceDefaults diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMap.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMap.kt similarity index 90% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMap.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMap.kt index a03608bc2b..0e33d254ee 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMap.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMap.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.view.KeyEvent import io.github.sds100.keymapper.actions.Action @@ -10,11 +10,10 @@ import io.github.sds100.keymapper.constraints.ConstraintModeEntityMapper import io.github.sds100.keymapper.constraints.ConstraintState import io.github.sds100.keymapper.data.entities.FloatingButtonEntityWithLayout import io.github.sds100.keymapper.data.entities.KeyMapEntity -import io.github.sds100.keymapper.mappings.keymaps.detection.KeyMapController -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyCodeTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.Trigger -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerEntityMapper -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKey +import io.github.sds100.keymapper.keymaps.detection.KeyMapController +import io.github.sds100.keymapper.trigger.Trigger +import io.github.sds100.keymapper.trigger.TriggerEntityMapper +import io.github.sds100.keymapper.trigger.TriggerKey import kotlinx.serialization.Serializable import java.util.UUID @@ -72,7 +71,7 @@ fun KeyMap.requiresImeKeyEventForwarding(): Boolean { actionList.any { it.data is ActionData.AnswerCall || it.data is ActionData.EndCall } val hasVolumeKeys = trigger.keys - .mapNotNull { it as? KeyCodeTriggerKey } + .mapNotNull { it as? io.github.sds100.keymapper.trigger.KeyCodeTriggerKey } .any { it.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || it.keyCode == KeyEvent.KEYCODE_VOLUME_UP @@ -88,7 +87,7 @@ fun KeyMap.requiresImeKeyEventForwarding(): Boolean { * is incoming. */ fun KeyMap.requiresImeKeyEventForwardingInPhoneCall(triggerKey: TriggerKey): Boolean { - if (triggerKey !is KeyCodeTriggerKey) { + if (triggerKey !is io.github.sds100.keymapper.trigger.KeyCodeTriggerKey) { return false } @@ -96,7 +95,7 @@ fun KeyMap.requiresImeKeyEventForwardingInPhoneCall(triggerKey: TriggerKey): Boo actionList.any { it.data is ActionData.AnswerCall || it.data is ActionData.EndCall } val hasVolumeKeys = trigger.keys - .mapNotNull { it as? KeyCodeTriggerKey } + .mapNotNull { it as? io.github.sds100.keymapper.trigger.KeyCodeTriggerKey } .any { it.keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || it.keyCode == KeyEvent.KEYCODE_VOLUME_UP diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapAppBarState.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapAppBarState.kt similarity index 96% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapAppBarState.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapAppBarState.kt index 12b3030e65..1a7dd98598 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapAppBarState.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapAppBarState.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.constraints.ConstraintMode import io.github.sds100.keymapper.groups.GroupListItemModel diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapGroup.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapGroup.kt similarity index 82% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapGroup.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapGroup.kt index de2910ec78..32a4a17028 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapGroup.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapGroup.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.groups.Group import io.github.sds100.keymapper.util.State diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListItemCreator.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListItemCreator.kt similarity index 91% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListItemCreator.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListItemCreator.kt index b8f4d46016..7e99b73df8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListItemCreator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListItemCreator.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowForward @@ -9,21 +9,18 @@ import io.github.sds100.keymapper.actions.ActionUiHelper import io.github.sds100.keymapper.constraints.ConstraintErrorSnapshot import io.github.sds100.keymapper.constraints.ConstraintState import io.github.sds100.keymapper.constraints.ConstraintUiHelper -import io.github.sds100.keymapper.mappings.ClickType -import io.github.sds100.keymapper.mappings.FingerprintGestureType -import io.github.sds100.keymapper.mappings.keymaps.trigger.AssistantTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.AssistantTriggerType -import io.github.sds100.keymapper.mappings.keymaps.trigger.FingerprintTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.FloatingButtonKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyCodeTriggerKey -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyEventDetectionSource -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.Trigger -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerErrorSnapshot -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKeyDevice -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerMode import io.github.sds100.keymapper.system.devices.InputDeviceUtils import io.github.sds100.keymapper.system.inputevents.InputEventUtils +import io.github.sds100.keymapper.trigger.AssistantTriggerKey +import io.github.sds100.keymapper.trigger.AssistantTriggerType +import io.github.sds100.keymapper.trigger.FingerprintTriggerKey +import io.github.sds100.keymapper.trigger.FloatingButtonKey +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource +import io.github.sds100.keymapper.trigger.KeyMapListItemModel +import io.github.sds100.keymapper.trigger.Trigger +import io.github.sds100.keymapper.trigger.TriggerErrorSnapshot +import io.github.sds100.keymapper.trigger.TriggerKeyDevice +import io.github.sds100.keymapper.trigger.TriggerMode import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.isFixable import io.github.sds100.keymapper.util.ui.ResourceProvider @@ -58,7 +55,10 @@ class KeyMapListItemCreator( val triggerKeys = keyMap.trigger.keys.map { key -> when (key) { is AssistantTriggerKey -> assistantTriggerKeyName(key) - is KeyCodeTriggerKey -> keyCodeTriggerKeyName(key, showDeviceDescriptors) + is io.github.sds100.keymapper.trigger.KeyCodeTriggerKey -> keyCodeTriggerKeyName( + key, + showDeviceDescriptors, + ) is FloatingButtonKey -> floatingButtonKeyName(key) is FingerprintTriggerKey -> fingerprintKeyName(key) } @@ -241,7 +241,7 @@ class KeyMapListItemCreator( } private fun keyCodeTriggerKeyName( - key: KeyCodeTriggerKey, + key: io.github.sds100.keymapper.trigger.KeyCodeTriggerKey, showDeviceDescriptors: Boolean, ): String = buildString { when (key.clickType) { diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListScreen.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListScreen.kt index 5cf30faa3c..b36da5e53c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListScreen.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement @@ -56,8 +56,8 @@ import com.google.accompanist.drawablepainter.rememberDrawablePainter import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.constraints.ConstraintMode -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerError +import io.github.sds100.keymapper.trigger.KeyMapListItemModel +import io.github.sds100.keymapper.trigger.TriggerError import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.drawable diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListState.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListState.kt similarity index 56% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListState.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListState.kt index 9f79721767..e47b9f684e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListState.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListState.kt @@ -1,6 +1,6 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel +import io.github.sds100.keymapper.trigger.KeyMapListItemModel import io.github.sds100.keymapper.util.State data class KeyMapListState( diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt index 040a10bcab..9e23b1b183 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapListViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -17,17 +17,16 @@ import io.github.sds100.keymapper.groups.GroupListItemModel import io.github.sds100.keymapper.home.HomeWarningListItem import io.github.sds100.keymapper.home.SelectedKeyMapsEnabled import io.github.sds100.keymapper.home.ShowHomeScreenAlertsUseCase -import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyMapListItemModel -import io.github.sds100.keymapper.mappings.keymaps.trigger.SetupGuiKeyboardState -import io.github.sds100.keymapper.mappings.keymaps.trigger.SetupGuiKeyboardUseCase -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerError -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerErrorSnapshot import io.github.sds100.keymapper.sorting.SortKeyMapsUseCase import io.github.sds100.keymapper.sorting.SortViewModel import io.github.sds100.keymapper.system.accessibility.ServiceState import io.github.sds100.keymapper.system.inputmethod.ShowInputMethodPickerUseCase import io.github.sds100.keymapper.system.permissions.Permission +import io.github.sds100.keymapper.trigger.KeyMapListItemModel +import io.github.sds100.keymapper.trigger.SetupGuiKeyboardState +import io.github.sds100.keymapper.trigger.SetupGuiKeyboardUseCase +import io.github.sds100.keymapper.trigger.TriggerError +import io.github.sds100.keymapper.trigger.TriggerErrorSnapshot import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.State diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapOptionsScreen.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapOptionsScreen.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapOptionsScreen.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapOptionsScreen.kt index 03216b2bf0..87c6a00c5c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapOptionsScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapOptionsScreen.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.content.ClipData import androidx.compose.animation.AnimatedVisibility diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapRepository.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapRepository.kt similarity index 93% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapRepository.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapRepository.kt index 0d42b9c77c..b79810dbd7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/KeyMapRepository.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapRepository.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.data.entities.KeyMapEntity import io.github.sds100.keymapper.util.State diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ListKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ListKeyMapsUseCase.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ListKeyMapsUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ListKeyMapsUseCase.kt index d1b233cd92..8fc9f47b8b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ListKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ListKeyMapsUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import android.database.sqlite.SQLiteConstraintException import io.github.sds100.keymapper.R diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/PauseKeyMapsUseCase.kt similarity index 96% rename from app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/PauseKeyMapsUseCase.kt index 3bb0f20a98..b5acf2b422 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/PauseKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/PauseKeyMapsUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.repositories.PreferenceRepository diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ShortcutModel.kt similarity index 76% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutModel.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ShortcutModel.kt index ea54953873..b55b82446d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ShortcutModel.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ShortcutRow.kt similarity index 97% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/ShortcutRow.kt index 6c157e7ace..6653732dcd 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/ShortcutRow.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ShortcutRow.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps +package io.github.sds100.keymapper.keymaps import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement @@ -26,7 +26,7 @@ import androidx.compose.ui.unit.dp import com.google.accompanist.drawablepainter.rememberDrawablePainter import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.mappings.keymaps.trigger.TriggerKeyShortcut +import io.github.sds100.keymapper.trigger.TriggerKeyShortcut import io.github.sds100.keymapper.util.drawable import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/SimpleMappingController.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/SimpleMappingController.kt similarity index 97% rename from app/src/main/java/io/github/sds100/keymapper/mappings/SimpleMappingController.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/SimpleMappingController.kt index dc3d19e77c..7ea2439531 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/SimpleMappingController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/SimpleMappingController.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.actions.Action import io.github.sds100.keymapper.actions.PerformActionsUseCase @@ -6,8 +6,7 @@ import io.github.sds100.keymapper.actions.RepeatMode import io.github.sds100.keymapper.constraints.DetectConstraintsUseCase import io.github.sds100.keymapper.constraints.isSatisfied import io.github.sds100.keymapper.data.PreferenceDefaults -import io.github.sds100.keymapper.mappings.keymaps.KeyMap -import io.github.sds100.keymapper.mappings.keymaps.detection.DetectKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapsUseCase import io.github.sds100.keymapper.util.InputEventType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapModel.kt similarity index 61% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapModel.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapModel.kt index 6242e555bd..7023705558 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapModel.kt @@ -1,7 +1,7 @@ -package io.github.sds100.keymapper.mappings.keymaps.detection +package io.github.sds100.keymapper.keymaps.detection import io.github.sds100.keymapper.constraints.ConstraintState -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMap data class DetectKeyMapModel( val keyMap: KeyMap, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapsUseCase.kt similarity index 96% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapsUseCase.kt index b7db65f9c4..83d7d6b6ef 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapsUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.detection +package io.github.sds100.keymapper.keymaps.detection import android.accessibilityservice.AccessibilityService import android.os.SystemClock @@ -13,10 +13,9 @@ import io.github.sds100.keymapper.data.repositories.GroupRepository import io.github.sds100.keymapper.data.repositories.PreferenceRepository import io.github.sds100.keymapper.groups.Group import io.github.sds100.keymapper.groups.GroupEntityMapper -import io.github.sds100.keymapper.mappings.keymaps.KeyMap -import io.github.sds100.keymapper.mappings.keymaps.KeyMapEntityMapper -import io.github.sds100.keymapper.mappings.keymaps.KeyMapRepository -import io.github.sds100.keymapper.mappings.keymaps.trigger.FingerprintTriggerKey +import io.github.sds100.keymapper.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMapEntityMapper +import io.github.sds100.keymapper.keymaps.KeyMapRepository import io.github.sds100.keymapper.system.accessibility.IAccessibilityService import io.github.sds100.keymapper.system.display.DisplayAdapter import io.github.sds100.keymapper.system.inputevents.InputEventInjector @@ -29,6 +28,7 @@ import io.github.sds100.keymapper.system.popup.PopupMessageAdapter import io.github.sds100.keymapper.system.root.SuAdapter import io.github.sds100.keymapper.system.vibrator.VibratorAdapter import io.github.sds100.keymapper.system.volume.VolumeAdapter +import io.github.sds100.keymapper.trigger.FingerprintTriggerKey import io.github.sds100.keymapper.util.InputEventType import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.dataOrNull diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectScreenOffKeyEventsController.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectScreenOffKeyEventsController.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectScreenOffKeyEventsController.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectScreenOffKeyEventsController.kt index 600e9fab2f..a492fe2ec7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DetectScreenOffKeyEventsController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectScreenOffKeyEventsController.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.detection +package io.github.sds100.keymapper.keymaps.detection import android.view.InputDevice import android.view.KeyEvent diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DpadMotionEventTracker.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DpadMotionEventTracker.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DpadMotionEventTracker.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DpadMotionEventTracker.kt index 0266b50cfa..adc5cc98a3 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/DpadMotionEventTracker.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DpadMotionEventTracker.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.detection +package io.github.sds100.keymapper.keymaps.detection import android.view.InputDevice import android.view.KeyEvent diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/ParallelTriggerActionPerformer.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/ParallelTriggerActionPerformer.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/ParallelTriggerActionPerformer.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/detection/ParallelTriggerActionPerformer.kt index 1a8a3ef36d..0050a51d0d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/ParallelTriggerActionPerformer.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/ParallelTriggerActionPerformer.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.detection +package io.github.sds100.keymapper.keymaps.detection import io.github.sds100.keymapper.actions.Action import io.github.sds100.keymapper.actions.ActionData diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/SequenceTriggerActionPerformer.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/SequenceTriggerActionPerformer.kt similarity index 95% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/SequenceTriggerActionPerformer.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/detection/SequenceTriggerActionPerformer.kt index a5ef9e2b0d..aa04286de1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/SequenceTriggerActionPerformer.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/SequenceTriggerActionPerformer.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.detection +package io.github.sds100.keymapper.keymaps.detection import io.github.sds100.keymapper.actions.Action import io.github.sds100.keymapper.actions.PerformActionsUseCase diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/TriggerKeyMapFromOtherAppsController.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/TriggerKeyMapFromOtherAppsController.kt similarity index 88% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/TriggerKeyMapFromOtherAppsController.kt rename to app/src/main/java/io/github/sds100/keymapper/keymaps/detection/TriggerKeyMapFromOtherAppsController.kt index 089a482d2a..e780051f67 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/detection/TriggerKeyMapFromOtherAppsController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/TriggerKeyMapFromOtherAppsController.kt @@ -1,9 +1,9 @@ -package io.github.sds100.keymapper.mappings.keymaps.detection +package io.github.sds100.keymapper.keymaps.detection import io.github.sds100.keymapper.actions.PerformActionsUseCase import io.github.sds100.keymapper.constraints.DetectConstraintsUseCase -import io.github.sds100.keymapper.mappings.SimpleMappingController -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.SimpleMappingController import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch diff --git a/app/src/main/java/io/github/sds100/keymapper/sorting/SortKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/sorting/SortKeyMapsUseCase.kt index c263292258..03b9ca6835 100644 --- a/app/src/main/java/io/github/sds100/keymapper/sorting/SortKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/sorting/SortKeyMapsUseCase.kt @@ -2,15 +2,14 @@ package io.github.sds100.keymapper.sorting import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.repositories.PreferenceRepository -import io.github.sds100.keymapper.mappings.keymaps.DisplayKeyMapUseCase -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.DisplayKeyMapUseCase +import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.sorting.comparators.KeyMapActionsComparator import io.github.sds100.keymapper.sorting.comparators.KeyMapConstraintsComparator import io.github.sds100.keymapper.sorting.comparators.KeyMapOptionsComparator import io.github.sds100.keymapper.sorting.comparators.KeyMapTriggerComparator import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json class SortKeyMapsUseCaseImpl( diff --git a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapActionsComparator.kt b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapActionsComparator.kt index 94158219b8..479b5a3922 100644 --- a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapActionsComparator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapActionsComparator.kt @@ -2,7 +2,7 @@ package io.github.sds100.keymapper.sorting.comparators import io.github.sds100.keymapper.actions.ActionData import io.github.sds100.keymapper.actions.DisplayActionUseCase -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.Success import io.github.sds100.keymapper.util.valueOrNull diff --git a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapConstraintsComparator.kt b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapConstraintsComparator.kt index 7ba746f11e..036e4ec9af 100644 --- a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapConstraintsComparator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapConstraintsComparator.kt @@ -2,7 +2,7 @@ package io.github.sds100.keymapper.sorting.comparators import io.github.sds100.keymapper.constraints.Constraint import io.github.sds100.keymapper.constraints.DisplayConstraintUseCase -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.Success import io.github.sds100.keymapper.util.then diff --git a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapOptionsComparator.kt b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapOptionsComparator.kt index a891a23393..27cf6c9ed4 100644 --- a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapOptionsComparator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapOptionsComparator.kt @@ -1,6 +1,6 @@ package io.github.sds100.keymapper.sorting.comparators -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMap class KeyMapOptionsComparator( /** diff --git a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapTriggerComparator.kt b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapTriggerComparator.kt index b5425499c7..47ef0131aa 100644 --- a/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapTriggerComparator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/sorting/comparators/KeyMapTriggerComparator.kt @@ -1,6 +1,6 @@ package io.github.sds100.keymapper.sorting.comparators -import io.github.sds100.keymapper.mappings.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.KeyMap class KeyMapTriggerComparator( /** diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt index cd1a7ef200..73931e1160 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/BaseAccessibilityServiceController.kt @@ -13,15 +13,14 @@ import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.PreferenceDefaults import io.github.sds100.keymapper.data.repositories.AccessibilityNodeRepository import io.github.sds100.keymapper.data.repositories.PreferenceRepository -import io.github.sds100.keymapper.mappings.FingerprintGestureType -import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCase -import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase -import io.github.sds100.keymapper.mappings.keymaps.detection.DetectKeyMapsUseCase -import io.github.sds100.keymapper.mappings.keymaps.detection.DetectScreenOffKeyEventsController -import io.github.sds100.keymapper.mappings.keymaps.detection.DpadMotionEventTracker -import io.github.sds100.keymapper.mappings.keymaps.detection.KeyMapController -import io.github.sds100.keymapper.mappings.keymaps.detection.TriggerKeyMapFromOtherAppsController -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyEventDetectionSource +import io.github.sds100.keymapper.keymaps.FingerprintGestureType +import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCase +import io.github.sds100.keymapper.keymaps.PauseKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.detection.DetectScreenOffKeyEventsController +import io.github.sds100.keymapper.keymaps.detection.DpadMotionEventTracker +import io.github.sds100.keymapper.keymaps.detection.KeyMapController +import io.github.sds100.keymapper.keymaps.detection.TriggerKeyMapFromOtherAppsController import io.github.sds100.keymapper.reroutekeyevents.RerouteKeyEventsController import io.github.sds100.keymapper.reroutekeyevents.RerouteKeyEventsUseCase import io.github.sds100.keymapper.system.devices.DevicesAdapter @@ -30,6 +29,7 @@ import io.github.sds100.keymapper.system.inputevents.MyKeyEvent import io.github.sds100.keymapper.system.inputevents.MyMotionEvent import io.github.sds100.keymapper.system.inputmethod.InputMethodAdapter import io.github.sds100.keymapper.system.root.SuAdapter +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource import io.github.sds100.keymapper.util.ServiceEvent import io.github.sds100.keymapper.util.firstBlocking import kotlinx.coroutines.CoroutineScope diff --git a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt index c640bcf18f..0610083100 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/accessibility/MyAccessibilityService.kt @@ -27,11 +27,11 @@ import io.github.sds100.keymapper.actions.pinchscreen.PinchScreenType import io.github.sds100.keymapper.api.IKeyEventRelayServiceCallback import io.github.sds100.keymapper.api.KeyEventRelayService import io.github.sds100.keymapper.api.KeyEventRelayServiceWrapperImpl -import io.github.sds100.keymapper.mappings.FingerprintGestureType -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyEventDetectionSource +import io.github.sds100.keymapper.keymaps.FingerprintGestureType import io.github.sds100.keymapper.system.devices.InputDeviceUtils import io.github.sds100.keymapper.system.inputevents.MyKeyEvent import io.github.sds100.keymapper.system.inputevents.MyMotionEvent +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.Inject import io.github.sds100.keymapper.util.InputEventType diff --git a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/AutoSwitchImeController.kt b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/AutoSwitchImeController.kt index 63f41376a0..cf1a2dcca1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/AutoSwitchImeController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/inputmethod/AutoSwitchImeController.kt @@ -4,7 +4,7 @@ import io.github.sds100.keymapper.R import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.PreferenceDefaults import io.github.sds100.keymapper.data.repositories.PreferenceRepository -import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.PauseKeyMapsUseCase import io.github.sds100.keymapper.system.accessibility.ServiceAdapter import io.github.sds100.keymapper.system.devices.DevicesAdapter import io.github.sds100.keymapper.system.popup.PopupMessageAdapter diff --git a/app/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationController.kt b/app/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationController.kt index b3168fd813..15005576bb 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationController.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/notifications/NotificationController.kt @@ -6,7 +6,7 @@ import androidx.core.app.NotificationManagerCompat import io.github.sds100.keymapper.BaseMainActivity import io.github.sds100.keymapper.Constants import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.mappings.PauseKeyMapsUseCase +import io.github.sds100.keymapper.keymaps.PauseKeyMapsUseCase import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.system.accessibility.ControlAccessibilityServiceUseCase import io.github.sds100.keymapper.system.accessibility.ServiceState diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/AssistantTriggerKey.kt similarity index 96% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerKey.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/AssistantTriggerKey.kt index 45e37e94e4..1be79413e4 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/AssistantTriggerKey.kt @@ -1,8 +1,8 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import io.github.sds100.keymapper.data.entities.AssistantTriggerKeyEntity import io.github.sds100.keymapper.data.entities.TriggerKeyEntity -import io.github.sds100.keymapper.mappings.ClickType +import io.github.sds100.keymapper.keymaps.ClickType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import java.util.UUID diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerType.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/AssistantTriggerType.kt similarity index 90% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerType.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/AssistantTriggerType.kt index f82c6ad9b0..57a5b82736 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/AssistantTriggerType.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/AssistantTriggerType.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger /** * The type of assistant that triggers an assistant trigger key. The voice assistant diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt similarity index 97% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt index 34658e5890..87148ac2f9 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/BaseConfigTriggerViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import android.view.KeyEvent import androidx.compose.material.icons.Icons @@ -9,15 +9,15 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.mappings.ClickType -import io.github.sds100.keymapper.mappings.FingerprintGestureType -import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCase -import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapOptionsViewModel -import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapUseCase -import io.github.sds100.keymapper.mappings.keymaps.CreateKeyMapShortcutUseCase -import io.github.sds100.keymapper.mappings.keymaps.DisplayKeyMapUseCase -import io.github.sds100.keymapper.mappings.keymaps.KeyMap -import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel +import io.github.sds100.keymapper.keymaps.ClickType +import io.github.sds100.keymapper.keymaps.ConfigKeyMapOptionsViewModel +import io.github.sds100.keymapper.keymaps.ConfigKeyMapUseCase +import io.github.sds100.keymapper.keymaps.CreateKeyMapShortcutUseCase +import io.github.sds100.keymapper.keymaps.DisplayKeyMapUseCase +import io.github.sds100.keymapper.keymaps.FingerprintGestureType +import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCase +import io.github.sds100.keymapper.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.ShortcutModel import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.purchasing.ProductId import io.github.sds100.keymapper.purchasing.PurchasingManager diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ChooseTriggerKeyDeviceModel.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/ChooseTriggerKeyDeviceModel.kt similarity index 72% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ChooseTriggerKeyDeviceModel.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/ChooseTriggerKeyDeviceModel.kt index 7189f03d95..4ffb1bae0a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/ChooseTriggerKeyDeviceModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/ChooseTriggerKeyDeviceModel.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger /** * Created by sds100 on 07/03/2021. diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FingerprintTriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/FingerprintTriggerKey.kt similarity index 94% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FingerprintTriggerKey.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/FingerprintTriggerKey.kt index 1ec7fae6f4..a5240ff1da 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FingerprintTriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/FingerprintTriggerKey.kt @@ -1,9 +1,9 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import io.github.sds100.keymapper.data.entities.FingerprintTriggerKeyEntity import io.github.sds100.keymapper.data.entities.TriggerKeyEntity -import io.github.sds100.keymapper.mappings.ClickType -import io.github.sds100.keymapper.mappings.FingerprintGestureType +import io.github.sds100.keymapper.keymaps.ClickType +import io.github.sds100.keymapper.keymaps.FingerprintGestureType import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import java.util.UUID diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FloatingButtonKey.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/FloatingButtonKey.kt similarity index 95% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FloatingButtonKey.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/FloatingButtonKey.kt index ca23629e92..a316f326fa 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/FloatingButtonKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/FloatingButtonKey.kt @@ -1,11 +1,11 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import io.github.sds100.keymapper.data.entities.FloatingButtonEntityWithLayout import io.github.sds100.keymapper.data.entities.FloatingButtonKeyEntity import io.github.sds100.keymapper.data.entities.TriggerKeyEntity import io.github.sds100.keymapper.floating.FloatingButtonData import io.github.sds100.keymapper.floating.FloatingButtonEntityMapper -import io.github.sds100.keymapper.mappings.ClickType +import io.github.sds100.keymapper.keymaps.ClickType import kotlinx.serialization.Serializable import java.util.UUID diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyCodeTriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/KeyCodeTriggerKey.kt similarity index 95% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyCodeTriggerKey.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/KeyCodeTriggerKey.kt index 94b8c0f579..5973500c6c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyCodeTriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/KeyCodeTriggerKey.kt @@ -1,8 +1,8 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import io.github.sds100.keymapper.data.entities.KeyCodeTriggerKeyEntity import io.github.sds100.keymapper.data.entities.TriggerKeyEntity -import io.github.sds100.keymapper.mappings.ClickType +import io.github.sds100.keymapper.keymaps.ClickType import kotlinx.serialization.Serializable import splitties.bitflags.hasFlag import splitties.bitflags.withFlag @@ -89,8 +89,9 @@ data class KeyCodeTriggerKey( TriggerKeyDevice.Internal -> KeyCodeTriggerKeyEntity.DEVICE_ID_THIS_DEVICE } - val deviceName = if (key.device is TriggerKeyDevice.External) { - key.device.name + val deviceName = + if (key.device is TriggerKeyDevice.External) { + key.device.name } else { null } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyEventDetectionSource.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/KeyEventDetectionSource.kt similarity index 58% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyEventDetectionSource.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/KeyEventDetectionSource.kt index b1624e368f..66f147dfc8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyEventDetectionSource.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/KeyEventDetectionSource.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger enum class KeyEventDetectionSource { ACCESSIBILITY_SERVICE, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyMapListItemModel.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/KeyMapListItemModel.kt similarity index 92% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyMapListItemModel.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/KeyMapListItemModel.kt index 95ebd9ba69..accb6bf73b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/KeyMapListItemModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/KeyMapListItemModel.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import androidx.compose.ui.graphics.vector.ImageVector import io.github.sds100.keymapper.constraints.ConstraintMode diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerButtonRow.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerButtonRow.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt index 5f97f455ad..3646b41d7a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerButtonRow.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerState.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerState.kt similarity index 85% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerState.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerState.kt index 2a0c34f462..afaad64fad 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerState.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerState.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger /** * Created by sds100 on 04/03/2021. diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerUseCase.kt similarity index 96% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerUseCase.kt index 68b783ef83..a9e5730ca2 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordTriggerUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerUseCase.kt @@ -1,7 +1,7 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import android.view.KeyEvent -import io.github.sds100.keymapper.mappings.keymaps.detection.DpadMotionEventTracker +import io.github.sds100.keymapper.keymaps.detection.DpadMotionEventTracker import io.github.sds100.keymapper.system.accessibility.ServiceAdapter import io.github.sds100.keymapper.system.devices.InputDeviceInfo import io.github.sds100.keymapper.system.inputevents.InputEventUtils diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordedKey.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordedKey.kt similarity index 74% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordedKey.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/RecordedKey.kt index a6eb4e92d9..17cbc6c856 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/RecordedKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordedKey.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger /** * Created by sds100 on 04/03/2021. diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardBottomSheet.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardBottomSheet.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardBottomSheet.kt index 51c21bd786..b6582cbee3 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardBottomSheet.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardBottomSheet.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardState.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardState.kt similarity index 85% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardState.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardState.kt index 4170cdb35a..c21861f62c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardState.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardState.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger data class SetupGuiKeyboardState( val isKeyboardInstalled: Boolean, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardUseCase.kt similarity index 97% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardUseCase.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardUseCase.kt index c891d88b8a..759e3dbff6 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/SetupGuiKeyboardUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/SetupGuiKeyboardUseCase.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import io.github.sds100.keymapper.system.apps.PackageInfo import io.github.sds100.keymapper.system.apps.PackageManagerAdapter diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/Trigger.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/Trigger.kt similarity index 95% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/Trigger.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/Trigger.kt index 576ed1fd12..46d94bf956 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/Trigger.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/Trigger.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import io.github.sds100.keymapper.data.entities.AssistantTriggerKeyEntity import io.github.sds100.keymapper.data.entities.EntityExtra @@ -9,7 +9,7 @@ import io.github.sds100.keymapper.data.entities.KeyCodeTriggerKeyEntity import io.github.sds100.keymapper.data.entities.TriggerEntity import io.github.sds100.keymapper.data.entities.getData import io.github.sds100.keymapper.floating.FloatingButtonEntityMapper -import io.github.sds100.keymapper.mappings.ClickType +import io.github.sds100.keymapper.keymaps.ClickType import io.github.sds100.keymapper.system.inputevents.InputEventUtils import io.github.sds100.keymapper.util.valueOrNull import kotlinx.serialization.Serializable @@ -53,7 +53,10 @@ data class Trigger( fun isDetectingWhenScreenOffAllowed(): Boolean { return keys.isNotEmpty() && keys.all { - it is KeyCodeTriggerKey && InputEventUtils.canDetectKeyWhenScreenOff(it.keyCode) + it is KeyCodeTriggerKey && + InputEventUtils.canDetectKeyWhenScreenOff( + it.keyCode, + ) } } @@ -90,7 +93,9 @@ object TriggerEntityMapper { val keys = entity.keys.map { key -> when (key) { is AssistantTriggerKeyEntity -> AssistantTriggerKey.fromEntity(key) - is KeyCodeTriggerKeyEntity -> KeyCodeTriggerKey.fromEntity(key) + is KeyCodeTriggerKeyEntity -> KeyCodeTriggerKey.fromEntity( + key, + ) is FloatingButtonKeyEntity -> { val floatingButton = floatingButtons.find { it.button.uid == key.buttonUid } FloatingButtonKey.fromEntity(key, floatingButton) @@ -203,7 +208,9 @@ object TriggerEntityMapper { val keys = trigger.keys.map { key -> when (key) { is AssistantTriggerKey -> AssistantTriggerKey.toEntity(key) - is KeyCodeTriggerKey -> KeyCodeTriggerKey.toEntity(key) + is KeyCodeTriggerKey -> KeyCodeTriggerKey.toEntity( + key, + ) is FloatingButtonKey -> FloatingButtonKey.toEntity(key) is FingerprintTriggerKey -> FingerprintTriggerKey.toEntity(key) } diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerError.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerError.kt similarity index 91% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerError.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerError.kt index a3fa5cc5e1..1d00b9fa36 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerError.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerError.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger /** * Created by sds100 on 04/04/2021. diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerErrorSnapshot.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerErrorSnapshot.kt similarity index 83% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerErrorSnapshot.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerErrorSnapshot.kt index 7b4fe3910d..5aac124b17 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerErrorSnapshot.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerErrorSnapshot.kt @@ -1,9 +1,9 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import android.os.Build import android.view.KeyEvent -import io.github.sds100.keymapper.mappings.keymaps.KeyMap -import io.github.sds100.keymapper.mappings.keymaps.requiresImeKeyEventForwardingInPhoneCall +import io.github.sds100.keymapper.keymaps.KeyMap +import io.github.sds100.keymapper.keymaps.requiresImeKeyEventForwardingInPhoneCall import io.github.sds100.keymapper.purchasing.ProductId import io.github.sds100.keymapper.system.inputevents.InputEventUtils import io.github.sds100.keymapper.util.Error @@ -53,7 +53,8 @@ data class TriggerErrorSnapshot( return TriggerError.CANT_DETECT_IN_PHONE_CALL } - val requiresDndAccess = key is KeyCodeTriggerKey && key.keyCode in keysThatRequireDndAccess + val requiresDndAccess = + key is KeyCodeTriggerKey && key.keyCode in keysThatRequireDndAccess if (requiresDndAccess) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isDndAccessGranted) { @@ -69,7 +70,11 @@ data class TriggerErrorSnapshot( } val containsDpadKey = - key is KeyCodeTriggerKey && InputEventUtils.isDpadKeyCode(key.keyCode) && key.detectionSource == KeyEventDetectionSource.INPUT_METHOD + key is KeyCodeTriggerKey && + InputEventUtils.isDpadKeyCode( + key.keyCode, + ) && + key.detectionSource == KeyEventDetectionSource.INPUT_METHOD if (showDpadImeSetupError && !isKeyMapperImeChosen && containsDpadKey) { return TriggerError.DPAD_IME_NOT_SELECTED diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKey.kt similarity index 89% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKey.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKey.kt index a94d0db410..0f6c89a17d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKey.kt @@ -1,6 +1,6 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger -import io.github.sds100.keymapper.mappings.ClickType +import io.github.sds100.keymapper.keymaps.ClickType import kotlinx.serialization.Serializable @Serializable diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyDevice.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyDevice.kt similarity index 95% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyDevice.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyDevice.kt index 3c0422ef05..24f6dc8672 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyDevice.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyDevice.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import io.github.sds100.keymapper.system.devices.InputDeviceInfo import kotlinx.serialization.Serializable diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyListItem.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyListItem.kt index 4b44d2ea23..2bc355370a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyListItem.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyListItem.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.draggable @@ -42,8 +42,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.R -import io.github.sds100.keymapper.mappings.ClickType -import io.github.sds100.keymapper.mappings.FingerprintGestureType +import io.github.sds100.keymapper.keymaps.ClickType +import io.github.sds100.keymapper.keymaps.FingerprintGestureType import io.github.sds100.keymapper.util.ui.LinkType import io.github.sds100.keymapper.util.ui.compose.DragDropState diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyOptionsBottomSheet.kt similarity index 98% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyOptionsBottomSheet.kt index f1fb647ca7..5de6ed7cbc 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyOptionsBottomSheet.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyOptionsBottomSheet.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -36,8 +36,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.mappings.ClickType -import io.github.sds100.keymapper.mappings.FingerprintGestureType +import io.github.sds100.keymapper.keymaps.ClickType +import io.github.sds100.keymapper.keymaps.FingerprintGestureType import io.github.sds100.keymapper.util.ui.CheckBoxListItem import io.github.sds100.keymapper.util.ui.compose.CheckBoxText import io.github.sds100.keymapper.util.ui.compose.RadioButtonText diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyShortcut.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyShortcut.kt similarity index 61% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyShortcut.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyShortcut.kt index 305ab268dc..26c90c031c 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerKeyShortcut.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerKeyShortcut.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger enum class TriggerKeyShortcut { ASSISTANT, diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerMode.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerMode.kt similarity index 82% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerMode.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerMode.kt index 2da4ef2551..1c04435521 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerMode.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerMode.kt @@ -1,6 +1,6 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger -import io.github.sds100.keymapper.mappings.ClickType +import io.github.sds100.keymapper.keymaps.ClickType import kotlinx.serialization.Serializable /** @@ -15,7 +15,7 @@ sealed class TriggerMode : Comparable { data class Parallel(val clickType: ClickType) : TriggerMode() { override fun compareTo(other: TriggerMode): Int { if (other !is Parallel) { - return super.compareTo(other) + return super.compareTo(other) } return clickType.compareTo(other.clickType) diff --git a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt similarity index 99% rename from app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt rename to app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt index f20a2ad284..153bb38a87 100644 --- a/app/src/main/java/io/github/sds100/keymapper/mappings/keymaps/trigger/TriggerScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.mappings.keymaps.trigger +package io.github.sds100.keymapper.trigger import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -39,9 +39,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.core.layout.WindowHeightSizeClass import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme -import io.github.sds100.keymapper.mappings.ClickType -import io.github.sds100.keymapper.mappings.keymaps.ShortcutModel -import io.github.sds100.keymapper.mappings.keymaps.ShortcutRow +import io.github.sds100.keymapper.keymaps.ClickType +import io.github.sds100.keymapper.keymaps.ShortcutModel +import io.github.sds100.keymapper.keymaps.ShortcutRow import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.ui.LinkType import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo diff --git a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt index 6bb6786364..3ad39fcfac 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt @@ -22,13 +22,12 @@ import io.github.sds100.keymapper.constraints.ChooseConstraintViewModel import io.github.sds100.keymapper.constraints.CreateConstraintUseCaseImpl import io.github.sds100.keymapper.home.HomeViewModel import io.github.sds100.keymapper.home.ShowHomeScreenAlertsUseCaseImpl +import io.github.sds100.keymapper.keymaps.ConfigKeyMapViewModel +import io.github.sds100.keymapper.keymaps.CreateKeyMapShortcutViewModel +import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCaseImpl +import io.github.sds100.keymapper.keymaps.ListKeyMapsUseCaseImpl import io.github.sds100.keymapper.logging.DisplayLogUseCaseImpl import io.github.sds100.keymapper.logging.LogViewModel -import io.github.sds100.keymapper.mappings.FingerprintGesturesSupportedUseCaseImpl -import io.github.sds100.keymapper.mappings.keymaps.ConfigKeyMapViewModel -import io.github.sds100.keymapper.mappings.keymaps.CreateKeyMapShortcutViewModel -import io.github.sds100.keymapper.mappings.keymaps.ListKeyMapsUseCaseImpl -import io.github.sds100.keymapper.mappings.keymaps.trigger.SetupGuiKeyboardUseCaseImpl import io.github.sds100.keymapper.settings.ConfigSettingsUseCaseImpl import io.github.sds100.keymapper.settings.SettingsViewModel import io.github.sds100.keymapper.sorting.SortKeyMapsUseCaseImpl @@ -42,6 +41,7 @@ import io.github.sds100.keymapper.system.bluetooth.ChooseBluetoothDeviceUseCaseI import io.github.sds100.keymapper.system.bluetooth.ChooseBluetoothDeviceViewModel import io.github.sds100.keymapper.system.inputmethod.ShowInputMethodPickerUseCaseImpl import io.github.sds100.keymapper.system.intents.ConfigIntentViewModel +import io.github.sds100.keymapper.trigger.SetupGuiKeyboardUseCaseImpl /** * Created by sds100 on 26/01/2020. diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt b/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt index 4bacac2040..2cbc56b280 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ServiceEvent.kt @@ -2,9 +2,9 @@ package io.github.sds100.keymapper.util import android.os.Parcelable import io.github.sds100.keymapper.actions.ActionData -import io.github.sds100.keymapper.mappings.keymaps.trigger.KeyEventDetectionSource import io.github.sds100.keymapper.system.accessibility.RecordAccessibilityNodeState import io.github.sds100.keymapper.system.devices.InputDeviceInfo +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/CompactChip.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/CompactChip.kt index 2fe1b81ea9..e669f1733d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/CompactChip.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/CompactChip.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import io.github.sds100.keymapper.mappings.keymaps.chipHeight +import io.github.sds100.keymapper.keymaps.chipHeight @Composable fun CompactChip( diff --git a/app/src/main/res/navigation/nav_config_keymap.xml b/app/src/main/res/navigation/nav_config_keymap.xml index 824d90ac2c..4ea4136136 100644 --- a/app/src/main/res/navigation/nav_config_keymap.xml +++ b/app/src/main/res/navigation/nav_config_keymap.xml @@ -6,7 +6,7 @@ Date: Wed, 14 May 2025 17:54:10 +0200 Subject: [PATCH 75/95] fix style and tests --- .../keymapper/trigger/KeyCodeTriggerKey.kt | 6 +-- .../keymapper/ConfigKeyMapUseCaseTest.kt | 12 ++--- .../DpadMotionEventTrackerTest.kt | 2 +- .../{keymaps => }/KeyMapControllerTest.kt | 44 +++++++++---------- .../ProcessKeyMapGroupsForDetectionTest.kt | 3 +- ...riggerKeyMapFromOtherAppsControllerTest.kt | 5 +-- .../sds100/keymapper/util/KeyMapUtils.kt | 10 ++--- 7 files changed, 39 insertions(+), 43 deletions(-) rename app/src/test/java/io/github/sds100/keymapper/keymaps/{keymaps => }/DpadMotionEventTrackerTest.kt (99%) rename app/src/test/java/io/github/sds100/keymapper/keymaps/{keymaps => }/KeyMapControllerTest.kt (98%) rename app/src/test/java/io/github/sds100/keymapper/keymaps/{keymaps => }/ProcessKeyMapGroupsForDetectionTest.kt (98%) rename app/src/test/java/io/github/sds100/keymapper/keymaps/{keymaps => }/TriggerKeyMapFromOtherAppsControllerTest.kt (96%) diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/KeyCodeTriggerKey.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/KeyCodeTriggerKey.kt index 5973500c6c..91d5096ac2 100644 --- a/app/src/main/java/io/github/sds100/keymapper/trigger/KeyCodeTriggerKey.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/KeyCodeTriggerKey.kt @@ -92,9 +92,9 @@ data class KeyCodeTriggerKey( val deviceName = if (key.device is TriggerKeyDevice.External) { key.device.name - } else { - null - } + } else { + null + } val clickType = when (key.clickType) { ClickType.SHORT_PRESS -> TriggerKeyEntity.SHORT_PRESS diff --git a/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt index ae784a0290..d80b0cbe1e 100644 --- a/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/ConfigKeyMapUseCaseTest.kt @@ -8,13 +8,13 @@ import io.github.sds100.keymapper.keymaps.ClickType import io.github.sds100.keymapper.keymaps.ConfigKeyMapUseCaseController import io.github.sds100.keymapper.keymaps.FingerprintGestureType import io.github.sds100.keymapper.keymaps.KeyMap -import io.github.sds100.keymapper.keymaps.keymaps.trigger.AssistantTriggerKey -import io.github.sds100.keymapper.keymaps.keymaps.trigger.AssistantTriggerType -import io.github.sds100.keymapper.keymaps.keymaps.trigger.KeyEventDetectionSource -import io.github.sds100.keymapper.keymaps.keymaps.trigger.Trigger -import io.github.sds100.keymapper.keymaps.keymaps.trigger.TriggerKeyDevice -import io.github.sds100.keymapper.keymaps.keymaps.trigger.TriggerMode import io.github.sds100.keymapper.system.inputevents.InputEventUtils +import io.github.sds100.keymapper.trigger.AssistantTriggerKey +import io.github.sds100.keymapper.trigger.AssistantTriggerType +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource +import io.github.sds100.keymapper.trigger.Trigger +import io.github.sds100.keymapper.trigger.TriggerKeyDevice +import io.github.sds100.keymapper.trigger.TriggerMode import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.dataOrNull import io.github.sds100.keymapper.util.singleKeyTrigger diff --git a/app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/DpadMotionEventTrackerTest.kt b/app/src/test/java/io/github/sds100/keymapper/keymaps/DpadMotionEventTrackerTest.kt similarity index 99% rename from app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/DpadMotionEventTrackerTest.kt rename to app/src/test/java/io/github/sds100/keymapper/keymaps/DpadMotionEventTrackerTest.kt index 2fe5a67768..732c7d92b0 100644 --- a/app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/DpadMotionEventTrackerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/keymaps/DpadMotionEventTrackerTest.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.keymaps.keymaps +package io.github.sds100.keymapper.keymaps import android.view.InputDevice import android.view.KeyEvent diff --git a/app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/KeyMapControllerTest.kt b/app/src/test/java/io/github/sds100/keymapper/keymaps/KeyMapControllerTest.kt similarity index 98% rename from app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/KeyMapControllerTest.kt rename to app/src/test/java/io/github/sds100/keymapper/keymaps/KeyMapControllerTest.kt index 8687292cb9..c068b9cb03 100644 --- a/app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/KeyMapControllerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/keymaps/KeyMapControllerTest.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.keymaps.keymaps +package io.github.sds100.keymapper.keymaps import android.view.KeyEvent import androidx.arch.core.executor.testing.InstantTaskExecutorRule @@ -12,22 +12,20 @@ import io.github.sds100.keymapper.constraints.ConstraintMode import io.github.sds100.keymapper.constraints.ConstraintSnapshot import io.github.sds100.keymapper.constraints.ConstraintState import io.github.sds100.keymapper.constraints.DetectConstraintsUseCase -import io.github.sds100.keymapper.keymaps.ClickType -import io.github.sds100.keymapper.keymaps.FingerprintGestureType -import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapModel import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapsUseCase import io.github.sds100.keymapper.keymaps.detection.KeyMapController -import io.github.sds100.keymapper.keymaps.keymaps.trigger.FingerprintTriggerKey -import io.github.sds100.keymapper.keymaps.keymaps.trigger.KeyEventDetectionSource -import io.github.sds100.keymapper.keymaps.keymaps.trigger.Trigger -import io.github.sds100.keymapper.keymaps.keymaps.trigger.TriggerKey -import io.github.sds100.keymapper.keymaps.keymaps.trigger.TriggerKeyDevice -import io.github.sds100.keymapper.keymaps.keymaps.trigger.TriggerMode import io.github.sds100.keymapper.system.camera.CameraLens import io.github.sds100.keymapper.system.devices.InputDeviceInfo import io.github.sds100.keymapper.system.inputevents.MyKeyEvent import io.github.sds100.keymapper.system.inputevents.MyMotionEvent +import io.github.sds100.keymapper.trigger.FingerprintTriggerKey +import io.github.sds100.keymapper.trigger.KeyCodeTriggerKey +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource +import io.github.sds100.keymapper.trigger.Trigger +import io.github.sds100.keymapper.trigger.TriggerKey +import io.github.sds100.keymapper.trigger.TriggerKeyDevice +import io.github.sds100.keymapper.trigger.TriggerMode import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.InputEventType import io.github.sds100.keymapper.util.TestConstraintSnapshot @@ -2899,7 +2897,7 @@ class KeyMapControllerTest { listOf(KeyMap(0, trigger = trigger, actionList = listOf(TEST_ACTION))) // when - (trigger.keys[1] as _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey).let { + (trigger.keys[1] as KeyCodeTriggerKey).let { inputKeyEvent( it.keyCode, KeyEvent.ACTION_DOWN, @@ -2907,7 +2905,7 @@ class KeyMapControllerTest { ) } - (trigger.keys[1] as _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey).let { + (trigger.keys[1] as KeyCodeTriggerKey).let { val consumed = inputKeyEvent( it.keyCode, KeyEvent.ACTION_UP, @@ -2949,7 +2947,7 @@ class KeyMapControllerTest { listOf(KeyMap(0, trigger = trigger, actionList = listOf(TEST_ACTION))) // when - for (key in trigger.keys.mapNotNull { it as? _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey }) { + for (key in trigger.keys.mapNotNull { it as? KeyCodeTriggerKey }) { inputKeyEvent( key.keyCode, KeyEvent.ACTION_DOWN, @@ -2959,7 +2957,7 @@ class KeyMapControllerTest { var consumedUpCount = 0 - for (key in trigger.keys.mapNotNull { it as? _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey }) { + for (key in trigger.keys.mapNotNull { it as? KeyCodeTriggerKey }) { val consumed = inputKeyEvent( key.keyCode, @@ -2988,7 +2986,7 @@ class KeyMapControllerTest { listOf(KeyMap(0, trigger = trigger, actionList = listOf(TEST_ACTION))) // when - for (key in trigger.keys.mapNotNull { it as? _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey }) { + for (key in trigger.keys.mapNotNull { it as? KeyCodeTriggerKey }) { inputKeyEvent( key.keyCode, KeyEvent.ACTION_DOWN, @@ -3000,7 +2998,7 @@ class KeyMapControllerTest { var consumedUpCount = 0 - for (key in trigger.keys.mapNotNull { it as? _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey }) { + for (key in trigger.keys.mapNotNull { it as? KeyCodeTriggerKey }) { val consumed = inputKeyEvent( key.keyCode, @@ -3222,7 +3220,7 @@ class KeyMapControllerTest { // WHEN var consumedCount = 0 - for (key in keyMap.trigger.keys.mapNotNull { it as? _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey }) { + for (key in keyMap.trigger.keys.mapNotNull { it as? KeyCodeTriggerKey }) { val consumed = inputKeyEvent( 999, @@ -3248,7 +3246,7 @@ class KeyMapControllerTest { var consumedCount = 0 - for (key in keyMap.trigger.keys.mapNotNull { it as? _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey }) { + for (key in keyMap.trigger.keys.mapNotNull { it as? KeyCodeTriggerKey }) { val consumed = inputKeyEvent( key.keyCode, @@ -3272,7 +3270,7 @@ class KeyMapControllerTest { var consumedCount = 0 - for (key in keyMap.trigger.keys.mapNotNull { it as? _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey }) { + for (key in keyMap.trigger.keys.mapNotNull { it as? KeyCodeTriggerKey }) { val consumed = inputKeyEvent( key.keyCode, @@ -4066,7 +4064,7 @@ class KeyMapControllerTest { } private suspend fun mockTriggerKeyInput(key: TriggerKey, delay: Long? = null) { - if (key !is _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey) { + if (key !is KeyCodeTriggerKey) { return } @@ -4154,10 +4152,10 @@ class KeyMapControllerTest { delay: Long? = null, ) { require(trigger.mode is TriggerMode.Parallel) - require(trigger.keys.all { it is _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey }) + require(trigger.keys.all { it is KeyCodeTriggerKey }) for (key in trigger.keys) { - if (key !is _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey) { + if (key !is KeyCodeTriggerKey) { continue } @@ -4177,7 +4175,7 @@ class KeyMapControllerTest { } for (key in trigger.keys) { - if (key !is _root_ide_package_.io.github.sds100.keymapper.trigger.KeyCodeTriggerKey) { + if (key !is KeyCodeTriggerKey) { continue } diff --git a/app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/ProcessKeyMapGroupsForDetectionTest.kt b/app/src/test/java/io/github/sds100/keymapper/keymaps/ProcessKeyMapGroupsForDetectionTest.kt similarity index 98% rename from app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/ProcessKeyMapGroupsForDetectionTest.kt rename to app/src/test/java/io/github/sds100/keymapper/keymaps/ProcessKeyMapGroupsForDetectionTest.kt index fd549e02a6..ac2ab51cf3 100644 --- a/app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/ProcessKeyMapGroupsForDetectionTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/keymaps/ProcessKeyMapGroupsForDetectionTest.kt @@ -1,10 +1,9 @@ -package io.github.sds100.keymapper.keymaps.keymaps +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.constraints.Constraint import io.github.sds100.keymapper.constraints.ConstraintMode import io.github.sds100.keymapper.constraints.ConstraintState import io.github.sds100.keymapper.groups.Group -import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapModel import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapsUseCaseImpl import org.hamcrest.MatcherAssert.assertThat diff --git a/app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt b/app/src/test/java/io/github/sds100/keymapper/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt similarity index 96% rename from app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt rename to app/src/test/java/io/github/sds100/keymapper/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt index 244e115fff..021d19bc77 100644 --- a/app/src/test/java/io/github/sds100/keymapper/keymaps/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/keymaps/TriggerKeyMapFromOtherAppsControllerTest.kt @@ -1,4 +1,4 @@ -package io.github.sds100.keymapper.keymaps.keymaps +package io.github.sds100.keymapper.keymaps import io.github.sds100.keymapper.actions.Action import io.github.sds100.keymapper.actions.ActionData @@ -6,10 +6,9 @@ import io.github.sds100.keymapper.actions.ActionErrorSnapshot import io.github.sds100.keymapper.actions.PerformActionsUseCase import io.github.sds100.keymapper.actions.RepeatMode import io.github.sds100.keymapper.constraints.DetectConstraintsUseCase -import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.keymaps.detection.DetectKeyMapsUseCase import io.github.sds100.keymapper.keymaps.detection.TriggerKeyMapFromOtherAppsController -import io.github.sds100.keymapper.keymaps.keymaps.trigger.Trigger +import io.github.sds100.keymapper.trigger.Trigger import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.TestConstraintSnapshot import junitparams.JUnitParamsRunner diff --git a/app/src/test/java/io/github/sds100/keymapper/util/KeyMapUtils.kt b/app/src/test/java/io/github/sds100/keymapper/util/KeyMapUtils.kt index b247ceee77..eb4a9c26c3 100644 --- a/app/src/test/java/io/github/sds100/keymapper/util/KeyMapUtils.kt +++ b/app/src/test/java/io/github/sds100/keymapper/util/KeyMapUtils.kt @@ -1,11 +1,11 @@ package io.github.sds100.keymapper.util import io.github.sds100.keymapper.keymaps.ClickType -import io.github.sds100.keymapper.keymaps.keymaps.trigger.KeyEventDetectionSource -import io.github.sds100.keymapper.keymaps.keymaps.trigger.Trigger -import io.github.sds100.keymapper.keymaps.keymaps.trigger.TriggerKey -import io.github.sds100.keymapper.keymaps.keymaps.trigger.TriggerKeyDevice -import io.github.sds100.keymapper.keymaps.keymaps.trigger.TriggerMode +import io.github.sds100.keymapper.trigger.KeyEventDetectionSource +import io.github.sds100.keymapper.trigger.Trigger +import io.github.sds100.keymapper.trigger.TriggerKey +import io.github.sds100.keymapper.trigger.TriggerKeyDevice +import io.github.sds100.keymapper.trigger.TriggerMode /** * Created by sds100 on 19/04/2021. From 9b2092ba904fbe2f7b26e0364aab5682051a13cb Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 18:25:14 +0200 Subject: [PATCH 76/95] #1466 feat: add tap target to create a key map --- .../io/github/sds100/keymapper/data/Keys.kt | 17 +++++- .../keymapper/home/HomeKeyMapListScreen.kt | 55 ++++------------- .../sds100/keymapper/home/HomeViewModel.kt | 16 +++-- .../keymapper/keymaps/KeyMapListState.kt | 1 + .../keymapper/keymaps/KeyMapListViewModel.kt | 30 ++++++++-- .../onboarding/OnboardingTapTarget.kt | 29 +++++++++ .../keymapper/onboarding/OnboardingUseCase.kt | 50 +++++++++++----- .../util/ui/compose/KeyMapperTapTarget.kt | 59 +++++++++++++++++++ app/src/main/res/values/strings.xml | 11 +++- 9 files changed, 192 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingTapTarget.kt create mode 100644 app/src/main/java/io/github/sds100/keymapper/util/ui/compose/KeyMapperTapTarget.kt diff --git a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt index f4befce2c4..ae6e68d740 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt @@ -59,8 +59,6 @@ object Keys { val lastInstalledVersionCodeBackground = intPreferencesKey("last_installed_version_accessibility_service") - val shownQuickStartGuideHint = booleanPreferencesKey("tap_target_quick_start_guide") - val fingerprintGesturesAvailable = booleanPreferencesKey("fingerprint_gestures_available") @@ -96,4 +94,19 @@ object Keys { val neverShowNotificationPermissionAlert = booleanPreferencesKey("key_never_show_notification_permission_alert") + + val shownTapTargetCreateKeyMap = + booleanPreferencesKey("key_shown_tap_target_create_key_map") + + val shownTapTargetRecordTrigger = + booleanPreferencesKey("key_shown_tap_target_record_trigger") + + val shownTapTargetChooseAction = + booleanPreferencesKey("key_shown_tap_target_choose_action") + + val shownTapTargetChooseConstraint = + booleanPreferencesKey("key_shown_tap_target_choose_constraint") + + val skipTapTargetTutorial = + booleanPreferencesKey("key_skip_tap_target_tutorial") } diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt index bc1685d533..c7f448d788 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeKeyMapListScreen.kt @@ -11,13 +11,9 @@ import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawing @@ -29,14 +25,11 @@ import androidx.compose.material.icons.automirrored.outlined.ArrowForward import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.FlashlightOn import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable @@ -60,7 +53,6 @@ import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.canopas.lib.showcase.IntroShowcase -import com.canopas.lib.showcase.component.ShowcaseStyle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.backup.ImportExportState import io.github.sds100.keymapper.backup.RestoreType @@ -70,6 +62,7 @@ import io.github.sds100.keymapper.groups.GroupListItemModel import io.github.sds100.keymapper.keymaps.KeyMapAppBarState import io.github.sds100.keymapper.keymaps.KeyMapList import io.github.sds100.keymapper.keymaps.KeyMapListViewModel +import io.github.sds100.keymapper.onboarding.OnboardingTapTarget import io.github.sds100.keymapper.sorting.SortBottomSheet import io.github.sds100.keymapper.system.files.FileUtils import io.github.sds100.keymapper.trigger.DpadTriggerSetupBottomSheet @@ -82,6 +75,8 @@ import io.github.sds100.keymapper.util.drawable import io.github.sds100.keymapper.util.ui.compose.CollapsableFloatingActionButton import io.github.sds100.keymapper.util.ui.compose.ComposeChipModel import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo +import io.github.sds100.keymapper.util.ui.compose.KeyMapperTapTarget +import io.github.sds100.keymapper.util.ui.compose.keyMapperShowcaseStyle import io.github.sds100.keymapper.util.ui.compose.openUriSafe @OptIn(ExperimentalMaterial3Api::class) @@ -161,8 +156,10 @@ fun HomeKeyMapListScreen( var keyMapListBottomPadding by remember { mutableStateOf(100.dp) } IntroShowcase( - showIntroShowCase = true, - onShowCaseCompleted = {}, + showIntroShowCase = state.showCreateKeyMapTapTarget, + onShowCaseCompleted = { + viewModel.onTapTargetsCompleted() + }, dismissOnClickOutside = true, ) { HomeKeyMapListScreen( @@ -180,40 +177,12 @@ fun HomeKeyMapListScreen( .windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.End)) .introShowCaseTarget( index = 0, - style = ShowcaseStyle( - backgroundColor = MaterialTheme.colorScheme.primary, - backgroundAlpha = 0.99f, - ), + style = keyMapperShowcaseStyle(), ) { - Column { - Text( - text = "Create your first key map!", - color = MaterialTheme.colorScheme.onPrimary, - style = MaterialTheme.typography.titleLarge, - ) - - Spacer(Modifier.height(16.dp)) - Text( - text = "A key map is a rule to tell your device what to do when a button is pressed.", - color = MaterialTheme.colorScheme.onPrimary, - style = MaterialTheme.typography.bodyMedium, - ) - Spacer(Modifier.height(16.dp)) - - OutlinedButton( - onClick = {}, - // Set border color to white - border = BorderStroke( - width = 1.dp, - color = MaterialTheme.colorScheme.onPrimary, - ), - ) { - Text( - text = "Skip tutorial", - color = MaterialTheme.colorScheme.onPrimary, - ) - } - } + KeyMapperTapTarget( + OnboardingTapTarget.CREATE_KEY_MAP, + onSkipClick = viewModel::onSkipTapTargetClick, + ) }, onClick = viewModel::onNewKeyMapClick, showText = viewModel.showFabText, diff --git a/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt index 873d9e2ec3..6044fad6cf 100644 --- a/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/home/HomeViewModel.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -81,6 +80,7 @@ class HomeViewModel( pauseKeyMaps, backupRestore, showInputMethodPickerUseCase, + onboarding, ) } @@ -93,15 +93,13 @@ class HomeViewModel( } init { - - combine( - onboarding.showWhatsNew, - onboarding.showQuickStartGuideHint, - ) { showWhatsNew, showQuickStartGuideHint -> - if (showWhatsNew) { - showWhatsNewDialog() + viewModelScope.launch { + onboarding.showWhatsNew.collect { showWhatsNew -> + if (showWhatsNew) { + showWhatsNewDialog() + } } - }.launchIn(viewModelScope) + } viewModelScope.launch { if (setupGuiKeyboard.isInstalled.first() && !setupGuiKeyboard.isCompatibleVersion.first()) { diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListState.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListState.kt index e47b9f684e..44f6139227 100644 --- a/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListState.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListState.kt @@ -6,4 +6,5 @@ import io.github.sds100.keymapper.util.State data class KeyMapListState( val appBarState: KeyMapAppBarState, val listItems: State>, + val showCreateKeyMapTapTarget: Boolean = false, ) diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt index 9e23b1b183..f5da0eb0dd 100644 --- a/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt @@ -17,6 +17,8 @@ import io.github.sds100.keymapper.groups.GroupListItemModel import io.github.sds100.keymapper.home.HomeWarningListItem import io.github.sds100.keymapper.home.SelectedKeyMapsEnabled import io.github.sds100.keymapper.home.ShowHomeScreenAlertsUseCase +import io.github.sds100.keymapper.onboarding.OnboardingTapTarget +import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.sorting.SortKeyMapsUseCase import io.github.sds100.keymapper.sorting.SortViewModel import io.github.sds100.keymapper.system.accessibility.ServiceState @@ -83,7 +85,7 @@ class KeyMapListViewModel( private val pauseKeyMaps: PauseKeyMapsUseCase, private val backupRestore: BackupRestoreMappingsUseCase, private val showInputMethodPickerUseCase: ShowInputMethodPickerUseCase, - + private val onboarding: OnboardingUseCase, ) : PopupViewModel by PopupViewModelImpl(), ResourceProvider by resourceProvider, NavigationViewModel by NavigationViewModelImpl() { @@ -111,6 +113,7 @@ class KeyMapListViewModel( isPaused = false, ), listItems = State.Loading, + showCreateKeyMapTapTarget = false, ) private val _state: MutableStateFlow = MutableStateFlow(initialState) val state = _state.asStateFlow() @@ -316,20 +319,29 @@ class KeyMapListViewModel( } } + val showCreateKeyMapTapTarget = combine( + onboarding.showTapTarget(OnboardingTapTarget.CREATE_KEY_MAP), + onboarding.showWhatsNew, + ) { showTapTarget, showWhatsNew -> + // Only show the tap target if whats new is not showing. + showTapTarget && !showWhatsNew + } + coroutineScope.launch { combine( listItemStateFlow, appBarStateFlow, - ) { listState, appBarState -> - Pair(listState, appBarState) - }.collectLatest { (listState, appBarState) -> + showCreateKeyMapTapTarget, + ) { listState, appBarState, showCreateKeyMapTapTarget -> + Triple(listState, appBarState, showCreateKeyMapTapTarget) + }.collectLatest { (listState, appBarState, showCreateKeyMapTapTarget) -> listState.ifIsData { list -> if (list.isNotEmpty()) { showFabText = false } } - _state.value = KeyMapListState(appBarState, listState) + _state.value = KeyMapListState(appBarState, listState, showCreateKeyMapTapTarget) } } @@ -901,4 +913,12 @@ class KeyMapListViewModel( } } } + + fun onTapTargetsCompleted() { + onboarding.dismissedTapTarget(OnboardingTapTarget.CREATE_KEY_MAP) + } + + fun onSkipTapTargetClick() { + onboarding.skipTapTargetOnboarding() + } } diff --git a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingTapTarget.kt b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingTapTarget.kt new file mode 100644 index 0000000000..b949fc0c12 --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingTapTarget.kt @@ -0,0 +1,29 @@ +package io.github.sds100.keymapper.onboarding + +import androidx.annotation.StringRes +import io.github.sds100.keymapper.R + +enum class OnboardingTapTarget( + @StringRes val titleRes: Int, + @StringRes val messageRes: Int, +) { + CREATE_KEY_MAP( + titleRes = R.string.tap_target_create_key_map_title, + messageRes = R.string.tap_target_create_key_map_message, + ), + + RECORD_TRIGGER( + titleRes = R.string.tap_target_record_trigger_title, + messageRes = R.string.tap_target_record_trigger_message, + ), + + CHOOSE_ACTION( + titleRes = R.string.tap_target_choose_action_title, + messageRes = R.string.tap_target_choose_action_message, + ), + + CHOOSE_CONSTRAINT( + titleRes = R.string.tap_target_choose_constraint_title, + messageRes = R.string.tap_target_choose_constraint_message, + ), +} diff --git a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt index 73b90c0118..9b0b1b205f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt @@ -1,5 +1,6 @@ package io.github.sds100.keymapper.onboarding +import androidx.datastore.preferences.core.Preferences import io.github.sds100.keymapper.Constants import io.github.sds100.keymapper.actions.ActionData import io.github.sds100.keymapper.actions.canUseImeToPerform @@ -101,18 +102,6 @@ class OnboardingUseCaseImpl( set(Keys.lastInstalledVersionCodeBackground, Constants.VERSION_CODE) } - override val showQuickStartGuideHint: Flow = get(Keys.shownQuickStartGuideHint).map { - if (it == null) { - true - } else { - !it - } - } - - override fun shownQuickStartGuideHint() { - preferences.set(Keys.shownQuickStartGuideHint, true) - } - override fun isTvDevice(): Boolean = leanbackAdapter.isTvDevice() override val promptForShizukuPermission: Flow = combine( @@ -149,6 +138,36 @@ class OnboardingUseCaseImpl( override fun viewedAdvancedTriggers() { set(Keys.viewedAdvancedTriggers, true) } + + override fun showTapTarget(tapTarget: OnboardingTapTarget): Flow { + val key = getTapTargetKey(tapTarget) + return combine( + preferences.get(key).map { it != true }, + preferences.get(Keys.skipTapTargetTutorial) + .map { it ?: false }, + ) { showTapTarget, skipTapTarget -> + showTapTarget && !skipTapTarget + } + } + + override fun dismissedTapTarget(tapTarget: OnboardingTapTarget) { + val key = getTapTargetKey(tapTarget) + preferences.set(key, true) + } + + private fun getTapTargetKey(tapTarget: OnboardingTapTarget): Preferences.Key { + val key = when (tapTarget) { + OnboardingTapTarget.CREATE_KEY_MAP -> Keys.shownTapTargetCreateKeyMap + OnboardingTapTarget.RECORD_TRIGGER -> Keys.shownTapTargetRecordTrigger + OnboardingTapTarget.CHOOSE_ACTION -> Keys.shownTapTargetChooseAction + OnboardingTapTarget.CHOOSE_CONSTRAINT -> Keys.shownTapTargetChooseConstraint + } + return key + } + + override fun skipTapTargetOnboarding() { + preferences.set(Keys.skipTapTargetTutorial, true) + } } interface OnboardingUseCase { @@ -181,9 +200,6 @@ interface OnboardingUseCase { fun showedWhatsNew() fun getWhatsNewText(): String - val showQuickStartGuideHint: Flow - fun shownQuickStartGuideHint() - val promptForShizukuPermission: Flow val showShizukuAppIntroSlide: Boolean @@ -193,4 +209,8 @@ interface OnboardingUseCase { val hasViewedAdvancedTriggers: Flow fun viewedAdvancedTriggers() + + fun showTapTarget(tapTarget: OnboardingTapTarget): Flow + fun dismissedTapTarget(tapTarget: OnboardingTapTarget) + fun skipTapTargetOnboarding() } diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/KeyMapperTapTarget.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/KeyMapperTapTarget.kt new file mode 100644 index 0000000000..e2f85f905a --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/KeyMapperTapTarget.kt @@ -0,0 +1,59 @@ +package io.github.sds100.keymapper.util.ui.compose + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.canopas.lib.showcase.component.ShowcaseStyle +import io.github.sds100.keymapper.R +import io.github.sds100.keymapper.onboarding.OnboardingTapTarget + +@Composable +fun KeyMapperTapTarget(tapTarget: OnboardingTapTarget, onSkipClick: () -> Unit) { + val textColor = MaterialTheme.colorScheme.onPrimary + Column { + Text( + text = stringResource(tapTarget.titleRes), + color = textColor, + style = MaterialTheme.typography.titleLarge, + ) + + Spacer(Modifier.height(16.dp)) + + Text( + text = stringResource(tapTarget.messageRes), + color = textColor, + style = MaterialTheme.typography.bodyMedium, + ) + + Spacer(Modifier.height(16.dp)) + + OutlinedButton( + onClick = onSkipClick, + border = BorderStroke( + width = 1.dp, + color = textColor, + ), + ) { + Text( + text = stringResource(R.string.tap_target_skip_tutorial_button), + color = textColor, + ) + } + } +} + +@Composable +fun keyMapperShowcaseStyle(): ShowcaseStyle { + return ShowcaseStyle( + backgroundColor = MaterialTheme.colorScheme.primary, + backgroundAlpha = 0.99f, + ) +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a209c603bf..395d3bc1c4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1223,8 +1223,15 @@ - Quick Start Guide - Check out the Quick Start Guide if you are stuck. + Skip tutorial + Create your first key map! + A key map is a rule to tell your device what to do when a button is pressed. + Record a trigger + Press the physical buttons you want to change while recording. + Choose an action + An action is what should happen when you press the trigger. + Choose a constraint (optional) + If you want the key map to only work in certain situations. E.g an app is open. From bb2181ce3d126c254e8c212c7c11ce458da8beb7 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 19:51:28 +0200 Subject: [PATCH 77/95] #1466 feat: add tap targets to record a trigger and view advanced triggers --- .../io/github/sds100/keymapper/UseCases.kt | 2 + .../io/github/sds100/keymapper/data/Keys.kt | 3 + .../sds100/keymapper/data/db/dao/KeyMapDao.kt | 3 + .../data/repositories/RoomKeyMapRepository.kt | 4 + .../keymapper/keymaps/KeyMapRepository.kt | 1 + .../onboarding/OnboardingTapTarget.kt | 5 ++ .../keymapper/onboarding/OnboardingUseCase.kt | 44 ++++++++-- .../trigger/BaseConfigTriggerViewModel.kt | 43 ++++++++-- .../trigger/RecordTriggerButtonRow.kt | 82 +++++++++++-------- .../sds100/keymapper/trigger/TriggerScreen.kt | 34 ++++++-- .../util/ui/compose/KeyMapperTapTarget.kt | 33 ++++---- app/src/main/res/values/strings.xml | 5 +- 12 files changed, 186 insertions(+), 73 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt index dcc8d93507..d57d0bf37b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt +++ b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt @@ -90,6 +90,8 @@ object UseCases { ServiceLocator.shizukuAdapter(ctx), ServiceLocator.permissionAdapter(ctx), ServiceLocator.packageManagerAdapter(ctx), + ServiceLocator.purchasingManager(ctx), + ServiceLocator.roomKeyMapRepository(ctx), ) fun createKeymapShortcut(ctx: Context) = CreateKeyMapShortcutUseCaseImpl( diff --git a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt index ae6e68d740..dfd38baa43 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt @@ -101,6 +101,9 @@ object Keys { val shownTapTargetRecordTrigger = booleanPreferencesKey("key_shown_tap_target_record_trigger") + val shownTapTargetAdvancedTriggers = + booleanPreferencesKey("key_shown_tap_target_advanced_triggers") + val shownTapTargetChooseAction = booleanPreferencesKey("key_shown_tap_target_choose_action") diff --git a/app/src/main/java/io/github/sds100/keymapper/data/db/dao/KeyMapDao.kt b/app/src/main/java/io/github/sds100/keymapper/data/db/dao/KeyMapDao.kt index b5f7481efc..c72b0a0f78 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/db/dao/KeyMapDao.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/db/dao/KeyMapDao.kt @@ -70,4 +70,7 @@ interface KeyMapDao { @Update(onConflict = OnConflictStrategy.ABORT) suspend fun update(vararg keyMap: KeyMapEntity) + + @Query("SELECT COUNT(*) FROM $TABLE_NAME") + fun count(): Flow } diff --git a/app/src/main/java/io/github/sds100/keymapper/data/repositories/RoomKeyMapRepository.kt b/app/src/main/java/io/github/sds100/keymapper/data/repositories/RoomKeyMapRepository.kt index fb2ff2759d..7ad76800a7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/repositories/RoomKeyMapRepository.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/repositories/RoomKeyMapRepository.kt @@ -122,6 +122,10 @@ class RoomKeyMapRepository( } } + override fun count(): Flow { + return keyMapDao.count().flowOn(dispatchers.io()) + } + private suspend fun migrateFingerprintMaps() = withContext(dispatchers.io()) { val entities = fingerprintMapDao.getAll().first() diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapRepository.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapRepository.kt index b79810dbd7..2c16070364 100644 --- a/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapRepository.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapRepository.kt @@ -17,6 +17,7 @@ interface KeyMapRepository { suspend fun get(uid: String): KeyMapEntity? fun delete(vararg uid: String) suspend fun deleteAll() + fun count(): Flow fun duplicate(vararg uid: String) fun enableById(vararg uid: String) diff --git a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingTapTarget.kt b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingTapTarget.kt index b949fc0c12..b24f1b41db 100644 --- a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingTapTarget.kt +++ b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingTapTarget.kt @@ -17,6 +17,11 @@ enum class OnboardingTapTarget( messageRes = R.string.tap_target_record_trigger_message, ), + ADVANCED_TRIGGERS( + titleRes = R.string.tap_target_advanced_triggers_title, + messageRes = R.string.tap_target_advanced_triggers_message, + ), + CHOOSE_ACTION( titleRes = R.string.tap_target_choose_action_title, messageRes = R.string.tap_target_choose_action_message, diff --git a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt index 9b0b1b205f..40c8179c57 100644 --- a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt @@ -7,6 +7,9 @@ import io.github.sds100.keymapper.actions.canUseImeToPerform import io.github.sds100.keymapper.actions.canUseShizukuToPerform import io.github.sds100.keymapper.data.Keys import io.github.sds100.keymapper.data.repositories.PreferenceRepository +import io.github.sds100.keymapper.keymaps.KeyMapRepository +import io.github.sds100.keymapper.purchasing.ProductId +import io.github.sds100.keymapper.purchasing.PurchasingManager import io.github.sds100.keymapper.shizuku.ShizukuAdapter import io.github.sds100.keymapper.shizuku.ShizukuUtils import io.github.sds100.keymapper.system.apps.PackageManagerAdapter @@ -16,9 +19,13 @@ import io.github.sds100.keymapper.system.leanback.LeanbackAdapter import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.system.permissions.PermissionAdapter import io.github.sds100.keymapper.util.PrefDelegate +import io.github.sds100.keymapper.util.Result +import io.github.sds100.keymapper.util.State import io.github.sds100.keymapper.util.VersionHelper +import io.github.sds100.keymapper.util.handle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @@ -32,6 +39,8 @@ class OnboardingUseCaseImpl( private val shizukuAdapter: ShizukuAdapter, private val permissionAdapter: PermissionAdapter, private val packageManagerAdapter: PackageManagerAdapter, + private val purchasingManager: PurchasingManager, + private val keyMapRepository: KeyMapRepository, ) : PreferenceRepository by preferences, OnboardingUseCase { @@ -140,13 +149,33 @@ class OnboardingUseCaseImpl( } override fun showTapTarget(tapTarget: OnboardingTapTarget): Flow { - val key = getTapTargetKey(tapTarget) - return combine( - preferences.get(key).map { it != true }, - preferences.get(Keys.skipTapTargetTutorial) - .map { it ?: false }, - ) { showTapTarget, skipTapTarget -> - showTapTarget && !skipTapTarget + val shownKey = getTapTargetKey(tapTarget) + + if (tapTarget == OnboardingTapTarget.ADVANCED_TRIGGERS) { + return combine( + preferences.get(shownKey).map { it ?: false }, + purchasingManager.purchases.filterIsInstance>>>(), + keyMapRepository.count(), + ) { isShown, purchases, keyMapCount -> + // Only show the tap target for advanced triggers if it has not already been shown + // and the user has not made any purchases. Also, the user must have saved a key map + // as a heuristic for they actually interacted with Key Mapper a bit before + // pushing them to paid features. + !isShown && + keyMapCount > 0 && + purchases.data.handle( + onSuccess = { it.isEmpty() }, + onError = { false }, + ) + } + } else { + return combine( + preferences.get(shownKey).map { it ?: false }, + preferences.get(Keys.skipTapTargetTutorial).map { it ?: false }, + keyMapRepository.count(), + ) { isShown, skipTapTarget, keyMapCount -> + !isShown && !skipTapTarget && keyMapCount == 0 + } } } @@ -159,6 +188,7 @@ class OnboardingUseCaseImpl( val key = when (tapTarget) { OnboardingTapTarget.CREATE_KEY_MAP -> Keys.shownTapTargetCreateKeyMap OnboardingTapTarget.RECORD_TRIGGER -> Keys.shownTapTargetRecordTrigger + OnboardingTapTarget.ADVANCED_TRIGGERS -> Keys.shownTapTargetAdvancedTriggers OnboardingTapTarget.CHOOSE_ACTION -> Keys.shownTapTargetChooseAction OnboardingTapTarget.CHOOSE_CONSTRAINT -> Keys.shownTapTargetChooseConstraint } diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt index 87148ac2f9..73b0cbc1a9 100644 --- a/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt @@ -18,6 +18,7 @@ import io.github.sds100.keymapper.keymaps.FingerprintGestureType import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCase import io.github.sds100.keymapper.keymaps.KeyMap import io.github.sds100.keymapper.keymaps.ShortcutModel +import io.github.sds100.keymapper.onboarding.OnboardingTapTarget import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.purchasing.ProductId import io.github.sds100.keymapper.purchasing.PurchasingManager @@ -43,6 +44,7 @@ import io.github.sds100.keymapper.util.ui.ViewModelHelper import io.github.sds100.keymapper.util.ui.compose.ComposeIconInfo import io.github.sds100.keymapper.util.ui.showPopup import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -174,6 +176,13 @@ abstract class BaseConfigTriggerViewModel( private var isRecordingCompletionUserInitiated: Boolean = false init { + val showTapTargetsPairFlow: Flow> = combine( + onboarding.showTapTarget(OnboardingTapTarget.RECORD_TRIGGER), + onboarding.showTapTarget(OnboardingTapTarget.ADVANCED_TRIGGERS), + ) { recordTriggerTapTarget, advancedTriggersTapTarget -> + Pair(recordTriggerTapTarget, advancedTriggersTapTarget) + } + // IMPORTANT! Do not flow on another thread because this causes the drag and drop // animations to be more janky. combine( @@ -181,15 +190,16 @@ abstract class BaseConfigTriggerViewModel( config.keyMap, displayKeyMap.showDeviceDescriptors, triggerKeyShortcuts, - onboarding.hasViewedAdvancedTriggers, - ) { triggerErrorSnapshot, keyMap, showDeviceDescriptors, shortcuts, viewedAdvancedTriggers -> + showTapTargetsPairFlow, + ) { triggerErrorSnapshot, keyMap, showDeviceDescriptors, shortcuts, showTapTargetsPair -> _state.update { buildUiState( keyMap, showDeviceDescriptors, shortcuts, triggerErrorSnapshot, - viewedAdvancedTriggers, + showTapTargetsPair.first, + showTapTargetsPair.second, ) } }.launchIn(coroutineScope) @@ -255,7 +265,8 @@ abstract class BaseConfigTriggerViewModel( showDeviceDescriptors: Boolean, triggerKeyShortcuts: Set>, triggerErrorSnapshot: TriggerErrorSnapshot, - viewedAdvancedTriggers: Boolean, + showRecordTriggerTapTarget: Boolean, + showAdvancedTriggersTapTarget: Boolean, ): State { return keyMapState.mapData { keyMap -> val trigger = keyMap.trigger @@ -263,7 +274,8 @@ abstract class BaseConfigTriggerViewModel( if (trigger.keys.isEmpty()) { return@mapData ConfigTriggerState.Empty( triggerKeyShortcuts, - !viewedAdvancedTriggers, + showRecordTriggerTapTarget = showRecordTriggerTapTarget, + showAdvancedTriggersTapTarget = showAdvancedTriggersTapTarget, ) } @@ -316,7 +328,7 @@ abstract class BaseConfigTriggerViewModel( triggerModeButtonsVisible = triggerModeButtonsVisible, checkedTriggerMode = trigger.mode, shortcuts = triggerKeyShortcuts, - showNewBadge = !viewedAdvancedTriggers, + showAdvancedTriggersTapTarget = showAdvancedTriggersTapTarget, ) } } @@ -772,15 +784,28 @@ abstract class BaseConfigTriggerViewModel( fun onNeverShowNoKeysRecordedClick() { onboarding.neverShowNoKeysRecordedBottomSheet() } + + fun onRecordTriggerTapTargetCompleted() { + onboarding.dismissedTapTarget(OnboardingTapTarget.RECORD_TRIGGER) + } + + fun onSkipTapTargetClick() { + onboarding.skipTapTargetOnboarding() + } + + fun onAdvancedTriggersTapTargetCompleted() { + onboarding.dismissedTapTarget(OnboardingTapTarget.ADVANCED_TRIGGERS) + } } sealed class ConfigTriggerState { abstract val shortcuts: Set> - abstract val showNewBadge: Boolean + abstract val showAdvancedTriggersTapTarget: Boolean data class Empty( override val shortcuts: Set> = emptySet(), - override val showNewBadge: Boolean, + val showRecordTriggerTapTarget: Boolean = false, + override val showAdvancedTriggersTapTarget: Boolean = false, ) : ConfigTriggerState() data class Loaded( @@ -792,7 +817,7 @@ sealed class ConfigTriggerState { val triggerModeButtonsEnabled: Boolean = false, val triggerModeButtonsVisible: Boolean = false, override val shortcuts: Set> = emptySet(), - override val showNewBadge: Boolean, + override val showAdvancedTriggersTapTarget: Boolean = false, ) : ConfigTriggerState() } diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt index 3646b41d7a..0f3a847eb9 100644 --- a/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt @@ -4,13 +4,10 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.material3.Badge import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -21,9 +18,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.canopas.lib.showcase.IntroShowcase import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.compose.LocalCustomColorsPalette +import io.github.sds100.keymapper.onboarding.OnboardingTapTarget +import io.github.sds100.keymapper.util.ui.compose.KeyMapperTapTarget +import io.github.sds100.keymapper.util.ui.compose.keyMapperShowcaseStyle @Composable fun RecordTriggerButtonRow( @@ -31,25 +32,53 @@ fun RecordTriggerButtonRow( onRecordTriggerClick: () -> Unit = {}, recordTriggerState: RecordTriggerState, onAdvancedTriggersClick: () -> Unit = {}, - showNewBadge: Boolean, + showRecordTriggerTapTarget: Boolean = false, + onRecordTriggerTapTargetCompleted: () -> Unit = {}, + onSkipTapTarget: () -> Unit = {}, + showAdvancedTriggerTapTarget: Boolean = false, + onAdvancedTriggerTapTargetCompleted: () -> Unit = {}, ) { Row(modifier) { - RecordTriggerButton( - modifier = Modifier - .weight(1f) - .align(Alignment.Bottom), - recordTriggerState, - onClick = onRecordTriggerClick, - ) + IntroShowcase( + showIntroShowCase = showRecordTriggerTapTarget, + onShowCaseCompleted = onRecordTriggerTapTargetCompleted, + dismissOnClickOutside = true, + ) { + RecordTriggerButton( + modifier = Modifier + .weight(1f) + .align(Alignment.Bottom) + .introShowCaseTarget(0, style = keyMapperShowcaseStyle()) { + KeyMapperTapTarget( + OnboardingTapTarget.RECORD_TRIGGER, + onSkipClick = onSkipTapTarget, + ) + }, + recordTriggerState, + onClick = onRecordTriggerClick, + ) + } Spacer(modifier = Modifier.width(8.dp)) - AdvancedTriggersButton( - modifier = Modifier.weight(1f), - isEnabled = recordTriggerState !is RecordTriggerState.CountingDown, - onClick = onAdvancedTriggersClick, - showNewBadge = showNewBadge, - ) + IntroShowcase( + showIntroShowCase = showAdvancedTriggerTapTarget, + onShowCaseCompleted = onAdvancedTriggerTapTargetCompleted, + dismissOnClickOutside = true, + ) { + AdvancedTriggersButton( + modifier = Modifier + .weight(1f) + .introShowCaseTarget(0, style = keyMapperShowcaseStyle()) { + KeyMapperTapTarget( + OnboardingTapTarget.ADVANCED_TRIGGERS, + showSkipButton = false, + ) + }, + isEnabled = recordTriggerState !is RecordTriggerState.CountingDown, + onClick = onAdvancedTriggersClick, + ) + } } } @@ -89,7 +118,6 @@ private fun RecordTriggerButton( private fun AdvancedTriggersButton( modifier: Modifier, isEnabled: Boolean, - showNewBadge: Boolean, onClick: () -> Unit, ) { Box(modifier = modifier) { @@ -106,22 +134,6 @@ private fun AdvancedTriggersButton( overflow = TextOverflow.Ellipsis, ) } - - if (showNewBadge) { - Badge( - modifier = Modifier - .align(Alignment.TopEnd) - .height(36.dp), - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer, - ) { - Text( - modifier = Modifier.padding(horizontal = 8.dp), - text = stringResource(R.string.button_advanced_triggers_badge), - style = MaterialTheme.typography.labelLarge, - ) - } - } } } @@ -133,7 +145,6 @@ private fun PreviewCountingDown() { RecordTriggerButtonRow( modifier = Modifier.fillMaxWidth(), recordTriggerState = RecordTriggerState.CountingDown(3), - showNewBadge = true, ) } } @@ -147,7 +158,6 @@ private fun PreviewStopped() { RecordTriggerButtonRow( modifier = Modifier.fillMaxWidth(), recordTriggerState = RecordTriggerState.Idle, - showNewBadge = false, ) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt index 153bb38a87..3e6ee25543 100644 --- a/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt @@ -132,6 +132,9 @@ fun TriggerScreen(modifier: Modifier = Modifier, viewModel: ConfigTriggerViewMod onMoveTriggerKey = viewModel::onMoveTriggerKey, onFixErrorClick = viewModel::onTriggerErrorClick, onClickShortcut = viewModel::onClickTriggerKeyShortcut, + onRecordTriggerTapTargetCompleted = viewModel::onRecordTriggerTapTargetCompleted, + onSkipTapTarget = viewModel::onSkipTapTargetClick, + onAdvancedTriggerTapTargetCompleted = viewModel::onAdvancedTriggersTapTargetCompleted, ) } else { TriggerScreenVertical( @@ -148,6 +151,9 @@ fun TriggerScreen(modifier: Modifier = Modifier, viewModel: ConfigTriggerViewMod onMoveTriggerKey = viewModel::onMoveTriggerKey, onFixErrorClick = viewModel::onTriggerErrorClick, onClickShortcut = viewModel::onClickTriggerKeyShortcut, + onRecordTriggerTapTargetCompleted = viewModel::onRecordTriggerTapTargetCompleted, + onSkipTapTarget = viewModel::onSkipTapTargetClick, + onAdvancedTriggerTapTargetCompleted = viewModel::onAdvancedTriggersTapTargetCompleted, ) } } @@ -183,6 +189,9 @@ private fun TriggerScreenVertical( onMoveTriggerKey: (fromIndex: Int, toIndex: Int) -> Unit = { _, _ -> }, onFixErrorClick: (TriggerError) -> Unit = {}, onClickShortcut: (TriggerKeyShortcut) -> Unit = {}, + onRecordTriggerTapTargetCompleted: () -> Unit = {}, + onSkipTapTarget: () -> Unit = {}, + onAdvancedTriggerTapTargetCompleted: () -> Unit = {}, ) { Surface(modifier = modifier) { Column { @@ -264,7 +273,12 @@ private fun TriggerScreenVertical( onRecordTriggerClick = onRecordTriggerClick, recordTriggerState = recordTriggerState, onAdvancedTriggersClick = onAdvancedTriggersClick, - showNewBadge = configState.showNewBadge, + showRecordTriggerTapTarget = (configState as? ConfigTriggerState.Empty)?.showRecordTriggerTapTarget + ?: false, + onRecordTriggerTapTargetCompleted = onRecordTriggerTapTargetCompleted, + onSkipTapTarget = onSkipTapTarget, + showAdvancedTriggerTapTarget = configState.showAdvancedTriggersTapTarget, + onAdvancedTriggerTapTargetCompleted = onAdvancedTriggerTapTargetCompleted, ) } } @@ -285,6 +299,9 @@ private fun TriggerScreenHorizontal( onMoveTriggerKey: (fromIndex: Int, toIndex: Int) -> Unit = { _, _ -> }, onFixErrorClick: (TriggerError) -> Unit = {}, onClickShortcut: (TriggerKeyShortcut) -> Unit = {}, + onRecordTriggerTapTargetCompleted: () -> Unit = {}, + onSkipTapTarget: () -> Unit = {}, + onAdvancedTriggerTapTargetCompleted: () -> Unit = {}, ) { Surface(modifier = modifier) { when (configState) { @@ -328,7 +345,11 @@ private fun TriggerScreenHorizontal( onRecordTriggerClick = onRecordTriggerClick, recordTriggerState = recordTriggerState, onAdvancedTriggersClick = onAdvancedTriggersClick, - showNewBadge = configState.showNewBadge, + showRecordTriggerTapTarget = (configState as? ConfigTriggerState.Empty)?.showRecordTriggerTapTarget + ?: false, + onRecordTriggerTapTargetCompleted = onRecordTriggerTapTargetCompleted, + onSkipTapTarget = onSkipTapTarget, + showAdvancedTriggerTapTarget = configState.showAdvancedTriggersTapTarget, ) } } @@ -384,7 +405,11 @@ private fun TriggerScreenHorizontal( onRecordTriggerClick = onRecordTriggerClick, recordTriggerState = recordTriggerState, onAdvancedTriggersClick = onAdvancedTriggersClick, - showNewBadge = configState.showNewBadge, + showRecordTriggerTapTarget = false, + onRecordTriggerTapTargetCompleted = onRecordTriggerTapTargetCompleted, + onSkipTapTarget = onSkipTapTarget, + showAdvancedTriggerTapTarget = configState.showAdvancedTriggersTapTarget, + onAdvancedTriggerTapTargetCompleted = onAdvancedTriggerTapTargetCompleted, ) } } @@ -586,7 +611,6 @@ private val previewState = ConfigTriggerState.Loaded( data = TriggerKeyShortcut.FINGERPRINT_GESTURE, ), ), - showNewBadge = true, ) @Preview(device = Devices.PIXEL) @@ -613,7 +637,6 @@ private fun VerticalEmptyPreview() { data = TriggerKeyShortcut.FINGERPRINT_GESTURE, ), ), - showNewBadge = true, ), recordTriggerState = RecordTriggerState.Idle, ) @@ -644,7 +667,6 @@ private fun HorizontalEmptyPreview() { data = TriggerKeyShortcut.FINGERPRINT_GESTURE, ), ), - showNewBadge = true, ), recordTriggerState = RecordTriggerState.Idle, diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/KeyMapperTapTarget.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/KeyMapperTapTarget.kt index e2f85f905a..71313f5f0b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/KeyMapperTapTarget.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/KeyMapperTapTarget.kt @@ -16,7 +16,11 @@ import io.github.sds100.keymapper.R import io.github.sds100.keymapper.onboarding.OnboardingTapTarget @Composable -fun KeyMapperTapTarget(tapTarget: OnboardingTapTarget, onSkipClick: () -> Unit) { +fun KeyMapperTapTarget( + tapTarget: OnboardingTapTarget, + showSkipButton: Boolean = true, + onSkipClick: () -> Unit = {}, +) { val textColor = MaterialTheme.colorScheme.onPrimary Column { Text( @@ -33,19 +37,20 @@ fun KeyMapperTapTarget(tapTarget: OnboardingTapTarget, onSkipClick: () -> Unit) style = MaterialTheme.typography.bodyMedium, ) - Spacer(Modifier.height(16.dp)) - - OutlinedButton( - onClick = onSkipClick, - border = BorderStroke( - width = 1.dp, - color = textColor, - ), - ) { - Text( - text = stringResource(R.string.tap_target_skip_tutorial_button), - color = textColor, - ) + if (showSkipButton) { + Spacer(Modifier.height(16.dp)) + OutlinedButton( + onClick = onSkipClick, + border = BorderStroke( + width = 1.dp, + color = textColor, + ), + ) { + Text( + text = stringResource(R.string.tap_target_skip_tutorial_button), + color = textColor, + ) + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 395d3bc1c4..0f09a74cc3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1227,7 +1227,10 @@ Create your first key map! A key map is a rule to tell your device what to do when a button is pressed. Record a trigger - Press the physical buttons you want to change while recording. + Tap record, and then press the physical buttons you want to change. + Love Key Mapper? ❤️ + For an affordable price, make powerful key maps with on-screen floating buttons and support the developers 👨‍💻. + Choose an action An action is what should happen when you press the trigger. Choose a constraint (optional) From 0a6240a358c0727fdc92368fea34141a35812bf9 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 20:06:12 +0200 Subject: [PATCH 78/95] #1466 update CREDITS.md --- CREDITS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CREDITS.md b/CREDITS.md index 2327ac943f..8d7b22f638 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -8,11 +8,11 @@ Many thanks to... - [App Mockup] for their screenshot utility. - Airbnb for their [Epoxy](https://github.com/airbnb/epoxy) RecyclerView. They made having multiple itemview types and dragging and dropping super easy! - @[Jake Wharton](https://github.com/JakeWharton) for his [Timber](https://github.com/JakeWharton/timber) logging library. - - @[AppIntro](https://github.com/AppIntro) for their [AppIntro](https://github.com/AppIntro/AppIntro) library. - @[srikanth-lingala](https://github.com/srikanth-lingala) for their Java zip file [library](https://github.com/srikanth-lingala/zip4j). - @[anggrayudi](https://github.com/anggrayudi) for their [Simple Storage](https://github.com/anggrayudi/SimpleStorage) library that makes working with Scoped Storage and the Storage Access Framework on Android much easier. - @[MFlisar](https://github.com/MFlisar) for their [drag and select](https://github.com/MFlisar/DragSelectRecyclerView) library. - @[RikkaApps](https://github.com/RikkaApps) for Shizuku! It is amazing. + - @[canopas](https://github.com/canopas) for their Jetpack Compose Tap Target library https://github.com/canopas/compose-intro-showcase. [salomonbrys]: https://github.com/salomonbrys [Kotson]: https://github.com/salomonbrys/Kotson From e6421f8148b044a59f4b73c930007b6b6b088a26 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 20:26:23 +0200 Subject: [PATCH 79/95] #1466 feat: show tap targets for actions and constraints --- .../keymapper/keymaps/ConfigKeyMapScreen.kt | 72 +++++++++++++++---- .../keymaps/ConfigKeyMapViewModel.kt | 32 +++++++++ .../keymapper/keymaps/KeyMapListViewModel.kt | 2 +- .../keymapper/onboarding/OnboardingUseCase.kt | 40 +++++++++-- .../trigger/BaseConfigTriggerViewModel.kt | 4 +- app/src/main/res/values/strings.xml | 4 +- 6 files changed, 130 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt index c7d7ace279..f70a0998e8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapScreen.kt @@ -54,11 +54,15 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.canopas.lib.showcase.IntroShowcase import io.github.sds100.keymapper.R import io.github.sds100.keymapper.actions.ActionsScreen import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.constraints.ConstraintsScreen +import io.github.sds100.keymapper.onboarding.OnboardingTapTarget import io.github.sds100.keymapper.trigger.TriggerScreen +import io.github.sds100.keymapper.util.ui.compose.KeyMapperTapTarget +import io.github.sds100.keymapper.util.ui.compose.keyMapperShowcaseStyle import io.github.sds100.keymapper.util.ui.compose.openUriSafe import kotlinx.coroutines.launch @@ -69,6 +73,9 @@ fun ConfigKeyMapScreen( navigateBack: () -> Unit, ) { val isKeyMapEnabled by viewModel.isEnabled.collectAsStateWithLifecycle() + val showActionTapTarget by viewModel.showActionsTapTarget.collectAsStateWithLifecycle() + val showConstraintTapTarget by viewModel.showConstraintsTapTarget.collectAsStateWithLifecycle() + val snackbarHostState = remember { SnackbarHostState() } var showBackDialog by rememberSaveable { mutableStateOf(false) } @@ -118,6 +125,11 @@ fun ConfigKeyMapScreen( navigateBack() }, snackbarHostState = snackbarHostState, + showActionTapTarget = showActionTapTarget, + onActionTapTargetCompleted = viewModel::onActionTapTargetCompleted, + showConstraintTapTarget = showConstraintTapTarget, + onConstraintTapTargetCompleted = viewModel::onConstraintTapTargetCompleted, + onSkipTutorialClick = viewModel::onSkipTutorialClick, ) } @@ -134,6 +146,11 @@ private fun ConfigKeyMapScreen( onBackClick: () -> Unit = {}, onDoneClick: () -> Unit = {}, snackbarHostState: SnackbarHostState = SnackbarHostState(), + showActionTapTarget: Boolean = false, + onActionTapTargetCompleted: () -> Unit = {}, + showConstraintTapTarget: Boolean = false, + onConstraintTapTargetCompleted: () -> Unit = {}, + onSkipTutorialClick: () -> Unit = {}, ) { val scope = rememberCoroutineScope() val triggerHelpUrl = stringResource(R.string.url_trigger_guide) @@ -187,22 +204,49 @@ private fun ConfigKeyMapScreen( @Composable fun Tabs() { for ((index, tab) in tabs.withIndex()) { - Tab( - selected = pagerState.targetPage == index, - text = { - Text( - text = getTabTitle(tab), - maxLines = 1, - ) - }, - onClick = { - scope.launch { - pagerState.animateScrollToPage( - tabs.indexOf(tab), + val tapTarget: OnboardingTapTarget? = when { + showActionTapTarget && tab == ConfigKeyMapTab.ACTIONS -> OnboardingTapTarget.CHOOSE_ACTION + showConstraintTapTarget && (tab == ConfigKeyMapTab.CONSTRAINTS || tab == ConfigKeyMapTab.CONSTRAINTS_AND_OPTIONS) -> OnboardingTapTarget.CHOOSE_CONSTRAINT + else -> null + } + + IntroShowcase( + showIntroShowCase = tapTarget != null, + onShowCaseCompleted = if (tapTarget == OnboardingTapTarget.CHOOSE_ACTION) onActionTapTargetCompleted else onConstraintTapTargetCompleted, + dismissOnClickOutside = true, + ) { + var tabModifier: Modifier = Modifier + + if (tapTarget != null) { + tabModifier = tabModifier.introShowCaseTarget( + index = 0, + style = keyMapperShowcaseStyle(), + ) { + KeyMapperTapTarget( + tapTarget = tapTarget, + onSkipClick = onSkipTutorialClick, ) } - }, - ) + } + + Tab( + modifier = tabModifier, + selected = pagerState.targetPage == index, + text = { + Text( + text = getTabTitle(tab), + maxLines = 1, + ) + }, + onClick = { + scope.launch { + pagerState.animateScrollToPage( + tabs.indexOf(tab), + ) + } + }, + ) + } } } diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt index 2ecc1417df..f86292155e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/ConfigKeyMapViewModel.kt @@ -8,6 +8,7 @@ import io.github.sds100.keymapper.actions.ConfigActionsViewModel import io.github.sds100.keymapper.actions.CreateActionUseCase import io.github.sds100.keymapper.actions.TestActionUseCase import io.github.sds100.keymapper.constraints.ConfigConstraintsViewModel +import io.github.sds100.keymapper.onboarding.OnboardingTapTarget import io.github.sds100.keymapper.onboarding.OnboardingUseCase import io.github.sds100.keymapper.purchasing.PurchasingManager import io.github.sds100.keymapper.trigger.ConfigTriggerViewModel @@ -21,6 +22,7 @@ import io.github.sds100.keymapper.util.ifIsData import io.github.sds100.keymapper.util.ui.ResourceProvider import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -85,6 +87,24 @@ class ConfigKeyMapViewModel( val isKeyMapEdited: Boolean get() = config.isEdited + val showActionsTapTarget: StateFlow = + combine( + onboarding.showTapTarget(OnboardingTapTarget.CHOOSE_ACTION), + config.keyMap, + ) { showTapTarget, keyMapState -> + // Show the choose action tap target if they have recorded a key. + showTapTarget && keyMapState.dataOrNull()?.trigger?.keys?.isNotEmpty() ?: false + }.stateIn(viewModelScope, SharingStarted.Lazily, false) + + val showConstraintsTapTarget: StateFlow = + combine( + onboarding.showTapTarget(OnboardingTapTarget.CHOOSE_CONSTRAINT), + config.keyMap, + ) { showTapTarget, keyMapState -> + // Show the choose constraint tap target if they have added an action. + showTapTarget && keyMapState.dataOrNull()?.actionList?.isNotEmpty() ?: false + }.stateIn(viewModelScope, SharingStarted.Lazily, false) + fun save() = config.save() fun saveState(outState: Bundle) { @@ -117,6 +137,18 @@ class ConfigKeyMapViewModel( config.setEnabled(enabled) } + fun onActionTapTargetCompleted() { + onboarding.completedTapTarget(OnboardingTapTarget.CHOOSE_ACTION) + } + + fun onConstraintTapTargetCompleted() { + onboarding.completedTapTarget(OnboardingTapTarget.CHOOSE_CONSTRAINT) + } + + fun onSkipTutorialClick() { + onboarding.skipTapTargetOnboarding() + } + class Factory( private val config: ConfigKeyMapUseCase, private val testAction: TestActionUseCase, diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt index f5da0eb0dd..9ce55cb086 100644 --- a/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/KeyMapListViewModel.kt @@ -915,7 +915,7 @@ class KeyMapListViewModel( } fun onTapTargetsCompleted() { - onboarding.dismissedTapTarget(OnboardingTapTarget.CREATE_KEY_MAP) + onboarding.completedTapTarget(OnboardingTapTarget.CREATE_KEY_MAP) } fun onSkipTapTargetClick() { diff --git a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt index 40c8179c57..eb8cfbfc10 100644 --- a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt @@ -6,6 +6,7 @@ import io.github.sds100.keymapper.actions.ActionData import io.github.sds100.keymapper.actions.canUseImeToPerform import io.github.sds100.keymapper.actions.canUseShizukuToPerform import io.github.sds100.keymapper.data.Keys +import io.github.sds100.keymapper.data.entities.KeyMapEntity import io.github.sds100.keymapper.data.repositories.PreferenceRepository import io.github.sds100.keymapper.keymaps.KeyMapRepository import io.github.sds100.keymapper.purchasing.ProductId @@ -172,14 +173,14 @@ class OnboardingUseCaseImpl( return combine( preferences.get(shownKey).map { it ?: false }, preferences.get(Keys.skipTapTargetTutorial).map { it ?: false }, - keyMapRepository.count(), - ) { isShown, skipTapTarget, keyMapCount -> - !isShown && !skipTapTarget && keyMapCount == 0 + keyMapRepository.keyMapList.filterIsInstance>>(), + ) { isShown, skipTapTarget, keyMapList -> + showTutorialTapTarget(tapTarget, isShown, skipTapTarget, keyMapList.data) } } } - override fun dismissedTapTarget(tapTarget: OnboardingTapTarget) { + override fun completedTapTarget(tapTarget: OnboardingTapTarget) { val key = getTapTargetKey(tapTarget) preferences.set(key, true) } @@ -195,6 +196,35 @@ class OnboardingUseCaseImpl( return key } + /** + * Whether to show a tutorial tap target. This will try to determine whether the user + * has interacted with each feature before by checking the key maps they've created (if any). + * E.g if they have no key maps with actions then show a tap target highlighting the action tab + * when they create a key map. + */ + private fun showTutorialTapTarget( + tapTarget: OnboardingTapTarget, + isShown: Boolean, + skipTutorial: Boolean, + keyMapList: List, + ): Boolean { + if (isShown) { + return false + } + + if (skipTutorial) { + return false + } + + return when (tapTarget) { + OnboardingTapTarget.CREATE_KEY_MAP -> keyMapList.isEmpty() + OnboardingTapTarget.RECORD_TRIGGER -> keyMapList.all { it.trigger.keys.isEmpty() } + OnboardingTapTarget.CHOOSE_ACTION -> keyMapList.all { it.actionList.isEmpty() } + OnboardingTapTarget.CHOOSE_CONSTRAINT -> keyMapList.all { it.constraintList.isEmpty() } + else -> throw IllegalArgumentException("This is not a tutorial tap target: $tapTarget") + } + } + override fun skipTapTargetOnboarding() { preferences.set(Keys.skipTapTargetTutorial, true) } @@ -241,6 +271,6 @@ interface OnboardingUseCase { fun viewedAdvancedTriggers() fun showTapTarget(tapTarget: OnboardingTapTarget): Flow - fun dismissedTapTarget(tapTarget: OnboardingTapTarget) + fun completedTapTarget(tapTarget: OnboardingTapTarget) fun skipTapTargetOnboarding() } diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt index 73b0cbc1a9..21a500ecdc 100644 --- a/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/BaseConfigTriggerViewModel.kt @@ -786,7 +786,7 @@ abstract class BaseConfigTriggerViewModel( } fun onRecordTriggerTapTargetCompleted() { - onboarding.dismissedTapTarget(OnboardingTapTarget.RECORD_TRIGGER) + onboarding.completedTapTarget(OnboardingTapTarget.RECORD_TRIGGER) } fun onSkipTapTargetClick() { @@ -794,7 +794,7 @@ abstract class BaseConfigTriggerViewModel( } fun onAdvancedTriggersTapTargetCompleted() { - onboarding.dismissedTapTarget(OnboardingTapTarget.ADVANCED_TRIGGERS) + onboarding.completedTapTarget(OnboardingTapTarget.ADVANCED_TRIGGERS) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f09a74cc3..53471403e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1229,12 +1229,12 @@ Record a trigger Tap record, and then press the physical buttons you want to change. Love Key Mapper? ❤️ - For an affordable price, make powerful key maps with on-screen floating buttons and support the developers 👨‍💻. + For an affordable price, make powerful key maps with on-screen floating buttons and support the development of more great features 👨‍💻. Choose an action An action is what should happen when you press the trigger. Choose a constraint (optional) - If you want the key map to only work in certain situations. E.g an app is open. + If you want the key map to only work in certain situations, for example, when an app is open. From 529ad08a5187b9d1149652c53141535048f6a12a Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 20:28:35 +0200 Subject: [PATCH 80/95] #1466 feat: only show the advanced triggers tap target if they made at least one valid key map with a trigger and action --- .../sds100/keymapper/onboarding/OnboardingUseCase.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt index eb8cfbfc10..3bd5309ca0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/onboarding/OnboardingUseCase.kt @@ -156,14 +156,14 @@ class OnboardingUseCaseImpl( return combine( preferences.get(shownKey).map { it ?: false }, purchasingManager.purchases.filterIsInstance>>>(), - keyMapRepository.count(), - ) { isShown, purchases, keyMapCount -> + keyMapRepository.keyMapList.filterIsInstance>>(), + ) { isShown, purchases, keyMapList -> // Only show the tap target for advanced triggers if it has not already been shown - // and the user has not made any purchases. Also, the user must have saved a key map + // and the user has not made any purchases. Also, the user must have saved a working key map // as a heuristic for they actually interacted with Key Mapper a bit before // pushing them to paid features. !isShown && - keyMapCount > 0 && + keyMapList.data.any { it.trigger.keys.isNotEmpty() && it.actionList.isNotEmpty() } && purchases.data.handle( onSuccess = { it.isEmpty() }, onError = { false }, From 81b5bc0b7025073b017b5433c9b5a3b91944f399 Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 20:31:11 +0200 Subject: [PATCH 81/95] #1466 upgrade tap target library --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index be0a881da5..6e1166be24 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -196,7 +196,7 @@ dependencies { proImplementation "com.airbnb.android:lottie-compose:6.6.3" implementation("com.squareup.okhttp3:okhttp:4.12.0") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") - implementation 'com.canopas.intro-showcase-view:introshowcaseview:2.0.1' + implementation 'com.canopas.intro-showcase-view:introshowcaseview:2.0.2' // splitties implementation "com.louiscad.splitties:splitties-bitflags:$splitties_version" From a141fad4a0709c1fb796ccaad4bc4104d739111f Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 21:29:38 +0200 Subject: [PATCH 82/95] #1466 remove unused turkish translation --- app/src/main/res/values-tr/strings.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 2e9e7dfb88..917c56edc4 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -969,10 +969,6 @@ Kaydedilmiş ses dosyalarını ayarlardan silebilirsiniz. Java programlama dilinde geçerli Kısa sayıların virgülle ayrılmış bir listesi. Örn. 3242,12354 Intent bayrakları bit bayrakları olarak saklanır. Bu bayraklar Intent\'in nasıl işleneceğini değiştirir. Bir Etkinlik Intent\'i için bu alan boş bırakılırsa, Key Mapper varsayılan olarak FLAG_ACTIVITY_NEW_TASK kullanır. Daha fazla bilgi için Android geliştirici belgelerini görmek üzere \'dokümanlar\'a dokunun. - - Hızlı Başlangıç Kılavuzu - Eğer takılırsanız Hızlı Başlangıç Kılavuzu\'na göz atın. - GitHub Web Sitesi From 162ce471447bb2c5544ef220bafaa94618ed4d2a Mon Sep 17 00:00:00 2001 From: sds100 Date: Wed, 14 May 2025 22:41:51 +0200 Subject: [PATCH 83/95] chore: bump version code --- app/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/version.properties b/app/version.properties index 2752506b43..c53820e201 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.2 -VERSION_CODE=119 +VERSION_CODE=121 VERSION_NUM=0 \ No newline at end of file From 23fe601cabea453b474b172ac6147258451ab21c Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 12:46:19 +0200 Subject: [PATCH 84/95] #1701 feat: improve the ordering of actions and categories --- CHANGELOG.md | 8 ++++++++ .../sds100/keymapper/actions/ActionCategory.kt | 3 ++- .../github/sds100/keymapper/actions/ActionId.kt | 15 +++++++-------- .../sds100/keymapper/actions/ActionUtils.kt | 16 +++++++++------- .../keymapper/actions/ChooseActionViewModel.kt | 5 +++-- app/src/main/res/values/strings.xml | 3 ++- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d029abf02a..ed8298b821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ #### TO BE RELEASED +## Added + +- #1466 show onboarding when creating a key map for the first time + +## Changed + +- #1701 improve the order of the actions and categories + ## Bug fixes - #1686 (more fixes) do not show some screens behind system bars on the left/right side of the device. diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionCategory.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionCategory.kt index 6de92e797a..c6dfed43a6 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionCategory.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionCategory.kt @@ -6,7 +6,7 @@ package io.github.sds100.keymapper.actions enum class ActionCategory { APPS, INPUT, - CAMERA_SOUND, + FLASHLIGHT, CONNECTIVITY, CONTENT, NAVIGATION, @@ -17,4 +17,5 @@ enum class ActionCategory { INTERFACE, TELEPHONY, NOTIFICATIONS, + SPECIAL, } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt index 5b93ee03a7..80840e9949 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionId.kt @@ -8,14 +8,14 @@ enum class ActionId { APP_SHORTCUT, KEY_CODE, KEY_EVENT, + TEXT, TAP_SCREEN, SWIPE_SCREEN, PINCH_SCREEN, - TEXT, URL, + HTTP_REQUEST, INTENT, PHONE_CALL, - SOUND, INTERACT_UI_ELEMENT, TOGGLE_WIFI, @@ -65,6 +65,7 @@ enum class ActionId { TOGGLE_QUICK_SETTINGS, COLLAPSE_STATUS_BAR, + SOUND, PAUSE_MEDIA, PAUSE_MEDIA_PACKAGE, PLAY_MEDIA, @@ -102,15 +103,15 @@ enum class ActionId { DISABLE_NFC, TOGGLE_NFC, + TEXT_CUT, + TEXT_COPY, + TEXT_PASTE, MOVE_CURSOR_TO_END, + SELECT_WORD_AT_CURSOR, TOGGLE_KEYBOARD, SHOW_KEYBOARD, HIDE_KEYBOARD, SHOW_KEYBOARD_PICKER, - TEXT_CUT, - TEXT_COPY, - TEXT_PASTE, - SELECT_WORD_AT_CURSOR, SWITCH_KEYBOARD, @@ -136,6 +137,4 @@ enum class ActionId { ANSWER_PHONE_CALL, END_PHONE_CALL, DEVICE_CONTROLS, - - HTTP_REQUEST, } diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt index 7cff7aeb8d..f7833f70f0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ActionUtils.kt @@ -98,17 +98,17 @@ object ActionUtils { ActionCategory.KEYBOARD -> R.string.action_cat_keyboard ActionCategory.APPS -> R.string.action_cat_apps ActionCategory.INPUT -> R.string.action_cat_input - ActionCategory.CAMERA_SOUND -> R.string.action_cat_camera_sound + ActionCategory.FLASHLIGHT -> R.string.action_cat_flashlight ActionCategory.CONNECTIVITY -> R.string.action_cat_connectivity ActionCategory.CONTENT -> R.string.action_cat_content ActionCategory.INTERFACE -> R.string.action_cat_interface ActionCategory.TELEPHONY -> R.string.action_cat_telephony ActionCategory.DISPLAY -> R.string.action_cat_display ActionCategory.NOTIFICATIONS -> R.string.action_cat_notifications + ActionCategory.SPECIAL -> R.string.action_cat_special } fun getCategory(id: ActionId): ActionCategory = when (id) { - ActionId.CONSUME_KEY_EVENT -> ActionCategory.INPUT ActionId.KEY_CODE -> ActionCategory.INPUT ActionId.KEY_EVENT -> ActionCategory.INPUT ActionId.TAP_SCREEN -> ActionCategory.INPUT @@ -171,6 +171,7 @@ object ActionUtils { ActionId.TOGGLE_QUICK_SETTINGS -> ActionCategory.NAVIGATION ActionId.COLLAPSE_STATUS_BAR -> ActionCategory.NAVIGATION + ActionId.SOUND -> ActionCategory.MEDIA ActionId.PAUSE_MEDIA -> ActionCategory.MEDIA ActionId.PAUSE_MEDIA_PACKAGE -> ActionCategory.MEDIA ActionId.PLAY_MEDIA -> ActionCategory.MEDIA @@ -199,11 +200,10 @@ object ActionUtils { ActionId.GO_LAST_APP -> ActionCategory.NAVIGATION ActionId.OPEN_MENU -> ActionCategory.NAVIGATION - ActionId.TOGGLE_FLASHLIGHT -> ActionCategory.CAMERA_SOUND - ActionId.ENABLE_FLASHLIGHT -> ActionCategory.CAMERA_SOUND - ActionId.DISABLE_FLASHLIGHT -> ActionCategory.CAMERA_SOUND - ActionId.CHANGE_FLASHLIGHT_STRENGTH -> ActionCategory.CAMERA_SOUND - ActionId.SOUND -> ActionCategory.CAMERA_SOUND + ActionId.TOGGLE_FLASHLIGHT -> ActionCategory.FLASHLIGHT + ActionId.ENABLE_FLASHLIGHT -> ActionCategory.FLASHLIGHT + ActionId.DISABLE_FLASHLIGHT -> ActionCategory.FLASHLIGHT + ActionId.CHANGE_FLASHLIGHT_STRENGTH -> ActionCategory.FLASHLIGHT ActionId.ENABLE_NFC -> ActionCategory.CONNECTIVITY ActionId.DISABLE_NFC -> ActionCategory.CONNECTIVITY @@ -242,6 +242,8 @@ object ActionUtils { ActionId.DEVICE_CONTROLS -> ActionCategory.APPS ActionId.INTERACT_UI_ELEMENT -> ActionCategory.APPS + + ActionId.CONSUME_KEY_EVENT -> ActionCategory.SPECIAL } @StringRes diff --git a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionViewModel.kt index ad7f075949..10ea36c237 100644 --- a/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/actions/ChooseActionViewModel.kt @@ -46,17 +46,18 @@ class ChooseActionViewModel( private val CATEGORY_ORDER = arrayOf( ActionCategory.INPUT, ActionCategory.APPS, + ActionCategory.FLASHLIGHT, + ActionCategory.CONTENT, ActionCategory.NAVIGATION, ActionCategory.VOLUME, ActionCategory.DISPLAY, ActionCategory.MEDIA, ActionCategory.INTERFACE, - ActionCategory.CONTENT, ActionCategory.KEYBOARD, ActionCategory.CONNECTIVITY, ActionCategory.TELEPHONY, - ActionCategory.CAMERA_SOUND, ActionCategory.NOTIFICATIONS, + ActionCategory.SPECIAL, ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 53471403e0..faa0fcb0c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1170,13 +1170,14 @@ Keyboard Apps Input - Camera & Sound + Flashlight Connectivity Content Interface Phone Display Notifications + Special From 4bcbaa797ab2d773688d0cddf85a928c001dda11 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 13:48:47 +0200 Subject: [PATCH 85/95] #1706 fix: optimize the trigger screen for smaller screens --- CHANGELOG.md | 1 + .../trigger/AdvancedTriggersBottomSheet.kt | 130 ++++++++++-------- .../trigger/RecordTriggerButtonRow.kt | 64 ++++++--- .../sds100/keymapper/trigger/TriggerScreen.kt | 53 +++++-- .../util/ui/compose/RadioButtonText.kt | 7 +- 5 files changed, 163 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed8298b821..fdded4f9d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ## Bug fixes - #1686 (more fixes) do not show some screens behind system bars on the left/right side of the device. +- #1701 optimize the trigger screen for smaller screens so elements are less cut off. ## [3.1.1](https://github.com/sds100/KeyMapper/releases/tag/v3.1.1) diff --git a/app/src/free/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt b/app/src/free/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt index 50319d11b2..dc1abbe1d9 100644 --- a/app/src/free/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt +++ b/app/src/free/java/io/github/sds100/keymapper/trigger/AdvancedTriggersBottomSheet.kt @@ -1,11 +1,15 @@ package io.github.sds100.keymapper.trigger import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme @@ -16,6 +20,7 @@ import androidx.compose.material3.SheetValue.Expanded import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalUriHandler @@ -59,70 +64,75 @@ private fun AdvancedTriggersBottomSheet( // Hide drag handle because other bottom sheets don't have it dragHandle = {}, ) { - Spacer(modifier = Modifier.height(8.dp)) - - Text( - modifier = Modifier.fillMaxWidth(), - textAlign = TextAlign.Center, - text = stringResource(R.string.advanced_triggers_sheet_title), - style = MaterialTheme.typography.headlineMedium, - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - text = stringResource(R.string.advanced_triggers_sheet_text), - ) - - Spacer(modifier = Modifier.height(8.dp)) - - Text( - modifier = Modifier - .padding(horizontal = 16.dp) - .fillMaxWidth(), - text = stringResource(R.string.purchasing_not_implemented_bottom_sheet_text), - fontStyle = FontStyle.Italic, - ) - - Spacer(modifier = Modifier.height(8.dp)) - - val uriHandler = LocalUriHandler.current - val googlePlayUrl = stringResource(R.string.url_play_store_listing) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - OutlinedButton( - modifier = Modifier, - onClick = { - scope.launch { - sheetState.hide() - onDismissRequest() - } - }, + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + Spacer(modifier = Modifier.height(8.dp)) + + Text( + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + text = stringResource(R.string.advanced_triggers_sheet_title), + style = MaterialTheme.typography.headlineMedium, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = stringResource(R.string.advanced_triggers_sheet_text), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), + text = stringResource(R.string.purchasing_not_implemented_bottom_sheet_text), + fontStyle = FontStyle.Italic, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + val uriHandler = LocalUriHandler.current + val googlePlayUrl = stringResource(R.string.url_play_store_listing) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { - Text(stringResource(R.string.neg_cancel)) + OutlinedButton( + modifier = Modifier, + onClick = { + scope.launch { + sheetState.hide() + onDismissRequest() + } + }, + ) { + Text(stringResource(R.string.neg_cancel)) + } + + Spacer(modifier = Modifier.width(8.dp)) + + FilledTonalButton( + modifier = Modifier, + onClick = { + scope.launch { + uriHandler.openUri(googlePlayUrl) + } + }, + ) { + Text(stringResource(R.string.purchasing_download_key_mapper_from_google_play)) + } } - FilledTonalButton( - modifier = Modifier, - onClick = { - scope.launch { - uriHandler.openUri(googlePlayUrl) - } - }, - ) { - Text(stringResource(R.string.purchasing_download_key_mapper_from_google_play)) - } + Spacer(Modifier.height(16.dp)) } - - Spacer(Modifier.height(16.dp)) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt index 0f3a847eb9..eead8308f3 100644 --- a/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/RecordTriggerButtonRow.kt @@ -1,16 +1,16 @@ package io.github.sds100.keymapper.trigger -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.text.BasicText +import androidx.compose.foundation.text.TextAutoSize import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -18,6 +18,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.canopas.lib.showcase.IntroShowcase import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme @@ -38,7 +39,7 @@ fun RecordTriggerButtonRow( showAdvancedTriggerTapTarget: Boolean = false, onAdvancedTriggerTapTargetCompleted: () -> Unit = {}, ) { - Row(modifier) { + Row(modifier, verticalAlignment = Alignment.CenterVertically) { IntroShowcase( showIntroShowCase = showRecordTriggerTapTarget, onShowCaseCompleted = onRecordTriggerTapTargetCompleted, @@ -47,7 +48,6 @@ fun RecordTriggerButtonRow( RecordTriggerButton( modifier = Modifier .weight(1f) - .align(Alignment.Bottom) .introShowCaseTarget(0, style = keyMapperShowcaseStyle()) { KeyMapperTapTarget( OnboardingTapTarget.RECORD_TRIGGER, @@ -106,9 +106,15 @@ private fun RecordTriggerButton( onClick = onClick, colors = colors, ) { - Text( + BasicText( text = text, - maxLines = 2, + maxLines = 1, + autoSize = TextAutoSize.StepBased( + minFontSize = 5.sp, + maxFontSize = MaterialTheme.typography.labelLarge.fontSize, + ), + style = MaterialTheme.typography.labelLarge, + color = { colors.contentColor }, overflow = TextOverflow.Ellipsis, ) } @@ -120,20 +126,23 @@ private fun AdvancedTriggersButton( isEnabled: Boolean, onClick: () -> Unit, ) { - Box(modifier = modifier) { - OutlinedButton( - modifier = Modifier - .fillMaxWidth() - .padding(top = 20.dp), - enabled = isEnabled, - onClick = onClick, - ) { - Text( - text = stringResource(R.string.button_advanced_triggers), - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) - } + OutlinedButton( + modifier = modifier, + enabled = isEnabled, + onClick = onClick, + ) { + val color = ButtonDefaults.textButtonColors().contentColor + BasicText( + text = stringResource(R.string.button_advanced_triggers), + maxLines = 1, + autoSize = TextAutoSize.StepBased( + minFontSize = 5.sp, + maxFontSize = MaterialTheme.typography.labelLarge.fontSize, + ), + style = MaterialTheme.typography.labelLarge, + color = { color }, + overflow = TextOverflow.Ellipsis, + ) } } @@ -162,3 +171,16 @@ private fun PreviewStopped() { } } } + +@Preview(widthDp = 300) +@Composable +private fun PreviewStoppedCompact() { + KeyMapperTheme { + Surface { + RecordTriggerButtonRow( + modifier = Modifier.fillMaxWidth(), + recordTriggerState = RecordTriggerState.Idle, + ) + } + } +} diff --git a/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt index 3e6ee25543..edbf72bd54 100644 --- a/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/trigger/TriggerScreen.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.core.layout.WindowHeightSizeClass +import androidx.window.core.layout.WindowWidthSizeClass import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme import io.github.sds100.keymapper.keymaps.ClickType @@ -167,6 +168,13 @@ private fun isHorizontalLayout(): Boolean { return windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT } +@Composable +private fun isVerticalCompactLayout(): Boolean { + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + + return windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT && windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT +} + @Composable private fun Loading(modifier: Modifier = Modifier) { Box(modifier = modifier, contentAlignment = Alignment.Center) { @@ -231,6 +239,7 @@ private fun TriggerScreenVertical( } is ConfigTriggerState.Loaded -> { + val isCompact = isVerticalCompactLayout() Spacer(Modifier.height(8.dp)) TriggerList( @@ -251,16 +260,26 @@ private fun TriggerScreenVertical( clickTypes = configState.clickTypeButtons, checkedClickType = configState.checkedClickType, onSelectClickType = onSelectClickType, + maxLines = if (isCompact) 1 else 2, ) } if (configState.triggerModeButtonsVisible) { + if (!isCompact) { + Text( + modifier = Modifier.padding(horizontal = 8.dp), + text = stringResource(R.string.press_dot_dot_dot), + style = MaterialTheme.typography.labelLarge, + ) + } + TriggerModeRadioGroup( modifier = Modifier.padding(horizontal = 8.dp), mode = configState.checkedTriggerMode, isEnabled = configState.triggerModeButtonsEnabled, onSelectParallelMode = onSelectParallelMode, onSelectSequenceMode = onSelectSequenceMode, + maxLines = if (isCompact) 1 else 2, ) } } @@ -389,6 +408,12 @@ private fun TriggerScreenHorizontal( ) } + Text( + modifier = Modifier.padding(horizontal = 8.dp), + text = stringResource(R.string.press_dot_dot_dot), + style = MaterialTheme.typography.labelLarge, + ) + if (configState.triggerModeButtonsVisible) { TriggerModeRadioGroup( modifier = Modifier.padding(horizontal = 8.dp), @@ -501,15 +526,17 @@ private fun ClickTypeRadioGroup( clickTypes: Set, checkedClickType: ClickType?, onSelectClickType: (ClickType) -> Unit, + maxLines: Int = 2, ) { Column(modifier = modifier) { - Row(modifier = Modifier.fillMaxWidth()) { + Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { if (clickTypes.contains(ClickType.SHORT_PRESS)) { RadioButtonText( modifier = Modifier.weight(1f), isSelected = checkedClickType == ClickType.SHORT_PRESS, text = stringResource(R.string.radio_button_short_press), onSelected = { onSelectClickType(ClickType.SHORT_PRESS) }, + maxLines = maxLines, ) } if (clickTypes.contains(ClickType.LONG_PRESS)) { @@ -518,6 +545,7 @@ private fun ClickTypeRadioGroup( isSelected = checkedClickType == ClickType.LONG_PRESS, text = stringResource(R.string.radio_button_long_press), onSelected = { onSelectClickType(ClickType.LONG_PRESS) }, + maxLines = maxLines, ) } if (clickTypes.contains(ClickType.DOUBLE_PRESS)) { @@ -526,6 +554,7 @@ private fun ClickTypeRadioGroup( isSelected = checkedClickType == ClickType.DOUBLE_PRESS, text = stringResource(R.string.radio_button_double_press), onSelected = { onSelectClickType(ClickType.DOUBLE_PRESS) }, + maxLines = maxLines, ) } } @@ -539,21 +568,17 @@ private fun TriggerModeRadioGroup( isEnabled: Boolean, onSelectParallelMode: () -> Unit, onSelectSequenceMode: () -> Unit, + maxLines: Int = 2, ) { Column(modifier = modifier) { - Text( - modifier = Modifier.padding(horizontal = 8.dp), - text = stringResource(R.string.press_dot_dot_dot), - style = MaterialTheme.typography.labelLarge, - ) - - Row(modifier = Modifier.fillMaxWidth()) { + Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { RadioButtonText( modifier = Modifier.weight(1f), isSelected = mode is TriggerMode.Parallel, isEnabled = isEnabled, text = stringResource(R.string.radio_button_parallel), onSelected = onSelectParallelMode, + maxLines = maxLines, ) RadioButtonText( modifier = Modifier.weight(1f), @@ -561,6 +586,7 @@ private fun TriggerModeRadioGroup( isEnabled = isEnabled, text = stringResource(R.string.radio_button_sequence), onSelected = onSelectSequenceMode, + maxLines = maxLines, ) } } @@ -624,6 +650,17 @@ private fun VerticalPreview() { } } +@Preview(heightDp = 400, widthDp = 300) +@Composable +private fun VerticalPreviewTiny() { + KeyMapperTheme { + TriggerScreenVertical( + configState = previewState, + recordTriggerState = RecordTriggerState.Idle, + ) + } +} + @Preview(device = Devices.PIXEL) @Composable private fun VerticalEmptyPreview() { diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/RadioButtonText.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/RadioButtonText.kt index 16c7d38414..84f4b07702 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/RadioButtonText.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/compose/RadioButtonText.kt @@ -21,13 +21,14 @@ fun RadioButtonText( text: String, isSelected: Boolean, isEnabled: Boolean = true, + maxLines: Int = 2, onSelected: () -> Unit, ) { Surface(modifier = modifier, shape = MaterialTheme.shapes.medium, color = Color.Transparent) { Row( modifier = Modifier .clickable(enabled = isEnabled, onClick = onSelected) - .padding(12.dp), + .padding(8.dp), verticalAlignment = Alignment.CenterVertically, ) { RadioButton( @@ -49,8 +50,8 @@ fun RadioButtonText( ), ) }, - maxLines = 2, - overflow = TextOverflow.Ellipsis, + maxLines = maxLines, + overflow = TextOverflow.Clip, ) } } From b046a28e4baa7185927f9bc11d26f1d021e6b3a2 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 14:53:10 +0200 Subject: [PATCH 86/95] #1699 fix: do not highlight a floating button as if it is pressed after triggering a key event action from it. --- CHANGELOG.md | 1 + app/version.properties | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdded4f9d9..be4801e662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - #1686 (more fixes) do not show some screens behind system bars on the left/right side of the device. - #1701 optimize the trigger screen for smaller screens so elements are less cut off. +- #1699 Do not highlight a floating button as if it is pressed after triggering a key event action from it. ## [3.1.1](https://github.com/sds100/KeyMapper/releases/tag/v3.1.1) diff --git a/app/version.properties b/app/version.properties index c53820e201..9702277f1d 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,3 +1,3 @@ VERSION_NAME=3.1.2 -VERSION_CODE=121 +VERSION_CODE=122 VERSION_NUM=0 \ No newline at end of file From 3bc3f48fba9ddb2e42f5087b7008eebeef0ea811 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 15:01:08 +0200 Subject: [PATCH 87/95] delete untranslated fastlane files --- .../metadata/android/ar/full_description.txt | 55 ------------------- .../metadata/android/ar/short_description.txt | 1 - fastlane/metadata/android/ar/title.txt | 1 - .../android/cs_CZ/full_description.txt | 55 ------------------- .../android/cs_CZ/short_description.txt | 1 - fastlane/metadata/android/cs_CZ/title.txt | 1 - .../android/de_DE/full_description.txt | 55 ------------------- .../android/de_DE/short_description.txt | 1 - fastlane/metadata/android/de_DE/title.txt | 1 - .../android/es_ES/full_description.txt | 55 ------------------- .../android/es_ES/short_description.txt | 1 - fastlane/metadata/android/es_ES/title.txt | 1 - .../android/fr_FR/full_description.txt | 55 ------------------- .../android/fr_FR/short_description.txt | 1 - fastlane/metadata/android/fr_FR/title.txt | 1 - .../android/hu_HU/full_description.txt | 55 ------------------- .../android/hu_HU/short_description.txt | 1 - fastlane/metadata/android/hu_HU/title.txt | 1 - .../android/id_ID/full_description.txt | 55 ------------------- .../android/id_ID/short_description.txt | 1 - fastlane/metadata/android/id_ID/title.txt | 1 - .../android/ka_GE/full_description.txt | 55 ------------------- .../android/ka_GE/short_description.txt | 1 - fastlane/metadata/android/ka_GE/title.txt | 1 - .../android/ko_KR/full_description.txt | 55 ------------------- .../android/ko_KR/short_description.txt | 1 - fastlane/metadata/android/ko_KR/title.txt | 1 - .../android/pl_PL/full_description.txt | 55 ------------------- .../android/pl_PL/short_description.txt | 1 - fastlane/metadata/android/pl_PL/title.txt | 1 - .../android/pt_BR/full_description.txt | 55 ------------------- .../android/pt_BR/short_description.txt | 1 - fastlane/metadata/android/pt_BR/title.txt | 1 - .../android/ru_RU/full_description.txt | 55 ------------------- .../android/ru_RU/short_description.txt | 1 - fastlane/metadata/android/ru_RU/title.txt | 1 - .../metadata/android/sk/full_description.txt | 55 ------------------- .../metadata/android/sk/short_description.txt | 1 - fastlane/metadata/android/sk/title.txt | 1 - .../metadata/android/uk/full_description.txt | 55 ------------------- .../metadata/android/uk/short_description.txt | 1 - fastlane/metadata/android/uk/title.txt | 1 - .../metadata/android/vi/full_description.txt | 55 ------------------- .../metadata/android/vi/short_description.txt | 1 - fastlane/metadata/android/vi/title.txt | 1 - .../android/zh_CN/full_description.txt | 55 ------------------- .../android/zh_CN/short_description.txt | 1 - fastlane/metadata/android/zh_CN/title.txt | 1 - .../android/zh_TW/full_description.txt | 55 ------------------- .../android/zh_TW/short_description.txt | 1 - fastlane/metadata/android/zh_TW/title.txt | 1 - 51 files changed, 969 deletions(-) delete mode 100644 fastlane/metadata/android/ar/full_description.txt delete mode 100644 fastlane/metadata/android/ar/short_description.txt delete mode 100644 fastlane/metadata/android/ar/title.txt delete mode 100644 fastlane/metadata/android/cs_CZ/full_description.txt delete mode 100644 fastlane/metadata/android/cs_CZ/short_description.txt delete mode 100644 fastlane/metadata/android/cs_CZ/title.txt delete mode 100644 fastlane/metadata/android/de_DE/full_description.txt delete mode 100644 fastlane/metadata/android/de_DE/short_description.txt delete mode 100644 fastlane/metadata/android/de_DE/title.txt delete mode 100644 fastlane/metadata/android/es_ES/full_description.txt delete mode 100644 fastlane/metadata/android/es_ES/short_description.txt delete mode 100644 fastlane/metadata/android/es_ES/title.txt delete mode 100644 fastlane/metadata/android/fr_FR/full_description.txt delete mode 100644 fastlane/metadata/android/fr_FR/short_description.txt delete mode 100644 fastlane/metadata/android/fr_FR/title.txt delete mode 100644 fastlane/metadata/android/hu_HU/full_description.txt delete mode 100644 fastlane/metadata/android/hu_HU/short_description.txt delete mode 100644 fastlane/metadata/android/hu_HU/title.txt delete mode 100644 fastlane/metadata/android/id_ID/full_description.txt delete mode 100644 fastlane/metadata/android/id_ID/short_description.txt delete mode 100644 fastlane/metadata/android/id_ID/title.txt delete mode 100644 fastlane/metadata/android/ka_GE/full_description.txt delete mode 100644 fastlane/metadata/android/ka_GE/short_description.txt delete mode 100644 fastlane/metadata/android/ka_GE/title.txt delete mode 100644 fastlane/metadata/android/ko_KR/full_description.txt delete mode 100644 fastlane/metadata/android/ko_KR/short_description.txt delete mode 100644 fastlane/metadata/android/ko_KR/title.txt delete mode 100644 fastlane/metadata/android/pl_PL/full_description.txt delete mode 100644 fastlane/metadata/android/pl_PL/short_description.txt delete mode 100644 fastlane/metadata/android/pl_PL/title.txt delete mode 100644 fastlane/metadata/android/pt_BR/full_description.txt delete mode 100644 fastlane/metadata/android/pt_BR/short_description.txt delete mode 100644 fastlane/metadata/android/pt_BR/title.txt delete mode 100644 fastlane/metadata/android/ru_RU/full_description.txt delete mode 100644 fastlane/metadata/android/ru_RU/short_description.txt delete mode 100644 fastlane/metadata/android/ru_RU/title.txt delete mode 100644 fastlane/metadata/android/sk/full_description.txt delete mode 100644 fastlane/metadata/android/sk/short_description.txt delete mode 100644 fastlane/metadata/android/sk/title.txt delete mode 100644 fastlane/metadata/android/uk/full_description.txt delete mode 100644 fastlane/metadata/android/uk/short_description.txt delete mode 100644 fastlane/metadata/android/uk/title.txt delete mode 100644 fastlane/metadata/android/vi/full_description.txt delete mode 100644 fastlane/metadata/android/vi/short_description.txt delete mode 100644 fastlane/metadata/android/vi/title.txt delete mode 100644 fastlane/metadata/android/zh_CN/full_description.txt delete mode 100644 fastlane/metadata/android/zh_CN/short_description.txt delete mode 100644 fastlane/metadata/android/zh_CN/title.txt delete mode 100644 fastlane/metadata/android/zh_TW/full_description.txt delete mode 100644 fastlane/metadata/android/zh_TW/short_description.txt delete mode 100644 fastlane/metadata/android/zh_TW/title.txt diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/ar/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ar/short_description.txt b/fastlane/metadata/android/ar/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ar/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ar/title.txt b/fastlane/metadata/android/ar/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/ar/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/full_description.txt b/fastlane/metadata/android/cs_CZ/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/cs_CZ/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/short_description.txt b/fastlane/metadata/android/cs_CZ/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/cs_CZ/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/cs_CZ/title.txt b/fastlane/metadata/android/cs_CZ/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/cs_CZ/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/full_description.txt b/fastlane/metadata/android/de_DE/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/de_DE/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/short_description.txt b/fastlane/metadata/android/de_DE/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/de_DE/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/de_DE/title.txt b/fastlane/metadata/android/de_DE/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/de_DE/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/full_description.txt b/fastlane/metadata/android/es_ES/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/es_ES/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/short_description.txt b/fastlane/metadata/android/es_ES/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/es_ES/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/es_ES/title.txt b/fastlane/metadata/android/es_ES/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/es_ES/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/full_description.txt b/fastlane/metadata/android/fr_FR/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/fr_FR/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/short_description.txt b/fastlane/metadata/android/fr_FR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/fr_FR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/fr_FR/title.txt b/fastlane/metadata/android/fr_FR/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/fr_FR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/full_description.txt b/fastlane/metadata/android/hu_HU/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/hu_HU/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/short_description.txt b/fastlane/metadata/android/hu_HU/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/hu_HU/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/hu_HU/title.txt b/fastlane/metadata/android/hu_HU/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/hu_HU/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/full_description.txt b/fastlane/metadata/android/id_ID/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/id_ID/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/short_description.txt b/fastlane/metadata/android/id_ID/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/id_ID/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/id_ID/title.txt b/fastlane/metadata/android/id_ID/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/id_ID/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/full_description.txt b/fastlane/metadata/android/ka_GE/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/ka_GE/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/short_description.txt b/fastlane/metadata/android/ka_GE/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ka_GE/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ka_GE/title.txt b/fastlane/metadata/android/ka_GE/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/ka_GE/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/full_description.txt b/fastlane/metadata/android/ko_KR/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/ko_KR/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/short_description.txt b/fastlane/metadata/android/ko_KR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ko_KR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ko_KR/title.txt b/fastlane/metadata/android/ko_KR/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/ko_KR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/full_description.txt b/fastlane/metadata/android/pl_PL/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/pl_PL/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/short_description.txt b/fastlane/metadata/android/pl_PL/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/pl_PL/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pl_PL/title.txt b/fastlane/metadata/android/pl_PL/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/pl_PL/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/full_description.txt b/fastlane/metadata/android/pt_BR/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/pt_BR/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/short_description.txt b/fastlane/metadata/android/pt_BR/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/pt_BR/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/pt_BR/title.txt b/fastlane/metadata/android/pt_BR/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/pt_BR/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/full_description.txt b/fastlane/metadata/android/ru_RU/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/ru_RU/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/short_description.txt b/fastlane/metadata/android/ru_RU/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/ru_RU/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/ru_RU/title.txt b/fastlane/metadata/android/ru_RU/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/ru_RU/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/sk/full_description.txt b/fastlane/metadata/android/sk/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/sk/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/sk/short_description.txt b/fastlane/metadata/android/sk/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/sk/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/sk/title.txt b/fastlane/metadata/android/sk/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/sk/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/uk/full_description.txt b/fastlane/metadata/android/uk/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/uk/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/uk/short_description.txt b/fastlane/metadata/android/uk/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/uk/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/uk/title.txt b/fastlane/metadata/android/uk/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/uk/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/vi/full_description.txt b/fastlane/metadata/android/vi/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/vi/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/vi/short_description.txt b/fastlane/metadata/android/vi/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/vi/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/vi/title.txt b/fastlane/metadata/android/vi/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/vi/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/full_description.txt b/fastlane/metadata/android/zh_CN/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/zh_CN/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/short_description.txt b/fastlane/metadata/android/zh_CN/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/zh_CN/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_CN/title.txt b/fastlane/metadata/android/zh_CN/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/zh_CN/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/full_description.txt b/fastlane/metadata/android/zh_TW/full_description.txt deleted file mode 100644 index 6863b48341..0000000000 --- a/fastlane/metadata/android/zh_TW/full_description.txt +++ /dev/null @@ -1,55 +0,0 @@ -Make custom macros on your keyboard or gamepad, make on-screen buttons in any app, and unlock new functionality from your volume buttons! - -Key Mapper supports a huge variety of buttons and keys*: - -- ALL your phone buttons (volume AND side key) -- Game controllers (D-pad, ABXY, and most others) -- Keyboards -- Headsets and headphones -- Fingerprint sensor - -Not enough keys? Design your own on-screen button layouts and remap those just like real keys! - - -What shortcuts can I make? --------------------------- - -With over 100 individual actions, the sky is the limit. -Build complex macros with screen taps and gestures, keyboard inputs, open apps, control media, and even send intents directly to other apps. - - -How much control do I have? ---------------------------- - -TRIGGERS: You decide how to trigger a key map. Long press, double press, press as many times as you like! Combine keys on different devices, and even include your on-screen buttons. - -ACTIONS: Design specific macros for what you want to do. Combine over 100 actions, and choose the delay between each one. Set repeating actions to automate and speed up slow tasks. - -CONSTRAINTS: You choose when key maps should run and when they shouldn't. Only need it in one specific app? Or when media is playing? On your lockscreen? Constrain your key maps for maximum control. - -* Most devices are already supported, with new devices being added over time. Let us know if it's not working for you and we can prioritize your device. - -Not currently supported: - - Mouse buttons - - Joysticks and triggers (LT,RT) on gamepads - - -Security and accessibility services ---------------------------- - -This app includes our Key Mapper Accessibility service that uses the Android Accessibility API to detect the app in focus and adapt key presses to user-defined key maps. It is also used to draw assistive Floating Button overlays on top of other apps. - -By accepting to run the accessibility service, the app will monitor key strokes while you're using your device. It will also emulate swipes and pinches if you are using those actions in the app. - -It will NOT collect any user data or connect to the internet to send any data anywhere. - -Our accessibility service is only triggered by the user when pressing a physical key on their device. It can be turned off any time by the user in the system accessibility settings. - -Come say hi in our Discord community! -www.keymapper.club - -See the code for yourself! (Open source) -code.keymapper.club - -Read the documentation: -docs.keymapper.club \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/short_description.txt b/fastlane/metadata/android/zh_TW/short_description.txt deleted file mode 100644 index ecaa2a662d..0000000000 --- a/fastlane/metadata/android/zh_TW/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file diff --git a/fastlane/metadata/android/zh_TW/title.txt b/fastlane/metadata/android/zh_TW/title.txt deleted file mode 100644 index 9810cafe1e..0000000000 --- a/fastlane/metadata/android/zh_TW/title.txt +++ /dev/null @@ -1 +0,0 @@ -Key Mapper & Floating Buttons \ No newline at end of file From 468afce932ac4f7dcf2a000d0291af1236d5bdd3 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Thu, 15 May 2025 13:01:45 +0000 Subject: [PATCH 88/95] New Crowdin translations by GitHub Action --- app/src/main/res/values-tr/strings.xml | 4 +++- fastlane/metadata/android/tr_TR/short_description.txt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 917c56edc4..ec3cc2f93a 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -921,7 +921,6 @@ Kaydedilmiş ses dosyalarını ayarlardan silebilirsiniz. Klavye Uygulamalar Giriş - Kamera ve Ses Bağlantı İçerik Arayüz @@ -969,6 +968,9 @@ Kaydedilmiş ses dosyalarını ayarlardan silebilirsiniz. Java programlama dilinde geçerli Kısa sayıların virgülle ayrılmış bir listesi. Örn. 3242,12354 Intent bayrakları bit bayrakları olarak saklanır. Bu bayraklar Intent\'in nasıl işleneceğini değiştirir. Bir Etkinlik Intent\'i için bu alan boş bırakılırsa, Key Mapper varsayılan olarak FLAG_ACTIVITY_NEW_TASK kullanır. Daha fazla bilgi için Android geliştirici belgelerini görmek üzere \'dokümanlar\'a dokunun. + + Bir eylem seçin + GitHub Web Sitesi diff --git a/fastlane/metadata/android/tr_TR/short_description.txt b/fastlane/metadata/android/tr_TR/short_description.txt index 11ab9493ec..ecaa2a662d 100644 --- a/fastlane/metadata/android/tr_TR/short_description.txt +++ b/fastlane/metadata/android/tr_TR/short_description.txt @@ -1 +1 @@ -HER ŞEY için kısayollar oluşturun! \ No newline at end of file +Make shortcuts for ANYTHING! Remap volume, power, keyboard, or floating buttons! \ No newline at end of file From 1b373b077518f581851e2c849dfcb7a7307a1419 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 17:19:13 +0200 Subject: [PATCH 89/95] #1394 feat: add empty PRO mode screen --- .../sds100/keymapper/BaseMainActivity.kt | 3 +- .../keymapper/promode/ProModeFragment.kt | 72 +++++++++++++++++ .../sds100/keymapper/promode/ProModeScreen.kt | 81 +++++++++++++++++++ .../keymapper/promode/ProModeViewModel.kt | 28 +++++++ .../settings/MainSettingsFragment.kt | 22 +++++ .../keymapper/settings/SettingsViewModel.kt | 11 +++ .../io/github/sds100/keymapper/util/Inject.kt | 7 ++ .../keymapper/util/ui/NavDestination.kt | 5 ++ .../keymapper/util/ui/NavigationViewModel.kt | 2 + app/src/main/res/navigation/nav_app.xml | 13 +++ app/src/main/res/values/strings.xml | 26 ++++++ 11 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/io/github/sds100/keymapper/promode/ProModeFragment.kt create mode 100644 app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt create mode 100644 app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt diff --git a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt index db26a652ac..2451f2aeea 100644 --- a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt @@ -44,7 +44,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.github.sds100.keymapper.Constants.PACKAGE_NAME import io.github.sds100.keymapper.compose.ComposeColors import io.github.sds100.keymapper.databinding.ActivityMainBinding -import io.github.sds100.keymapper.mappings.keymaps.trigger.RecordTriggerController import io.github.sds100.keymapper.nativelib.IEvdevService import io.github.sds100.keymapper.nativelib.adb.AdbPairingService import io.github.sds100.keymapper.system.accessibility.AccessibilityServiceAdapter @@ -235,7 +234,7 @@ abstract class BaseMainActivity : AppCompatActivity() { putExtra(EXTRA_FRAGMENT_ARG_KEY, "toggle_adb_wireless") val bundle = bundleOf(EXTRA_FRAGMENT_ARG_KEY to "toggle_adb_wireless") putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle) - startActivity(this) +// startActivity(this) } // See demo.DemoActivity in the Shizuku-API repository. diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeFragment.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeFragment.kt new file mode 100644 index 0000000000..cb31934545 --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeFragment.kt @@ -0,0 +1,72 @@ +package io.github.sds100.keymapper.promode + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.add +import androidx.compose.foundation.layout.displayCutout +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.findNavController +import io.github.sds100.keymapper.compose.KeyMapperTheme +import io.github.sds100.keymapper.databinding.FragmentComposeBinding +import io.github.sds100.keymapper.util.Inject +import io.github.sds100.keymapper.util.ui.setupNavigation +import io.github.sds100.keymapper.util.ui.showPopups + +class ProModeFragment : Fragment() { + + private val viewModel by viewModels { + Inject.proModeViewModel(requireContext()) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + viewModel.setupNavigation(this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + FragmentComposeBinding.inflate(inflater, container, false).apply { + composeView.apply { + // Dispose of the Composition when the view's LifecycleOwner + // is destroyed + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + KeyMapperTheme { + ProModeScreen( + modifier = Modifier + .fillMaxSize() + .windowInsetsPadding( + WindowInsets.systemBars.only(sides = WindowInsetsSides.Horizontal) + .add(WindowInsets.displayCutout.only(sides = WindowInsetsSides.Horizontal)), + ), + viewModel = viewModel, + onNavigateBack = findNavController()::navigateUp, + ) + } + } + } + return this.root + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewModel.showPopups(this, view) + } +} diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt new file mode 100644 index 0000000000..392300c32a --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt @@ -0,0 +1,81 @@ +package io.github.sds100.keymapper.promode + +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.displayCutoutPadding +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import io.github.sds100.keymapper.R +import io.github.sds100.keymapper.compose.KeyMapperTheme + +@Composable +fun ProModeScreen( + modifier: Modifier = Modifier, + viewModel: ProModeViewModel, + onNavigateBack: () -> Unit, +) { + ProModeScreen(modifier = modifier, onBackClick = onNavigateBack) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ProModeScreen( + modifier: Modifier = Modifier, + onBackClick: () -> Unit = {}, +) { + Scaffold( + modifier = modifier.displayCutoutPadding(), + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.pro_mode_app_bar_title)) }, + navigationIcon = { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.AutoMirrored.Rounded.ArrowBack, + contentDescription = stringResource(R.string.action_go_back), + ) + } + }, + ) + }, + ) { innerPadding -> + val layoutDirection = LocalLayoutDirection.current + val startPadding = innerPadding.calculateStartPadding(layoutDirection) + val endPadding = innerPadding.calculateEndPadding(layoutDirection) + + Surface( + modifier = Modifier + .fillMaxSize() + .padding( + top = innerPadding.calculateTopPadding(), + bottom = innerPadding.calculateBottomPadding(), + start = startPadding, + end = endPadding, + ), + + ) { + } + } +} + +@Preview +@Composable +private fun Preview() { + KeyMapperTheme { + ProModeScreen() + } +} diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt new file mode 100644 index 0000000000..f1f08ee692 --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt @@ -0,0 +1,28 @@ +package io.github.sds100.keymapper.promode + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import io.github.sds100.keymapper.util.ui.NavigationViewModel +import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl +import io.github.sds100.keymapper.util.ui.PopupViewModel +import io.github.sds100.keymapper.util.ui.PopupViewModelImpl +import io.github.sds100.keymapper.util.ui.ResourceProvider + +/** + * Created by sds100 on 22/07/2021. + */ +class ProModeViewModel( + resourceProvider: ResourceProvider, +) : ViewModel(), + ResourceProvider by resourceProvider, + PopupViewModel by PopupViewModelImpl(), + NavigationViewModel by NavigationViewModelImpl() { + + @Suppress("UNCHECKED_CAST") + class Factory( + private val resourceProvider: ResourceProvider, + ) : ViewModelProvider.NewInstanceFactory() { + + override fun create(modelClass: Class): T = ProModeViewModel(resourceProvider) as T + } +} diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt b/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt index 5c887d835e..9ac19e597f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt @@ -27,6 +27,7 @@ import io.github.sds100.keymapper.util.firstBlocking import io.github.sds100.keymapper.util.launchRepeatOnLifecycle import io.github.sds100.keymapper.util.str import io.github.sds100.keymapper.util.strArray +import io.github.sds100.keymapper.util.ui.setupNavigation import io.github.sds100.keymapper.util.viewLifecycleScope import kotlinx.coroutines.flow.collectLatest import splitties.alertdialog.appcompat.alertDialog @@ -60,6 +61,12 @@ class MainSettingsFragment : BaseSettingsFragment() { requireContext().contentResolver.takePersistableUriPermission(it, takeFlags) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + viewModel.setupNavigation(this) + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.preferenceDataStore = viewModel.sharedPrefsDataStoreWrapper addPreferencesFromResource(R.xml.preferences_empty) @@ -127,6 +134,21 @@ class MainSettingsFragment : BaseSettingsFragment() { } private fun populatePreferenceScreen() = preferenceScreen.apply { + // Pro mode + Preference(requireContext()).apply { + isSingleLineTitle = false + + setTitle(R.string.title_pref_pro_mode) + setSummary(R.string.summary_pref_pro_mode) + + setOnPreferenceClickListener { + viewModel.onProModeClick() + true + } + + addPreference(this) + } + // dark theme DropDownPreference(requireContext()).apply { key = Keys.darkTheme.name diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt index 6c96e76939..2d8f50e78f 100644 --- a/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt @@ -13,10 +13,14 @@ import io.github.sds100.keymapper.util.onSuccess import io.github.sds100.keymapper.util.otherwise import io.github.sds100.keymapper.util.ui.DialogResponse import io.github.sds100.keymapper.util.ui.MultiChoiceItem +import io.github.sds100.keymapper.util.ui.NavDestination +import io.github.sds100.keymapper.util.ui.NavigationViewModel +import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl import io.github.sds100.keymapper.util.ui.PopupUi import io.github.sds100.keymapper.util.ui.PopupViewModel import io.github.sds100.keymapper.util.ui.PopupViewModelImpl import io.github.sds100.keymapper.util.ui.ResourceProvider +import io.github.sds100.keymapper.util.ui.navigate import io.github.sds100.keymapper.util.ui.showPopup import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -32,6 +36,7 @@ class SettingsViewModel( private val useCase: ConfigSettingsUseCase, resourceProvider: ResourceProvider, ) : ViewModel(), + NavigationViewModel by NavigationViewModelImpl(), PopupViewModel by PopupViewModelImpl(), ResourceProvider by resourceProvider { val sharedPrefsDataStoreWrapper = SharedPrefsDataStoreWrapper(useCase) @@ -211,6 +216,12 @@ class SettingsViewModel( } } + fun onProModeClick() { + viewModelScope.launch { + navigate("pro_mode_settings", NavDestination.ProMode) + } + } + @Suppress("UNCHECKED_CAST") class Factory( private val configSettingsUseCase: ConfigSettingsUseCase, diff --git a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt index 3ad39fcfac..9b48ac110d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt @@ -28,6 +28,7 @@ import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCaseImp import io.github.sds100.keymapper.keymaps.ListKeyMapsUseCaseImpl import io.github.sds100.keymapper.logging.DisplayLogUseCaseImpl import io.github.sds100.keymapper.logging.LogViewModel +import io.github.sds100.keymapper.promode.ProModeViewModel import io.github.sds100.keymapper.settings.ConfigSettingsUseCaseImpl import io.github.sds100.keymapper.settings.SettingsViewModel import io.github.sds100.keymapper.sorting.SortKeyMapsUseCaseImpl @@ -257,4 +258,10 @@ object Inject { (ctx.applicationContext as KeyMapperApp).interactUiElementController, resourceProvider = ServiceLocator.resourceProvider(ctx), ) + + fun proModeViewModel( + ctx: Context, + ): ProModeViewModel.Factory = ProModeViewModel.Factory( + resourceProvider = ServiceLocator.resourceProvider(ctx), + ) } diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavDestination.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavDestination.kt index eababf264b..ec5d79d281 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavDestination.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavDestination.kt @@ -36,6 +36,7 @@ sealed class NavDestination { const val ID_SHIZUKU_SETTINGS = "shizuku_settings" const val ID_CONFIG_FLOATING_BUTTON = "config_floating_button" const val ID_INTERACT_UI_ELEMENT_ACTION = "interact_ui_element_action" + const val ID_PRO_MODE = "pro_mode" } data class ChooseApp( @@ -127,4 +128,8 @@ sealed class NavDestination { data class InteractUiElement(val action: ActionData.InteractUiElement?) : NavDestination() { override val id: String = ID_INTERACT_UI_ELEMENT_ACTION } + + data object ProMode : NavDestination() { + override val id: String = ID_PRO_MODE + } } diff --git a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt index 510eb75792..ee8475853d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/ui/NavigationViewModel.kt @@ -229,6 +229,8 @@ fun NavigationViewModel.setupNavigation(fragment: Fragment) { requestKey = requestKey, action = destination.action?.let { Json.encodeToString(destination.action) }, ) + + NavDestination.ProMode -> NavAppDirections.toProModeFragment() } fragment.findNavController().navigate(direction) diff --git a/app/src/main/res/navigation/nav_app.xml b/app/src/main/res/navigation/nav_app.xml index 61eb7309de..1571f7c30c 100644 --- a/app/src/main/res/navigation/nav_app.xml +++ b/app/src/main/res/navigation/nav_app.xml @@ -409,4 +409,17 @@ app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index faa0fcb0c2..8ca84f4bae 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -698,6 +698,9 @@ Are you sure you want to reset all settings in the app to the default? Your key maps will NOT be reset. The introduction screen and all warning pop ups will show again. Yes, reset + PRO mode + Advanced detection of key events.\ + @@ -1564,4 +1567,27 @@ +%d inherited constraint +%d inherited constraints + + + + PRO mode + Important! + These settings are dangerous and can cause your buttons to stop working if you set them incorrectly.\n\nIf you make a mistake, you may need to force restart your device by holding down the power button for a long time. + %d… + I understand + Understood + Set up + Root detected + You can skip the set up process by giving Key Mapper root permission. This will let Key Mapper auto start PRO mode on boot as well. + Use root + Shizuku detected + You can skip the set up process by giving Key Mapper Shizuku permission. + Use Shizuku + Set up with Key Mapper + Continue + Options + Enable PRO mode for all key maps + Key Mapper will use the ADB Shell for remapping + + From 6bb3ea728e3d1196e99a3b2b0ec426ed589ca995 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 17:53:15 +0200 Subject: [PATCH 90/95] #1394 feat: show warning at the top of PRO mode set up page --- .../io/github/sds100/keymapper/data/Keys.kt | 3 + .../sds100/keymapper/promode/ProModeScreen.kt | 148 +++++++++++++++++- .../keymapper/promode/ProModeSetupUseCase.kt | 22 +++ .../keymapper/promode/ProModeViewModel.kt | 53 ++++++- .../io/github/sds100/keymapper/util/Inject.kt | 4 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 225 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/io/github/sds100/keymapper/promode/ProModeSetupUseCase.kt diff --git a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt index dfd38baa43..c0b948f801 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt @@ -112,4 +112,7 @@ object Keys { val skipTapTargetTutorial = booleanPreferencesKey("key_skip_tap_target_tutorial") + + val isProModeWarningUnderstood = + booleanPreferencesKey("key_is_pro_mode_warning_understood") } diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt index 392300c32a..7dd60561ca 100644 --- a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt @@ -1,24 +1,44 @@ package io.github.sds100.keymapper.promode +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.calculateEndPadding import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.WarningAmber +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedCard import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme @@ -28,7 +48,14 @@ fun ProModeScreen( viewModel: ProModeViewModel, onNavigateBack: () -> Unit, ) { - ProModeScreen(modifier = modifier, onBackClick = onNavigateBack) + val proModeWarningState by viewModel.proModeWarningState.collectAsStateWithLifecycle() + + ProModeScreen(modifier = modifier, onBackClick = onNavigateBack) { + Content( + proModeWarningState = proModeWarningState, + onWarningButtonClick = viewModel::onWarningButtonClick, + ) + } } @OptIn(ExperimentalMaterial3Api::class) @@ -36,6 +63,7 @@ fun ProModeScreen( private fun ProModeScreen( modifier: Modifier = Modifier, onBackClick: () -> Unit = {}, + content: @Composable () -> Unit, ) { Scaffold( modifier = modifier.displayCutoutPadding(), @@ -66,9 +94,107 @@ private fun ProModeScreen( start = startPadding, end = endPadding, ), + ) { + content() + } + } +} + +@Composable +private fun Content( + modifier: Modifier = Modifier, + proModeWarningState: ProModeWarningState, + onWarningButtonClick: () -> Unit = {}, +) { + Column(modifier = modifier.verticalScroll(rememberScrollState())) { + WarningCard( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + state = proModeWarningState, + onButtonClick = onWarningButtonClick, + ) + + Spacer(modifier = Modifier.height(16.dp)) + + if (proModeWarningState is ProModeWarningState.Understood) { + } else { + Text( + modifier = Modifier.padding(horizontal = 32.dp), + text = stringResource(R.string.pro_mode_settings_unavailable_text), + textAlign = TextAlign.Center, + ) + } + } +} + +@Composable +private fun WarningCard( + modifier: Modifier = Modifier, + state: ProModeWarningState, + onButtonClick: () -> Unit = {}, +) { + OutlinedCard( + modifier = modifier, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.error), + ) { + Spacer(modifier = Modifier.height(16.dp)) + Row(modifier = Modifier.padding(horizontal = 16.dp)) { + Icon( + imageVector = Icons.Rounded.WarningAmber, + contentDescription = null, + tint = MaterialTheme.colorScheme.error, + ) + + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.pro_mode_warning_title), + style = MaterialTheme.typography.titleMedium, + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.pro_mode_warning_text), + style = MaterialTheme.typography.bodyMedium, + ) + + Spacer(modifier = Modifier.height(8.dp)) + + FilledTonalButton( + modifier = Modifier + .align(Alignment.End) + .padding(horizontal = 16.dp), + onClick = onButtonClick, + enabled = state is ProModeWarningState.Idle, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError, + ), ) { + if (state is ProModeWarningState.Understood) { + Icon(imageVector = Icons.Rounded.Check, contentDescription = null) + + Spacer(modifier = Modifier.width(8.dp)) + } + + val text = when (state) { + is ProModeWarningState.CountingDown -> stringResource( + R.string.pro_mode_warning_understand_button_countdown, + state.seconds, + ) + + ProModeWarningState.Idle -> stringResource(R.string.pro_mode_warning_understand_button_not_completed) + ProModeWarningState.Understood -> stringResource(R.string.pro_mode_warning_understand_button_completed) + } + + Text(text) } + + Spacer(modifier = Modifier.height(16.dp)) } } @@ -76,6 +202,24 @@ private fun ProModeScreen( @Composable private fun Preview() { KeyMapperTheme { - ProModeScreen() + ProModeScreen { + Content( + proModeWarningState = ProModeWarningState.Understood, + ) + } + } +} + +@Preview +@Composable +private fun PreviewCountingDown() { + KeyMapperTheme { + ProModeScreen { + Content( + proModeWarningState = ProModeWarningState.CountingDown( + seconds = 5, + ), + ) + } } } diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeSetupUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeSetupUseCase.kt new file mode 100644 index 0000000000..c97d1f9b61 --- /dev/null +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeSetupUseCase.kt @@ -0,0 +1,22 @@ +package io.github.sds100.keymapper.promode + +import io.github.sds100.keymapper.data.Keys +import io.github.sds100.keymapper.data.repositories.PreferenceRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class ProModeSetupUseCaseImpl( + private val preferences: PreferenceRepository, +) : ProModeSetupUseCase { + override val isWarningUnderstood: Flow = + preferences.get(Keys.isProModeWarningUnderstood).map { it ?: false } + + override fun onUnderstoodWarning() { + preferences.set(Keys.isProModeWarningUnderstood, true) + } +} + +interface ProModeSetupUseCase { + val isWarningUnderstood: Flow + fun onUnderstoodWarning() +} diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt index f1f08ee692..b4156c331d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt @@ -2,27 +2,72 @@ package io.github.sds100.keymapper.promode import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope import io.github.sds100.keymapper.util.ui.NavigationViewModel import io.github.sds100.keymapper.util.ui.NavigationViewModelImpl import io.github.sds100.keymapper.util.ui.PopupViewModel import io.github.sds100.keymapper.util.ui.PopupViewModelImpl import io.github.sds100.keymapper.util.ui.ResourceProvider +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.stateIn -/** - * Created by sds100 on 22/07/2021. - */ class ProModeViewModel( resourceProvider: ResourceProvider, + private val useCase: ProModeSetupUseCase, ) : ViewModel(), ResourceProvider by resourceProvider, PopupViewModel by PopupViewModelImpl(), NavigationViewModel by NavigationViewModelImpl() { + companion object { + private const val WARNING_COUNT_DOWN_DURATION = 5 + } + + @OptIn(ExperimentalCoroutinesApi::class) + val proModeWarningState: StateFlow = + useCase.isWarningUnderstood.flatMapLatest { isUnderstood -> + if (isUnderstood) { + flowOf(ProModeWarningState.Understood) + } else { + flow { + repeat(WARNING_COUNT_DOWN_DURATION) { + emit(ProModeWarningState.CountingDown(WARNING_COUNT_DOWN_DURATION - it)) + delay(1000L) + } + + emit(ProModeWarningState.Idle) + } + } + }.stateIn( + viewModelScope, + SharingStarted.Eagerly, + ProModeWarningState.CountingDown( + WARNING_COUNT_DOWN_DURATION, + ), + ) + + fun onWarningButtonClick() { + useCase.onUnderstoodWarning() + } + @Suppress("UNCHECKED_CAST") class Factory( private val resourceProvider: ResourceProvider, + private val useCase: ProModeSetupUseCase, ) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = ProModeViewModel(resourceProvider) as T + override fun create(modelClass: Class): T = ProModeViewModel(resourceProvider, useCase) as T } } + +sealed class ProModeWarningState { + data class CountingDown(val seconds: Int) : ProModeWarningState() + data object Idle : ProModeWarningState() + data object Understood : ProModeWarningState() +} diff --git a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt index 9b48ac110d..b7d191239a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt +++ b/app/src/main/java/io/github/sds100/keymapper/util/Inject.kt @@ -28,6 +28,7 @@ import io.github.sds100.keymapper.keymaps.FingerprintGesturesSupportedUseCaseImp import io.github.sds100.keymapper.keymaps.ListKeyMapsUseCaseImpl import io.github.sds100.keymapper.logging.DisplayLogUseCaseImpl import io.github.sds100.keymapper.logging.LogViewModel +import io.github.sds100.keymapper.promode.ProModeSetupUseCaseImpl import io.github.sds100.keymapper.promode.ProModeViewModel import io.github.sds100.keymapper.settings.ConfigSettingsUseCaseImpl import io.github.sds100.keymapper.settings.SettingsViewModel @@ -263,5 +264,8 @@ object Inject { ctx: Context, ): ProModeViewModel.Factory = ProModeViewModel.Factory( resourceProvider = ServiceLocator.resourceProvider(ctx), + useCase = ProModeSetupUseCaseImpl( + ServiceLocator.settingsRepository(ctx), + ), ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8ca84f4bae..83338b135b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1588,6 +1588,7 @@ Options Enable PRO mode for all key maps Key Mapper will use the ADB Shell for remapping + These settings are unavailable until you acknowledge the warning. From eb3554ae0abc46b5250af4fcdbbb2c4b7832e27b Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 18:27:20 +0200 Subject: [PATCH 91/95] #1394 feat: add cards to set up PRO mode with root or shizuku --- .../sds100/keymapper/compose/ComposeColors.kt | 8 ++ .../keymapper/compose/ComposeCustomColors.kt | 30 +++++ .../sds100/keymapper/promode/ProModeScreen.kt | 117 ++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/app/src/main/java/io/github/sds100/keymapper/compose/ComposeColors.kt b/app/src/main/java/io/github/sds100/keymapper/compose/ComposeColors.kt index 6d677359e4..10adb36631 100644 --- a/app/src/main/java/io/github/sds100/keymapper/compose/ComposeColors.kt +++ b/app/src/main/java/io/github/sds100/keymapper/compose/ComposeColors.kt @@ -45,6 +45,10 @@ object ComposeColors { val onGreenLight = Color(0xFFFFFFFF) val greenContainerLight = Color(0xFFBCF0B4) val onGreenContainerLight = Color(0xFF235024) + val magiskTealLight = Color(0xFF008072) + val onMagiskTealLight = Color(0xFFFFFFFF) + val shizukuBlueLight = Color(0xFF4556B7) + val onShizukuBlueLight = Color(0xFFFFFFFF) val primaryDark = Color(0xFFAAC7FF) val onPrimaryDark = Color(0xFF0A305F) @@ -87,4 +91,8 @@ object ComposeColors { val onGreenDark = Color(0xFF0A390F) val greenContainerDark = Color(0xFF235024) val onGreenContainerDark = Color(0xFFBCF0B4) + val magiskTealDark = Color(0xFF009B8C) + val onMagiskTealDark = Color(0xFFFFFFFF) + val shizukuBlueDark = Color(0xFFB7C4F4) + val onShizukuBlueDark = Color(0xFF0A305F) } diff --git a/app/src/main/java/io/github/sds100/keymapper/compose/ComposeCustomColors.kt b/app/src/main/java/io/github/sds100/keymapper/compose/ComposeCustomColors.kt index 2c1000f6bf..d8c26c3b59 100644 --- a/app/src/main/java/io/github/sds100/keymapper/compose/ComposeCustomColors.kt +++ b/app/src/main/java/io/github/sds100/keymapper/compose/ComposeCustomColors.kt @@ -1,6 +1,10 @@ package io.github.sds100.keymapper.compose +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable +import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.Color /** @@ -17,6 +21,10 @@ data class ComposeCustomColors( val onGreen: Color = Color.Unspecified, val greenContainer: Color = Color.Unspecified, val onGreenContainer: Color = Color.Unspecified, + val magiskTeal: Color = Color.Unspecified, + val onMagiskTeal: Color = Color.Unspecified, + val shizukuBlue: Color = Color.Unspecified, + val onShizukuBlue: Color = Color.Unspecified, ) { companion object { val LightPalette = ComposeCustomColors( @@ -26,6 +34,11 @@ data class ComposeCustomColors( onGreen = ComposeColors.onGreenLight, greenContainer = ComposeColors.greenContainerLight, onGreenContainer = ComposeColors.onGreenContainerLight, + magiskTeal = ComposeColors.magiskTealLight, + onMagiskTeal = ComposeColors.onMagiskTealLight, + shizukuBlue = ComposeColors.shizukuBlueLight, + onShizukuBlue = ComposeColors.onShizukuBlueLight, + ) val DarkPalette = ComposeCustomColors( @@ -35,6 +48,23 @@ data class ComposeCustomColors( onGreen = ComposeColors.onGreenDark, greenContainer = ComposeColors.greenContainerDark, onGreenContainer = ComposeColors.onGreenContainerDark, + magiskTeal = ComposeColors.magiskTealDark, + onMagiskTeal = ComposeColors.onMagiskTealDark, + shizukuBlue = ComposeColors.shizukuBlueDark, + onShizukuBlue = ComposeColors.onShizukuBlueDark, ) } + + @Composable + @Stable + fun contentColorFor(color: Color): Color { + return when (color) { + red -> onRed + green -> onGreen + greenContainer -> onGreenContainer + magiskTeal -> onMagiskTeal + shizukuBlue -> onShizukuBlue + else -> MaterialTheme.colorScheme.contentColorFor(color) + } + } } diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt index 7dd60561ca..090273d4ef 100644 --- a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt @@ -1,6 +1,7 @@ package io.github.sds100.keymapper.promode import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -16,7 +17,10 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import androidx.compose.material.icons.rounded.Android import androidx.compose.material.icons.rounded.Check +import androidx.compose.material.icons.rounded.Checklist +import androidx.compose.material.icons.rounded.Numbers import androidx.compose.material.icons.rounded.WarningAmber import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api @@ -33,6 +37,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -41,6 +47,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.sds100.keymapper.R import io.github.sds100.keymapper.compose.KeyMapperTheme +import io.github.sds100.keymapper.compose.LocalCustomColorsPalette +import io.github.sds100.keymapper.util.ui.compose.OptionsHeaderRow @Composable fun ProModeScreen( @@ -118,6 +126,47 @@ private fun Content( Spacer(modifier = Modifier.height(16.dp)) if (proModeWarningState is ProModeWarningState.Understood) { + OptionsHeaderRow( + modifier = Modifier.padding(horizontal = 16.dp), + icon = Icons.Rounded.Checklist, + text = stringResource(R.string.pro_mode_set_up_title), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + SetupCard( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + color = LocalCustomColorsPalette.current.magiskTeal, + icon = Icons.Rounded.Numbers, + title = stringResource(R.string.pro_mode_root_detected_title), + content = { + Text( + text = stringResource(R.string.pro_mode_root_detected_text), + style = MaterialTheme.typography.bodyMedium, + ) + }, + buttonText = stringResource(R.string.pro_mode_root_detected_button), + ) + + Spacer(modifier = Modifier.height(8.dp)) + + SetupCard( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + color = LocalCustomColorsPalette.current.shizukuBlue, + icon = Icons.Rounded.Android, + title = stringResource(R.string.pro_mode_shizuku_detected_title), + content = { + Text( + text = stringResource(R.string.pro_mode_shizuku_detected_text), + style = MaterialTheme.typography.bodyMedium, + ) + }, + buttonText = stringResource(R.string.pro_mode_shizuku_detected_button), + ) } else { Text( modifier = Modifier.padding(horizontal = 32.dp), @@ -198,6 +247,62 @@ private fun WarningCard( } } +@Composable +private fun SetupCard( + modifier: Modifier = Modifier, + color: Color, + icon: ImageVector, + title: String, + content: @Composable () -> Unit, + buttonText: String, + onButtonClick: () -> Unit = {}, +) { + OutlinedCard(modifier = modifier) { + Spacer(modifier = Modifier.height(16.dp)) + Row(modifier = Modifier.padding(horizontal = 16.dp)) { + Icon( + imageVector = icon, + contentDescription = null, + tint = color, + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + ) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + content() + } + + Spacer(modifier = Modifier.height(8.dp)) + + FilledTonalButton( + modifier = Modifier + .align(Alignment.End) + .padding(horizontal = 16.dp), + onClick = onButtonClick, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = color, + contentColor = LocalCustomColorsPalette.current.contentColorFor(color), + ), + ) { + Text(buttonText) + } + + Spacer(modifier = Modifier.height(16.dp)) + } +} + @Preview @Composable private fun Preview() { @@ -210,6 +315,18 @@ private fun Preview() { } } +@Preview +@Composable +private fun PreviewDark() { + KeyMapperTheme(darkTheme = true) { + ProModeScreen { + Content( + proModeWarningState = ProModeWarningState.Understood, + ) + } + } +} + @Preview @Composable private fun PreviewCountingDown() { From 9975c005fed06ed91b9a458a158941d4ca9d9df4 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 18:59:00 +0200 Subject: [PATCH 92/95] #1394 feat: use libsu library to detect and request root permission --- CREDITS.md | 1 + app/build.gradle | 1 + .../sds100/keymapper/BaseMainActivity.kt | 6 +++ .../github/sds100/keymapper/KeyMapperApp.kt | 7 +--- .../github/sds100/keymapper/ServiceLocator.kt | 4 +- .../io/github/sds100/keymapper/UseCases.kt | 4 +- .../io/github/sds100/keymapper/data/Keys.kt | 3 ++ .../keymaps/detection/DetectKeyMapsUseCase.kt | 2 +- .../sds100/keymapper/promode/ProModeScreen.kt | 4 +- .../keymapper/promode/ProModeViewModel.kt | 6 +++ .../settings/ConfigSettingsUseCase.kt | 17 +++++--- .../settings/MainSettingsFragment.kt | 10 +++-- .../keymapper/settings/SettingsViewModel.kt | 4 ++ .../system/{Shell.kt => SimpleShell.kt} | 2 +- .../system/navigation/OpenMenuHelper.kt | 2 +- .../ManageNotificationsUseCase.kt | 2 +- .../permissions/AndroidPermissionAdapter.kt | 4 +- .../sds100/keymapper/system/root/SuAdapter.kt | 40 ++++++++++--------- app/src/main/res/values/strings.xml | 5 +-- .../actions/PerformActionsUseCaseTest.kt | 2 +- 20 files changed, 76 insertions(+), 50 deletions(-) rename app/src/main/java/io/github/sds100/keymapper/system/{Shell.kt => SimpleShell.kt} (97%) diff --git a/CREDITS.md b/CREDITS.md index 8d7b22f638..24c12e44f6 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -13,6 +13,7 @@ Many thanks to... - @[MFlisar](https://github.com/MFlisar) for their [drag and select](https://github.com/MFlisar/DragSelectRecyclerView) library. - @[RikkaApps](https://github.com/RikkaApps) for Shizuku! It is amazing. - @[canopas](https://github.com/canopas) for their Jetpack Compose Tap Target library https://github.com/canopas/compose-intro-showcase. + - @[topjohnwu](https://github.com/topjohnwu) for Magisk and [libsu](https://github.com/topjohnwu/libsu). [salomonbrys]: https://github.com/salomonbrys [Kotson]: https://github.com/salomonbrys/Kotson diff --git a/app/build.gradle b/app/build.gradle index 795355c559..e6561ff3e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -198,6 +198,7 @@ dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") implementation 'com.canopas.intro-showcase-view:introshowcaseview:2.0.2' + implementation "com.github.topjohnwu.libsu:core:6.0.0" // splitties implementation "com.louiscad.splitties:splitties-bitflags:$splitties_version" diff --git a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt index 2451f2aeea..8e05826d7e 100644 --- a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt @@ -51,6 +51,7 @@ import io.github.sds100.keymapper.system.files.FileUtils import io.github.sds100.keymapper.system.inputevents.MyMotionEvent import io.github.sds100.keymapper.system.permissions.AndroidPermissionAdapter import io.github.sds100.keymapper.system.permissions.RequestPermissionDelegate +import io.github.sds100.keymapper.system.root.SuAdapterImpl import io.github.sds100.keymapper.trigger.RecordTriggerController import io.github.sds100.keymapper.util.launchRepeatOnLifecycle import io.github.sds100.keymapper.util.ui.showPopups @@ -92,6 +93,10 @@ abstract class BaseMainActivity : AppCompatActivity() { ServiceLocator.accessibilityServiceAdapter(this) } + private val suAdapter: SuAdapterImpl by lazy { + ServiceLocator.suAdapter(this) + } + val viewModel by viewModels { ActivityViewModel.Factory(ServiceLocator.resourceProvider(this)) } @@ -278,6 +283,7 @@ abstract class BaseMainActivity : AppCompatActivity() { // the activities have not necessarily resumed at that point. permissionAdapter.onPermissionsChanged() serviceAdapter.updateWhetherServiceIsEnabled() + suAdapter.invalidateIsRooted() } override fun onDestroy() { diff --git a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt index 5519d434ce..fdd0ad4952 100644 --- a/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt +++ b/app/src/main/java/io/github/sds100/keymapper/KeyMapperApp.kt @@ -130,12 +130,7 @@ class KeyMapperApp : MultiDexApplication() { val vibratorAdapter by lazy { AndroidVibratorAdapter(this) } val displayAdapter by lazy { AndroidDisplayAdapter(this, coroutineScope = appCoroutineScope) } val audioAdapter by lazy { AndroidVolumeAdapter(this) } - val suAdapter by lazy { - SuAdapterImpl( - appCoroutineScope, - ServiceLocator.settingsRepository(this), - ) - } + val suAdapter by lazy { SuAdapterImpl(appCoroutineScope) } val phoneAdapter by lazy { AndroidPhoneAdapter(this, appCoroutineScope) } val intentAdapter by lazy { IntentAdapterImpl(this) } val mediaAdapter by lazy { AndroidMediaAdapter(this, appCoroutineScope) } diff --git a/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt b/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt index 030d056d9a..94816343ce 100755 --- a/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt +++ b/app/src/main/java/io/github/sds100/keymapper/ServiceLocator.kt @@ -50,7 +50,7 @@ import io.github.sds100.keymapper.system.phone.PhoneAdapter import io.github.sds100.keymapper.system.popup.PopupMessageAdapter import io.github.sds100.keymapper.system.power.PowerAdapter import io.github.sds100.keymapper.system.ringtones.RingtoneAdapter -import io.github.sds100.keymapper.system.root.SuAdapter +import io.github.sds100.keymapper.system.root.SuAdapterImpl import io.github.sds100.keymapper.system.url.OpenUrlAdapter import io.github.sds100.keymapper.system.vibrator.VibratorAdapter import io.github.sds100.keymapper.system.volume.VolumeAdapter @@ -267,7 +267,7 @@ object ServiceLocator { fun audioAdapter(context: Context): VolumeAdapter = (context.applicationContext as KeyMapperApp).audioAdapter - fun suAdapter(context: Context): SuAdapter = (context.applicationContext as KeyMapperApp).suAdapter + fun suAdapter(context: Context): SuAdapterImpl = (context.applicationContext as KeyMapperApp).suAdapter fun intentAdapter(context: Context): IntentAdapter = (context.applicationContext as KeyMapperApp).intentAdapter diff --git a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt index d57d0bf37b..ef7bbc2dd0 100644 --- a/app/src/main/java/io/github/sds100/keymapper/UseCases.kt +++ b/app/src/main/java/io/github/sds100/keymapper/UseCases.kt @@ -21,7 +21,7 @@ import io.github.sds100.keymapper.reroutekeyevents.RerouteKeyEventsUseCaseImpl import io.github.sds100.keymapper.shizuku.ShizukuInputEventInjector import io.github.sds100.keymapper.sorting.SortKeyMapsUseCase import io.github.sds100.keymapper.sorting.SortKeyMapsUseCaseImpl -import io.github.sds100.keymapper.system.Shell +import io.github.sds100.keymapper.system.SimpleShell import io.github.sds100.keymapper.system.accessibility.ControlAccessibilityServiceUseCase import io.github.sds100.keymapper.system.accessibility.ControlAccessibilityServiceUseCaseImpl import io.github.sds100.keymapper.system.accessibility.IAccessibilityService @@ -143,7 +143,7 @@ object UseCases { ServiceLocator.inputMethodAdapter(ctx), ServiceLocator.fileAdapter(ctx), ServiceLocator.suAdapter(ctx), - Shell, + SimpleShell, ServiceLocator.intentAdapter(ctx), getActionError(ctx), keyMapperImeMessenger(ctx, keyEventRelayService), diff --git a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt index c0b948f801..0daba2d08a 100644 --- a/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt +++ b/app/src/main/java/io/github/sds100/keymapper/data/Keys.kt @@ -10,7 +10,10 @@ import androidx.datastore.preferences.core.stringSetPreferencesKey */ object Keys { val darkTheme = stringPreferencesKey("pref_dark_theme_mode") + + @Deprecated("Now use the libsu library to detect whether the device is rooted.") val hasRootPermission = booleanPreferencesKey("pref_allow_root_features") + val shownAppIntro = booleanPreferencesKey("pref_first_time") val showImePickerNotification = booleanPreferencesKey("pref_show_ime_notification") val showToggleKeyMapsNotification = booleanPreferencesKey("pref_show_remappings_notification") diff --git a/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapsUseCase.kt index 83d7d6b6ef..0e0b453212 100644 --- a/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/keymaps/detection/DetectKeyMapsUseCase.kt @@ -141,7 +141,7 @@ class DetectKeyMapsUseCaseImpl( override val detectScreenOffTriggers: Flow = combine( allKeyMapList, - suAdapter.isGranted, + suAdapter.isRooted, ) { keyMapList, isRootPermissionGranted -> keyMapList.any { it.keyMap.trigger.screenOffTrigger } && isRootPermissionGranted }.flowOn(Dispatchers.Default) diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt index 090273d4ef..4628e68aa7 100644 --- a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeScreen.kt @@ -123,7 +123,7 @@ private fun Content( onButtonClick = onWarningButtonClick, ) - Spacer(modifier = Modifier.height(16.dp)) + Spacer(modifier = Modifier.height(8.dp)) if (proModeWarningState is ProModeWarningState.Understood) { OptionsHeaderRow( @@ -174,6 +174,8 @@ private fun Content( textAlign = TextAlign.Center, ) } + + Spacer(modifier = Modifier.height(8.dp)) } } diff --git a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt index b4156c331d..d2f6e531f5 100644 --- a/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/promode/ProModeViewModel.kt @@ -71,3 +71,9 @@ sealed class ProModeWarningState { data object Idle : ProModeWarningState() data object Understood : ProModeWarningState() } + +data class ProModeSetupState( + val isRootDetected: Boolean, + val isShizukuDetected: Boolean, + +) diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/ConfigSettingsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/settings/ConfigSettingsUseCase.kt index fbc4707022..bc94307a06 100644 --- a/app/src/main/java/io/github/sds100/keymapper/settings/ConfigSettingsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/settings/ConfigSettingsUseCase.kt @@ -41,7 +41,7 @@ class ConfigSettingsUseCaseImpl( private val imeHelper by lazy { KeyMapperImeHelper(inputMethodAdapter) } - override val isRootGranted: Flow = suAdapter.isGranted + override val isRootGranted: Flow = suAdapter.isRooted override val isWriteSecureSettingsGranted: Flow = channelFlow { send(permissionAdapter.isGranted(Permission.WRITE_SECURE_SETTINGS)) @@ -157,6 +157,10 @@ class ConfigSettingsUseCaseImpl( permissionAdapter.request(Permission.POST_NOTIFICATIONS) } + override fun requestRootPermission() { + suAdapter.requestPermission() + } + override fun isNotificationsPermissionGranted(): Boolean = permissionAdapter.isGranted(Permission.POST_NOTIFICATIONS) override fun getSoundFiles(): List = soundsManager.soundFiles.value @@ -179,37 +183,38 @@ interface ConfigSettingsUseCase { fun setAutomaticBackupLocation(uri: String) fun disableAutomaticBackup() val isRootGranted: Flow - val isWriteSecureSettingsGranted: Flow + fun requestRootPermission() + val isWriteSecureSettingsGranted: Flow val isShizukuInstalled: Flow val isShizukuStarted: Flow val isShizukuPermissionGranted: Flow fun downloadShizuku() - fun openShizukuApp() + fun openShizukuApp() val rerouteKeyEvents: Flow val isCompatibleImeChosen: Flow val isCompatibleImeEnabled: Flow suspend fun enableCompatibleIme() suspend fun chooseCompatibleIme(): Result - suspend fun showImePicker(): Result<*> + suspend fun showImePicker(): Result<*> val defaultLongPressDelay: Flow val defaultDoublePressDelay: Flow val defaultRepeatDelay: Flow val defaultSequenceTriggerTimeout: Flow val defaultVibrateDuration: Flow - val defaultRepeatRate: Flow + val defaultRepeatRate: Flow fun getSoundFiles(): List fun deleteSoundFiles(uid: List) fun resetDefaultMappingOptions() fun requestWriteSecureSettingsPermission() fun requestNotificationsPermission() fun isNotificationsPermissionGranted(): Boolean + fun requestShizukuPermission() val connectedInputDevices: StateFlow>> - fun resetAllSettings() } diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt b/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt index 9ac19e597f..1cc746b57d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt +++ b/app/src/main/java/io/github/sds100/keymapper/settings/MainSettingsFragment.kt @@ -512,14 +512,16 @@ class MainSettingsFragment : BaseSettingsFragment() { } // root permission switch - SwitchPreferenceCompat(requireContext()).apply { - key = Keys.hasRootPermission.name - setDefaultValue(false) - + Preference(requireContext()).apply { isSingleLineTitle = false setTitle(R.string.title_pref_root_permission) setSummary(R.string.summary_pref_root_permission) + setOnPreferenceClickListener { + viewModel.onRequestRootClick() + true + } + addPreference(this) } diff --git a/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt b/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt index 2d8f50e78f..abb848bf48 100644 --- a/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt +++ b/app/src/main/java/io/github/sds100/keymapper/settings/SettingsViewModel.kt @@ -216,6 +216,10 @@ class SettingsViewModel( } } + fun onRequestRootClick() { + useCase.requestRootPermission() + } + fun onProModeClick() { viewModelScope.launch { navigate("pro_mode_settings", NavDestination.ProMode) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/Shell.kt b/app/src/main/java/io/github/sds100/keymapper/system/SimpleShell.kt similarity index 97% rename from app/src/main/java/io/github/sds100/keymapper/system/Shell.kt rename to app/src/main/java/io/github/sds100/keymapper/system/SimpleShell.kt index 10b043e54d..0d0fcd65bf 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/Shell.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/SimpleShell.kt @@ -10,7 +10,7 @@ import java.io.InputStream /** * Created by sds100 on 05/11/2018. */ -object Shell : ShellAdapter { +object SimpleShell : ShellAdapter { /** * @return whether the command was executed successfully */ diff --git a/app/src/main/java/io/github/sds100/keymapper/system/navigation/OpenMenuHelper.kt b/app/src/main/java/io/github/sds100/keymapper/system/navigation/OpenMenuHelper.kt index 0864806c2d..71deee9d52 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/navigation/OpenMenuHelper.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/navigation/OpenMenuHelper.kt @@ -46,7 +46,7 @@ class OpenMenuHelper( return success() } - suAdapter.isGranted.firstBlocking() -> + suAdapter.isRooted.firstBlocking() -> return suAdapter.execute("input keyevent ${KeyEvent.KEYCODE_MENU}\n") else -> { diff --git a/app/src/main/java/io/github/sds100/keymapper/system/notifications/ManageNotificationsUseCase.kt b/app/src/main/java/io/github/sds100/keymapper/system/notifications/ManageNotificationsUseCase.kt index 63ffafbdab..0f59556bd1 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/notifications/ManageNotificationsUseCase.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/notifications/ManageNotificationsUseCase.kt @@ -23,7 +23,7 @@ class ManageNotificationsUseCaseImpl( override val showImePickerNotification: Flow = combine( - suAdapter.isGranted, + suAdapter.isRooted, preferences.get(Keys.showImePickerNotification), ) { hasRootPermission, show -> when { diff --git a/app/src/main/java/io/github/sds100/keymapper/system/permissions/AndroidPermissionAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/permissions/AndroidPermissionAdapter.kt index 4b7e63735f..c124e7283d 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/permissions/AndroidPermissionAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/permissions/AndroidPermissionAdapter.kt @@ -100,7 +100,7 @@ class AndroidPermissionAdapter( .stateIn(coroutineScope, SharingStarted.Eagerly, false) init { - suAdapter.isGranted + suAdapter.isRooted .drop(1) .onEach { onPermissionsChanged() } .launchIn(coroutineScope) @@ -278,7 +278,7 @@ class AndroidPermissionAdapter( Manifest.permission.CALL_PHONE, ) == PERMISSION_GRANTED - Permission.ROOT -> suAdapter.isGranted.value + Permission.ROOT -> suAdapter.isRooted.value Permission.IGNORE_BATTERY_OPTIMISATION -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { diff --git a/app/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt index 7b9db9f99e..563ea707f6 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt @@ -1,18 +1,17 @@ package io.github.sds100.keymapper.system.root -import io.github.sds100.keymapper.data.Keys -import io.github.sds100.keymapper.data.repositories.PreferenceRepository -import io.github.sds100.keymapper.system.Shell +import com.topjohnwu.superuser.Shell +import io.github.sds100.keymapper.system.SimpleShell import io.github.sds100.keymapper.system.permissions.Permission import io.github.sds100.keymapper.util.Error import io.github.sds100.keymapper.util.Result import io.github.sds100.keymapper.util.Success import io.github.sds100.keymapper.util.firstBlocking import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.updateAndGet import java.io.IOException import java.io.InputStream @@ -22,32 +21,31 @@ import java.io.InputStream class SuAdapterImpl( coroutineScope: CoroutineScope, - private val preferenceRepository: PreferenceRepository, ) : SuAdapter { private var process: Process? = null - override val isGranted: StateFlow = preferenceRepository.get(Keys.hasRootPermission) - .map { it ?: false } - .stateIn(coroutineScope, SharingStarted.Eagerly, false) + override val isRooted: MutableStateFlow = MutableStateFlow(false) - override fun requestPermission(): Boolean { - preferenceRepository.set(Keys.hasRootPermission, true) + init { + invalidateIsRooted() + } + override fun requestPermission(): Boolean { // show the su prompt - Shell.run("su") + Shell.getShell() - return true + return isRooted.updateAndGet { Shell.isAppGrantedRoot() ?: false } } override fun execute(command: String, block: Boolean): Result<*> { - if (!isGranted.firstBlocking()) { + if (!isRooted.firstBlocking()) { return Error.PermissionDenied(Permission.ROOT) } try { if (block) { // Don't use the long running su process because that will block the thread indefinitely - Shell.run("su", "-c", command, waitFor = true) + SimpleShell.run("su", "-c", command, waitFor = true) } else { if (process == null) { process = ProcessBuilder("su").start() @@ -66,21 +64,25 @@ class SuAdapterImpl( } override fun getCommandOutput(command: String): Result { - if (!isGranted.firstBlocking()) { + if (!isRooted.firstBlocking()) { return Error.PermissionDenied(Permission.ROOT) } try { - val inputStream = Shell.getShellCommandStdOut("su", "-c", command) + val inputStream = SimpleShell.getShellCommandStdOut("su", "-c", command) return Success(inputStream) } catch (e: IOException) { return Error.UnknownIOError } } + + fun invalidateIsRooted() { + isRooted.update { Shell.isAppGrantedRoot() ?: false } + } } interface SuAdapter { - val isGranted: StateFlow + val isRooted: StateFlow /** * @return whether root permission was granted successfully diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 83338b135b..7b13aa6253 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -634,9 +634,8 @@ Show an on-screen message when automatically changing the keyboard - Key Mapper has root permission - Enable this if you want to use features/actions which only work on rooted devices. Key Mapper must have root permission from your root-access-management app (e.g Magisk, SuperSU) for these features to work. - Only turn this on if you know your device is rooted and you have given Key Mapper root permission. + Request root permission + If your device is rooted then this will show the root permission pop up from Magisk or your root app. Choose theme Light and dark themes available diff --git a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt index d2c9df0c1e..23ebbb1ca7 100644 --- a/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt +++ b/app/src/test/java/io/github/sds100/keymapper/actions/PerformActionsUseCaseTest.kt @@ -59,7 +59,7 @@ class PerformActionsUseCaseTest { inputMethodAdapter = mock(), fileAdapter = mock(), suAdapter = mock { - on { isGranted }.then { MutableStateFlow(false) } + on { isRooted }.then { MutableStateFlow(false) } }, shellAdapter = mock(), intentAdapter = mock(), From 6282d9a2808655d55c06f4af5813f136807269ad Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 19:04:05 +0200 Subject: [PATCH 93/95] #1394 feat: use libsu Shell for executing root commands --- .../sds100/keymapper/system/root/SuAdapter.kt | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt b/app/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt index 563ea707f6..9094681d3b 100644 --- a/app/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt +++ b/app/src/main/java/io/github/sds100/keymapper/system/root/SuAdapter.kt @@ -44,17 +44,9 @@ class SuAdapterImpl( try { if (block) { - // Don't use the long running su process because that will block the thread indefinitely - SimpleShell.run("su", "-c", command, waitFor = true) + Shell.cmd(command).exec() } else { - if (process == null) { - process = ProcessBuilder("su").start() - } - - with(process!!.outputStream.bufferedWriter()) { - write("$command\n") - flush() - } + Shell.cmd(command).submit() } return Success(Unit) @@ -77,6 +69,7 @@ class SuAdapterImpl( } fun invalidateIsRooted() { + Shell.getShell() isRooted.update { Shell.isAppGrantedRoot() ?: false } } } From 7a1118c3b7bee6b8dee101ccdb592ecac4410fc0 Mon Sep 17 00:00:00 2001 From: sds100 Date: Thu, 15 May 2025 20:55:01 +0200 Subject: [PATCH 94/95] #1394 delete redundant linux header files that are included in the Android NDK --- .../include/linux/freebsd/input-event-codes.h | 980 ------------------ .../main/cpp/include/linux/freebsd/input.h | 513 --------- .../main/cpp/include/linux/freebsd/uinput.h | 232 ----- nativelib/src/main/cpp/include/linux/input.h | 5 - .../include/linux/linux/input-event-codes.h | 787 -------------- .../src/main/cpp/include/linux/linux/input.h | 207 ---- .../src/main/cpp/include/linux/linux/uinput.h | 231 ----- nativelib/src/main/cpp/include/linux/uinput.h | 5 - 8 files changed, 2960 deletions(-) delete mode 100644 nativelib/src/main/cpp/include/linux/freebsd/input-event-codes.h delete mode 100644 nativelib/src/main/cpp/include/linux/freebsd/input.h delete mode 100644 nativelib/src/main/cpp/include/linux/freebsd/uinput.h delete mode 100644 nativelib/src/main/cpp/include/linux/input.h delete mode 100644 nativelib/src/main/cpp/include/linux/linux/input-event-codes.h delete mode 100644 nativelib/src/main/cpp/include/linux/linux/input.h delete mode 100644 nativelib/src/main/cpp/include/linux/linux/uinput.h delete mode 100644 nativelib/src/main/cpp/include/linux/uinput.h diff --git a/nativelib/src/main/cpp/include/linux/freebsd/input-event-codes.h b/nativelib/src/main/cpp/include/linux/freebsd/input-event-codes.h deleted file mode 100644 index a4206723f5..0000000000 --- a/nativelib/src/main/cpp/include/linux/freebsd/input-event-codes.h +++ /dev/null @@ -1,980 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ -/* - * Input event codes - * - * *** IMPORTANT *** - * This file is not only included from C-code but also from devicetree source - * files. As such this file MUST only contain comments and defines. - * - * Copyright (c) 1999-2002 Vojtech Pavlik - * Copyright (c) 2015 Hans de Goede - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - */ -#ifndef _UAPI_INPUT_EVENT_CODES_H -#define _UAPI_INPUT_EVENT_CODES_H - -/* - * Device properties and quirks - */ - -#define INPUT_PROP_POINTER 0x00 /* needs a pointer */ -#define INPUT_PROP_DIRECT 0x01 /* direct input devices */ -#define INPUT_PROP_BUTTONPAD 0x02 /* has button(s) under pad */ -#define INPUT_PROP_SEMI_MT 0x03 /* touch rectangle only */ -#define INPUT_PROP_TOPBUTTONPAD 0x04 /* softbuttons at top of pad */ -#define INPUT_PROP_POINTING_STICK 0x05 /* is a pointing stick */ -#define INPUT_PROP_ACCELEROMETER 0x06 /* has accelerometer */ - -#define INPUT_PROP_MAX 0x1f -#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) - -/* - * Event types - */ - -#define EV_SYN 0x00 -#define EV_KEY 0x01 -#define EV_REL 0x02 -#define EV_ABS 0x03 -#define EV_MSC 0x04 -#define EV_SW 0x05 -#define EV_LED 0x11 -#define EV_SND 0x12 -#define EV_REP 0x14 -#define EV_FF 0x15 -#define EV_PWR 0x16 -#define EV_FF_STATUS 0x17 -#define EV_MAX 0x1f -#define EV_CNT (EV_MAX+1) - -/* - * Synchronization events. - */ - -#define SYN_REPORT 0 -#define SYN_CONFIG 1 -#define SYN_MT_REPORT 2 -#define SYN_DROPPED 3 -#define SYN_MAX 0xf -#define SYN_CNT (SYN_MAX+1) - -/* - * Keys and buttons - * - * Most of the keys/buttons are modeled after USB HUT 1.12 - * (see http://www.usb.org/developers/hidpage). - * Abbreviations in the comments: - * AC - Application Control - * AL - Application Launch Button - * SC - System Control - */ - -#define KEY_RESERVED 0 -#define KEY_ESC 1 -#define KEY_1 2 -#define KEY_2 3 -#define KEY_3 4 -#define KEY_4 5 -#define KEY_5 6 -#define KEY_6 7 -#define KEY_7 8 -#define KEY_8 9 -#define KEY_9 10 -#define KEY_0 11 -#define KEY_MINUS 12 -#define KEY_EQUAL 13 -#define KEY_BACKSPACE 14 -#define KEY_TAB 15 -#define KEY_Q 16 -#define KEY_W 17 -#define KEY_E 18 -#define KEY_R 19 -#define KEY_T 20 -#define KEY_Y 21 -#define KEY_U 22 -#define KEY_I 23 -#define KEY_O 24 -#define KEY_P 25 -#define KEY_LEFTBRACE 26 -#define KEY_RIGHTBRACE 27 -#define KEY_ENTER 28 -#define KEY_LEFTCTRL 29 -#define KEY_A 30 -#define KEY_S 31 -#define KEY_D 32 -#define KEY_F 33 -#define KEY_G 34 -#define KEY_H 35 -#define KEY_J 36 -#define KEY_K 37 -#define KEY_L 38 -#define KEY_SEMICOLON 39 -#define KEY_APOSTROPHE 40 -#define KEY_GRAVE 41 -#define KEY_LEFTSHIFT 42 -#define KEY_BACKSLASH 43 -#define KEY_Z 44 -#define KEY_X 45 -#define KEY_C 46 -#define KEY_V 47 -#define KEY_B 48 -#define KEY_N 49 -#define KEY_M 50 -#define KEY_COMMA 51 -#define KEY_DOT 52 -#define KEY_SLASH 53 -#define KEY_RIGHTSHIFT 54 -#define KEY_KPASTERISK 55 -#define KEY_LEFTALT 56 -#define KEY_SPACE 57 -#define KEY_CAPSLOCK 58 -#define KEY_F1 59 -#define KEY_F2 60 -#define KEY_F3 61 -#define KEY_F4 62 -#define KEY_F5 63 -#define KEY_F6 64 -#define KEY_F7 65 -#define KEY_F8 66 -#define KEY_F9 67 -#define KEY_F10 68 -#define KEY_NUMLOCK 69 -#define KEY_SCROLLLOCK 70 -#define KEY_KP7 71 -#define KEY_KP8 72 -#define KEY_KP9 73 -#define KEY_KPMINUS 74 -#define KEY_KP4 75 -#define KEY_KP5 76 -#define KEY_KP6 77 -#define KEY_KPPLUS 78 -#define KEY_KP1 79 -#define KEY_KP2 80 -#define KEY_KP3 81 -#define KEY_KP0 82 -#define KEY_KPDOT 83 - -#define KEY_ZENKAKUHANKAKU 85 -#define KEY_102ND 86 -#define KEY_F11 87 -#define KEY_F12 88 -#define KEY_RO 89 -#define KEY_KATAKANA 90 -#define KEY_HIRAGANA 91 -#define KEY_HENKAN 92 -#define KEY_KATAKANAHIRAGANA 93 -#define KEY_MUHENKAN 94 -#define KEY_KPJPCOMMA 95 -#define KEY_KPENTER 96 -#define KEY_RIGHTCTRL 97 -#define KEY_KPSLASH 98 -#define KEY_SYSRQ 99 -#define KEY_RIGHTALT 100 -#define KEY_LINEFEED 101 -#define KEY_HOME 102 -#define KEY_UP 103 -#define KEY_PAGEUP 104 -#define KEY_LEFT 105 -#define KEY_RIGHT 106 -#define KEY_END 107 -#define KEY_DOWN 108 -#define KEY_PAGEDOWN 109 -#define KEY_INSERT 110 -#define KEY_DELETE 111 -#define KEY_MACRO 112 -#define KEY_MUTE 113 -#define KEY_VOLUMEDOWN 114 -#define KEY_VOLUMEUP 115 -#define KEY_POWER 116 /* SC System Power Down */ -#define KEY_KPEQUAL 117 -#define KEY_KPPLUSMINUS 118 -#define KEY_PAUSE 119 -#define KEY_SCALE 120 /* AL Compiz Scale (Expose) */ - -#define KEY_KPCOMMA 121 -#define KEY_HANGEUL 122 -#define KEY_HANGUEL KEY_HANGEUL -#define KEY_HANJA 123 -#define KEY_YEN 124 -#define KEY_LEFTMETA 125 -#define KEY_RIGHTMETA 126 -#define KEY_COMPOSE 127 - -#define KEY_STOP 128 /* AC Stop */ -#define KEY_AGAIN 129 -#define KEY_PROPS 130 /* AC Properties */ -#define KEY_UNDO 131 /* AC Undo */ -#define KEY_FRONT 132 -#define KEY_COPY 133 /* AC Copy */ -#define KEY_OPEN 134 /* AC Open */ -#define KEY_PASTE 135 /* AC Paste */ -#define KEY_FIND 136 /* AC Search */ -#define KEY_CUT 137 /* AC Cut */ -#define KEY_HELP 138 /* AL Integrated Help Center */ -#define KEY_MENU 139 /* Menu (show menu) */ -#define KEY_CALC 140 /* AL Calculator */ -#define KEY_SETUP 141 -#define KEY_SLEEP 142 /* SC System Sleep */ -#define KEY_WAKEUP 143 /* System Wake Up */ -#define KEY_FILE 144 /* AL Local Machine Browser */ -#define KEY_SENDFILE 145 -#define KEY_DELETEFILE 146 -#define KEY_XFER 147 -#define KEY_PROG1 148 -#define KEY_PROG2 149 -#define KEY_WWW 150 /* AL Internet Browser */ -#define KEY_MSDOS 151 -#define KEY_COFFEE 152 /* AL Terminal Lock/Screensaver */ -#define KEY_SCREENLOCK KEY_COFFEE -#define KEY_ROTATE_DISPLAY 153 /* Display orientation for e.g. tablets */ -#define KEY_DIRECTION KEY_ROTATE_DISPLAY -#define KEY_CYCLEWINDOWS 154 -#define KEY_MAIL 155 -#define KEY_BOOKMARKS 156 /* AC Bookmarks */ -#define KEY_COMPUTER 157 -#define KEY_BACK 158 /* AC Back */ -#define KEY_FORWARD 159 /* AC Forward */ -#define KEY_CLOSECD 160 -#define KEY_EJECTCD 161 -#define KEY_EJECTCLOSECD 162 -#define KEY_NEXTSONG 163 -#define KEY_PLAYPAUSE 164 -#define KEY_PREVIOUSSONG 165 -#define KEY_STOPCD 166 -#define KEY_RECORD 167 -#define KEY_REWIND 168 -#define KEY_PHONE 169 /* Media Select Telephone */ -#define KEY_ISO 170 -#define KEY_CONFIG 171 /* AL Consumer Control Configuration */ -#define KEY_HOMEPAGE 172 /* AC Home */ -#define KEY_REFRESH 173 /* AC Refresh */ -#define KEY_EXIT 174 /* AC Exit */ -#define KEY_MOVE 175 -#define KEY_EDIT 176 -#define KEY_SCROLLUP 177 -#define KEY_SCROLLDOWN 178 -#define KEY_KPLEFTPAREN 179 -#define KEY_KPRIGHTPAREN 180 -#define KEY_NEW 181 /* AC New */ -#define KEY_REDO 182 /* AC Redo/Repeat */ - -#define KEY_F13 183 -#define KEY_F14 184 -#define KEY_F15 185 -#define KEY_F16 186 -#define KEY_F17 187 -#define KEY_F18 188 -#define KEY_F19 189 -#define KEY_F20 190 -#define KEY_F21 191 -#define KEY_F22 192 -#define KEY_F23 193 -#define KEY_F24 194 - -#define KEY_PLAYCD 200 -#define KEY_PAUSECD 201 -#define KEY_PROG3 202 -#define KEY_PROG4 203 -#define KEY_ALL_APPLICATIONS 204 /* AC Desktop Show All Applications */ -#define KEY_DASHBOARD KEY_ALL_APPLICATIONS -#define KEY_SUSPEND 205 -#define KEY_CLOSE 206 /* AC Close */ -#define KEY_PLAY 207 -#define KEY_FASTFORWARD 208 -#define KEY_BASSBOOST 209 -#define KEY_PRINT 210 /* AC Print */ -#define KEY_HP 211 -#define KEY_CAMERA 212 -#define KEY_SOUND 213 -#define KEY_QUESTION 214 -#define KEY_EMAIL 215 -#define KEY_CHAT 216 -#define KEY_SEARCH 217 -#define KEY_CONNECT 218 -#define KEY_FINANCE 219 /* AL Checkbook/Finance */ -#define KEY_SPORT 220 -#define KEY_SHOP 221 -#define KEY_ALTERASE 222 -#define KEY_CANCEL 223 /* AC Cancel */ -#define KEY_BRIGHTNESSDOWN 224 -#define KEY_BRIGHTNESSUP 225 -#define KEY_MEDIA 226 - -#define KEY_SWITCHVIDEOMODE 227 /* Cycle between available video - outputs (Monitor/LCD/TV-out/etc) */ -#define KEY_KBDILLUMTOGGLE 228 -#define KEY_KBDILLUMDOWN 229 -#define KEY_KBDILLUMUP 230 - -#define KEY_SEND 231 /* AC Send */ -#define KEY_REPLY 232 /* AC Reply */ -#define KEY_FORWARDMAIL 233 /* AC Forward Msg */ -#define KEY_SAVE 234 /* AC Save */ -#define KEY_DOCUMENTS 235 - -#define KEY_BATTERY 236 - -#define KEY_BLUETOOTH 237 -#define KEY_WLAN 238 -#define KEY_UWB 239 - -#define KEY_UNKNOWN 240 - -#define KEY_VIDEO_NEXT 241 /* drive next video source */ -#define KEY_VIDEO_PREV 242 /* drive previous video source */ -#define KEY_BRIGHTNESS_CYCLE 243 /* brightness up, after max is min */ -#define KEY_BRIGHTNESS_AUTO 244 /* Set Auto Brightness: manual - brightness control is off, - rely on ambient */ -#define KEY_BRIGHTNESS_ZERO KEY_BRIGHTNESS_AUTO -#define KEY_DISPLAY_OFF 245 /* display device to off state */ - -#define KEY_WWAN 246 /* Wireless WAN (LTE, UMTS, GSM, etc.) */ -#define KEY_WIMAX KEY_WWAN -#define KEY_RFKILL 247 /* Key that controls all radios */ - -#define KEY_MICMUTE 248 /* Mute / unmute the microphone */ - -/* Code 255 is reserved for special needs of AT keyboard driver */ - -#define BTN_MISC 0x100 -#define BTN_0 0x100 -#define BTN_1 0x101 -#define BTN_2 0x102 -#define BTN_3 0x103 -#define BTN_4 0x104 -#define BTN_5 0x105 -#define BTN_6 0x106 -#define BTN_7 0x107 -#define BTN_8 0x108 -#define BTN_9 0x109 - -#define BTN_MOUSE 0x110 -#define BTN_LEFT 0x110 -#define BTN_RIGHT 0x111 -#define BTN_MIDDLE 0x112 -#define BTN_SIDE 0x113 -#define BTN_EXTRA 0x114 -#define BTN_FORWARD 0x115 -#define BTN_BACK 0x116 -#define BTN_TASK 0x117 - -#define BTN_JOYSTICK 0x120 -#define BTN_TRIGGER 0x120 -#define BTN_THUMB 0x121 -#define BTN_THUMB2 0x122 -#define BTN_TOP 0x123 -#define BTN_TOP2 0x124 -#define BTN_PINKIE 0x125 -#define BTN_BASE 0x126 -#define BTN_BASE2 0x127 -#define BTN_BASE3 0x128 -#define BTN_BASE4 0x129 -#define BTN_BASE5 0x12a -#define BTN_BASE6 0x12b -#define BTN_DEAD 0x12f - -#define BTN_GAMEPAD 0x130 -#define BTN_SOUTH 0x130 -#define BTN_A BTN_SOUTH -#define BTN_EAST 0x131 -#define BTN_B BTN_EAST -#define BTN_C 0x132 -#define BTN_NORTH 0x133 -#define BTN_X BTN_NORTH -#define BTN_WEST 0x134 -#define BTN_Y BTN_WEST -#define BTN_Z 0x135 -#define BTN_TL 0x136 -#define BTN_TR 0x137 -#define BTN_TL2 0x138 -#define BTN_TR2 0x139 -#define BTN_SELECT 0x13a -#define BTN_START 0x13b -#define BTN_MODE 0x13c -#define BTN_THUMBL 0x13d -#define BTN_THUMBR 0x13e - -#define BTN_DIGI 0x140 -#define BTN_TOOL_PEN 0x140 -#define BTN_TOOL_RUBBER 0x141 -#define BTN_TOOL_BRUSH 0x142 -#define BTN_TOOL_PENCIL 0x143 -#define BTN_TOOL_AIRBRUSH 0x144 -#define BTN_TOOL_FINGER 0x145 -#define BTN_TOOL_MOUSE 0x146 -#define BTN_TOOL_LENS 0x147 -#define BTN_TOOL_QUINTTAP 0x148 /* Five fingers on trackpad */ -#define BTN_STYLUS3 0x149 -#define BTN_TOUCH 0x14a -#define BTN_STYLUS 0x14b -#define BTN_STYLUS2 0x14c -#define BTN_TOOL_DOUBLETAP 0x14d -#define BTN_TOOL_TRIPLETAP 0x14e -#define BTN_TOOL_QUADTAP 0x14f /* Four fingers on trackpad */ - -#define BTN_WHEEL 0x150 -#define BTN_GEAR_DOWN 0x150 -#define BTN_GEAR_UP 0x151 - -#define KEY_OK 0x160 -#define KEY_SELECT 0x161 -#define KEY_GOTO 0x162 -#define KEY_CLEAR 0x163 -#define KEY_POWER2 0x164 -#define KEY_OPTION 0x165 -#define KEY_INFO 0x166 /* AL OEM Features/Tips/Tutorial */ -#define KEY_TIME 0x167 -#define KEY_VENDOR 0x168 -#define KEY_ARCHIVE 0x169 -#define KEY_PROGRAM 0x16a /* Media Select Program Guide */ -#define KEY_CHANNEL 0x16b -#define KEY_FAVORITES 0x16c -#define KEY_EPG 0x16d -#define KEY_PVR 0x16e /* Media Select Home */ -#define KEY_MHP 0x16f -#define KEY_LANGUAGE 0x170 -#define KEY_TITLE 0x171 -#define KEY_SUBTITLE 0x172 -#define KEY_ANGLE 0x173 -#define KEY_FULL_SCREEN 0x174 /* AC View Toggle */ -#define KEY_ZOOM KEY_FULL_SCREEN -#define KEY_MODE 0x175 -#define KEY_KEYBOARD 0x176 -#define KEY_ASPECT_RATIO 0x177 /* HUTRR37: Aspect */ -#define KEY_SCREEN KEY_ASPECT_RATIO -#define KEY_PC 0x178 /* Media Select Computer */ -#define KEY_TV 0x179 /* Media Select TV */ -#define KEY_TV2 0x17a /* Media Select Cable */ -#define KEY_VCR 0x17b /* Media Select VCR */ -#define KEY_VCR2 0x17c /* VCR Plus */ -#define KEY_SAT 0x17d /* Media Select Satellite */ -#define KEY_SAT2 0x17e -#define KEY_CD 0x17f /* Media Select CD */ -#define KEY_TAPE 0x180 /* Media Select Tape */ -#define KEY_RADIO 0x181 -#define KEY_TUNER 0x182 /* Media Select Tuner */ -#define KEY_PLAYER 0x183 -#define KEY_TEXT 0x184 -#define KEY_DVD 0x185 /* Media Select DVD */ -#define KEY_AUX 0x186 -#define KEY_MP3 0x187 -#define KEY_AUDIO 0x188 /* AL Audio Browser */ -#define KEY_VIDEO 0x189 /* AL Movie Browser */ -#define KEY_DIRECTORY 0x18a -#define KEY_LIST 0x18b -#define KEY_MEMO 0x18c /* Media Select Messages */ -#define KEY_CALENDAR 0x18d -#define KEY_RED 0x18e -#define KEY_GREEN 0x18f -#define KEY_YELLOW 0x190 -#define KEY_BLUE 0x191 -#define KEY_CHANNELUP 0x192 /* Channel Increment */ -#define KEY_CHANNELDOWN 0x193 /* Channel Decrement */ -#define KEY_FIRST 0x194 -#define KEY_LAST 0x195 /* Recall Last */ -#define KEY_AB 0x196 -#define KEY_NEXT 0x197 -#define KEY_RESTART 0x198 -#define KEY_SLOW 0x199 -#define KEY_SHUFFLE 0x19a -#define KEY_BREAK 0x19b -#define KEY_PREVIOUS 0x19c -#define KEY_DIGITS 0x19d -#define KEY_TEEN 0x19e -#define KEY_TWEN 0x19f -#define KEY_VIDEOPHONE 0x1a0 /* Media Select Video Phone */ -#define KEY_GAMES 0x1a1 /* Media Select Games */ -#define KEY_ZOOMIN 0x1a2 /* AC Zoom In */ -#define KEY_ZOOMOUT 0x1a3 /* AC Zoom Out */ -#define KEY_ZOOMRESET 0x1a4 /* AC Zoom */ -#define KEY_WORDPROCESSOR 0x1a5 /* AL Word Processor */ -#define KEY_EDITOR 0x1a6 /* AL Text Editor */ -#define KEY_SPREADSHEET 0x1a7 /* AL Spreadsheet */ -#define KEY_GRAPHICSEDITOR 0x1a8 /* AL Graphics Editor */ -#define KEY_PRESENTATION 0x1a9 /* AL Presentation App */ -#define KEY_DATABASE 0x1aa /* AL Database App */ -#define KEY_NEWS 0x1ab /* AL Newsreader */ -#define KEY_VOICEMAIL 0x1ac /* AL Voicemail */ -#define KEY_ADDRESSBOOK 0x1ad /* AL Contacts/Address Book */ -#define KEY_MESSENGER 0x1ae /* AL Instant Messaging */ -#define KEY_DISPLAYTOGGLE 0x1af /* Turn display (LCD) on and off */ -#define KEY_BRIGHTNESS_TOGGLE KEY_DISPLAYTOGGLE -#define KEY_SPELLCHECK 0x1b0 /* AL Spell Check */ -#define KEY_LOGOFF 0x1b1 /* AL Logoff */ - -#define KEY_DOLLAR 0x1b2 -#define KEY_EURO 0x1b3 - -#define KEY_FRAMEBACK 0x1b4 /* Consumer - transport controls */ -#define KEY_FRAMEFORWARD 0x1b5 -#define KEY_CONTEXT_MENU 0x1b6 /* GenDesc - system context menu */ -#define KEY_MEDIA_REPEAT 0x1b7 /* Consumer - transport control */ -#define KEY_10CHANNELSUP 0x1b8 /* 10 channels up (10+) */ -#define KEY_10CHANNELSDOWN 0x1b9 /* 10 channels down (10-) */ -#define KEY_IMAGES 0x1ba /* AL Image Browser */ -#define KEY_NOTIFICATION_CENTER 0x1bc /* Show/hide the notification center */ -#define KEY_PICKUP_PHONE 0x1bd /* Answer incoming call */ -#define KEY_HANGUP_PHONE 0x1be /* Decline incoming call */ - -#define KEY_DEL_EOL 0x1c0 -#define KEY_DEL_EOS 0x1c1 -#define KEY_INS_LINE 0x1c2 -#define KEY_DEL_LINE 0x1c3 - -#define KEY_FN 0x1d0 -#define KEY_FN_ESC 0x1d1 -#define KEY_FN_F1 0x1d2 -#define KEY_FN_F2 0x1d3 -#define KEY_FN_F3 0x1d4 -#define KEY_FN_F4 0x1d5 -#define KEY_FN_F5 0x1d6 -#define KEY_FN_F6 0x1d7 -#define KEY_FN_F7 0x1d8 -#define KEY_FN_F8 0x1d9 -#define KEY_FN_F9 0x1da -#define KEY_FN_F10 0x1db -#define KEY_FN_F11 0x1dc -#define KEY_FN_F12 0x1dd -#define KEY_FN_1 0x1de -#define KEY_FN_2 0x1df -#define KEY_FN_D 0x1e0 -#define KEY_FN_E 0x1e1 -#define KEY_FN_F 0x1e2 -#define KEY_FN_S 0x1e3 -#define KEY_FN_B 0x1e4 -#define KEY_FN_RIGHT_SHIFT 0x1e5 - -#define KEY_BRL_DOT1 0x1f1 -#define KEY_BRL_DOT2 0x1f2 -#define KEY_BRL_DOT3 0x1f3 -#define KEY_BRL_DOT4 0x1f4 -#define KEY_BRL_DOT5 0x1f5 -#define KEY_BRL_DOT6 0x1f6 -#define KEY_BRL_DOT7 0x1f7 -#define KEY_BRL_DOT8 0x1f8 -#define KEY_BRL_DOT9 0x1f9 -#define KEY_BRL_DOT10 0x1fa - -#define KEY_NUMERIC_0 0x200 /* used by phones, remote controls, */ -#define KEY_NUMERIC_1 0x201 /* and other keypads */ -#define KEY_NUMERIC_2 0x202 -#define KEY_NUMERIC_3 0x203 -#define KEY_NUMERIC_4 0x204 -#define KEY_NUMERIC_5 0x205 -#define KEY_NUMERIC_6 0x206 -#define KEY_NUMERIC_7 0x207 -#define KEY_NUMERIC_8 0x208 -#define KEY_NUMERIC_9 0x209 -#define KEY_NUMERIC_STAR 0x20a -#define KEY_NUMERIC_POUND 0x20b -#define KEY_NUMERIC_A 0x20c /* Phone key A - HUT Telephony 0xb9 */ -#define KEY_NUMERIC_B 0x20d -#define KEY_NUMERIC_C 0x20e -#define KEY_NUMERIC_D 0x20f - -#define KEY_CAMERA_FOCUS 0x210 -#define KEY_WPS_BUTTON 0x211 /* WiFi Protected Setup key */ - -#define KEY_TOUCHPAD_TOGGLE 0x212 /* Request switch touchpad on or off */ -#define KEY_TOUCHPAD_ON 0x213 -#define KEY_TOUCHPAD_OFF 0x214 - -#define KEY_CAMERA_ZOOMIN 0x215 -#define KEY_CAMERA_ZOOMOUT 0x216 -#define KEY_CAMERA_UP 0x217 -#define KEY_CAMERA_DOWN 0x218 -#define KEY_CAMERA_LEFT 0x219 -#define KEY_CAMERA_RIGHT 0x21a - -#define KEY_ATTENDANT_ON 0x21b -#define KEY_ATTENDANT_OFF 0x21c -#define KEY_ATTENDANT_TOGGLE 0x21d /* Attendant call on or off */ -#define KEY_LIGHTS_TOGGLE 0x21e /* Reading light on or off */ - -#define BTN_DPAD_UP 0x220 -#define BTN_DPAD_DOWN 0x221 -#define BTN_DPAD_LEFT 0x222 -#define BTN_DPAD_RIGHT 0x223 - -#define KEY_ALS_TOGGLE 0x230 /* Ambient light sensor */ -#define KEY_ROTATE_LOCK_TOGGLE 0x231 /* Display rotation lock */ -#define KEY_REFRESH_RATE_TOGGLE 0x232 /* Display refresh rate toggle */ - -#define KEY_BUTTONCONFIG 0x240 /* AL Button Configuration */ -#define KEY_TASKMANAGER 0x241 /* AL Task/Project Manager */ -#define KEY_JOURNAL 0x242 /* AL Log/Journal/Timecard */ -#define KEY_CONTROLPANEL 0x243 /* AL Control Panel */ -#define KEY_APPSELECT 0x244 /* AL Select Task/Application */ -#define KEY_SCREENSAVER 0x245 /* AL Screen Saver */ -#define KEY_VOICECOMMAND 0x246 /* Listening Voice Command */ -#define KEY_ASSISTANT 0x247 /* AL Context-aware desktop assistant */ -#define KEY_KBD_LAYOUT_NEXT 0x248 /* AC Next Keyboard Layout Select */ -#define KEY_EMOJI_PICKER 0x249 /* Show/hide emoji picker (HUTRR101) */ -#define KEY_DICTATE 0x24a /* Start or Stop Voice Dictation Session (HUTRR99) */ -#define KEY_CAMERA_ACCESS_ENABLE 0x24b /* Enables programmatic access to camera devices. (HUTRR72) */ -#define KEY_CAMERA_ACCESS_DISABLE 0x24c /* Disables programmatic access to camera devices. (HUTRR72) */ -#define KEY_CAMERA_ACCESS_TOGGLE 0x24d /* Toggles the current state of the camera access control. (HUTRR72) */ -#define KEY_ACCESSIBILITY 0x24e /* Toggles the system bound accessibility UI/command (HUTRR116) */ -#define KEY_DO_NOT_DISTURB 0x24f /* Toggles the system-wide "Do Not Disturb" control (HUTRR94)*/ - -#define KEY_BRIGHTNESS_MIN 0x250 /* Set Brightness to Minimum */ -#define KEY_BRIGHTNESS_MAX 0x251 /* Set Brightness to Maximum */ - -#define KEY_KBDINPUTASSIST_PREV 0x260 -#define KEY_KBDINPUTASSIST_NEXT 0x261 -#define KEY_KBDINPUTASSIST_PREVGROUP 0x262 -#define KEY_KBDINPUTASSIST_NEXTGROUP 0x263 -#define KEY_KBDINPUTASSIST_ACCEPT 0x264 -#define KEY_KBDINPUTASSIST_CANCEL 0x265 - -/* Diagonal movement keys */ -#define KEY_RIGHT_UP 0x266 -#define KEY_RIGHT_DOWN 0x267 -#define KEY_LEFT_UP 0x268 -#define KEY_LEFT_DOWN 0x269 - -#define KEY_ROOT_MENU 0x26a /* Show Device's Root Menu */ -/* Show Top Menu of the Media (e.g. DVD) */ -#define KEY_MEDIA_TOP_MENU 0x26b -#define KEY_NUMERIC_11 0x26c -#define KEY_NUMERIC_12 0x26d -/* - * Toggle Audio Description: refers to an audio service that helps blind and - * visually impaired consumers understand the action in a program. Note: in - * some countries this is referred to as "Video Description". - */ -#define KEY_AUDIO_DESC 0x26e -#define KEY_3D_MODE 0x26f -#define KEY_NEXT_FAVORITE 0x270 -#define KEY_STOP_RECORD 0x271 -#define KEY_PAUSE_RECORD 0x272 -#define KEY_VOD 0x273 /* Video on Demand */ -#define KEY_UNMUTE 0x274 -#define KEY_FASTREVERSE 0x275 -#define KEY_SLOWREVERSE 0x276 -/* - * Control a data application associated with the currently viewed channel, - * e.g. teletext or data broadcast application (MHEG, MHP, HbbTV, etc.) - */ -#define KEY_DATA 0x277 -#define KEY_ONSCREEN_KEYBOARD 0x278 -/* Electronic privacy screen control */ -#define KEY_PRIVACY_SCREEN_TOGGLE 0x279 - -/* Select an area of screen to be copied */ -#define KEY_SELECTIVE_SCREENSHOT 0x27a - -/* Move the focus to the next or previous user controllable element within a UI container */ -#define KEY_NEXT_ELEMENT 0x27b -#define KEY_PREVIOUS_ELEMENT 0x27c - -/* Toggle Autopilot engagement */ -#define KEY_AUTOPILOT_ENGAGE_TOGGLE 0x27d - -/* Shortcut Keys */ -#define KEY_MARK_WAYPOINT 0x27e -#define KEY_SOS 0x27f -#define KEY_NAV_CHART 0x280 -#define KEY_FISHING_CHART 0x281 -#define KEY_SINGLE_RANGE_RADAR 0x282 -#define KEY_DUAL_RANGE_RADAR 0x283 -#define KEY_RADAR_OVERLAY 0x284 -#define KEY_TRADITIONAL_SONAR 0x285 -#define KEY_CLEARVU_SONAR 0x286 -#define KEY_SIDEVU_SONAR 0x287 -#define KEY_NAV_INFO 0x288 -#define KEY_BRIGHTNESS_MENU 0x289 - -/* - * Some keyboards have keys which do not have a defined meaning, these keys - * are intended to be programmed / bound to macros by the user. For most - * keyboards with these macro-keys the key-sequence to inject, or action to - * take, is all handled by software on the host side. So from the kernel's - * point of view these are just normal keys. - * - * The KEY_MACRO# codes below are intended for such keys, which may be labeled - * e.g. G1-G18, or S1 - S30. The KEY_MACRO# codes MUST NOT be used for keys - * where the marking on the key does indicate a defined meaning / purpose. - * - * The KEY_MACRO# codes MUST also NOT be used as fallback for when no existing - * KEY_FOO define matches the marking / purpose. In this case a new KEY_FOO - * define MUST be added. - */ -#define KEY_MACRO1 0x290 -#define KEY_MACRO2 0x291 -#define KEY_MACRO3 0x292 -#define KEY_MACRO4 0x293 -#define KEY_MACRO5 0x294 -#define KEY_MACRO6 0x295 -#define KEY_MACRO7 0x296 -#define KEY_MACRO8 0x297 -#define KEY_MACRO9 0x298 -#define KEY_MACRO10 0x299 -#define KEY_MACRO11 0x29a -#define KEY_MACRO12 0x29b -#define KEY_MACRO13 0x29c -#define KEY_MACRO14 0x29d -#define KEY_MACRO15 0x29e -#define KEY_MACRO16 0x29f -#define KEY_MACRO17 0x2a0 -#define KEY_MACRO18 0x2a1 -#define KEY_MACRO19 0x2a2 -#define KEY_MACRO20 0x2a3 -#define KEY_MACRO21 0x2a4 -#define KEY_MACRO22 0x2a5 -#define KEY_MACRO23 0x2a6 -#define KEY_MACRO24 0x2a7 -#define KEY_MACRO25 0x2a8 -#define KEY_MACRO26 0x2a9 -#define KEY_MACRO27 0x2aa -#define KEY_MACRO28 0x2ab -#define KEY_MACRO29 0x2ac -#define KEY_MACRO30 0x2ad - -/* - * Some keyboards with the macro-keys described above have some extra keys - * for controlling the host-side software responsible for the macro handling: - * -A macro recording start/stop key. Note that not all keyboards which emit - * KEY_MACRO_RECORD_START will also emit KEY_MACRO_RECORD_STOP if - * KEY_MACRO_RECORD_STOP is not advertised, then KEY_MACRO_RECORD_START - * should be interpreted as a recording start/stop toggle; - * -Keys for switching between different macro (pre)sets, either a key for - * cycling through the configured presets or keys to directly select a preset. - */ -#define KEY_MACRO_RECORD_START 0x2b0 -#define KEY_MACRO_RECORD_STOP 0x2b1 -#define KEY_MACRO_PRESET_CYCLE 0x2b2 -#define KEY_MACRO_PRESET1 0x2b3 -#define KEY_MACRO_PRESET2 0x2b4 -#define KEY_MACRO_PRESET3 0x2b5 - -/* - * Some keyboards have a buildin LCD panel where the contents are controlled - * by the host. Often these have a number of keys directly below the LCD - * intended for controlling a menu shown on the LCD. These keys often don't - * have any labeling so we just name them KEY_KBD_LCD_MENU# - */ -#define KEY_KBD_LCD_MENU1 0x2b8 -#define KEY_KBD_LCD_MENU2 0x2b9 -#define KEY_KBD_LCD_MENU3 0x2ba -#define KEY_KBD_LCD_MENU4 0x2bb -#define KEY_KBD_LCD_MENU5 0x2bc - -#define BTN_TRIGGER_HAPPY 0x2c0 -#define BTN_TRIGGER_HAPPY1 0x2c0 -#define BTN_TRIGGER_HAPPY2 0x2c1 -#define BTN_TRIGGER_HAPPY3 0x2c2 -#define BTN_TRIGGER_HAPPY4 0x2c3 -#define BTN_TRIGGER_HAPPY5 0x2c4 -#define BTN_TRIGGER_HAPPY6 0x2c5 -#define BTN_TRIGGER_HAPPY7 0x2c6 -#define BTN_TRIGGER_HAPPY8 0x2c7 -#define BTN_TRIGGER_HAPPY9 0x2c8 -#define BTN_TRIGGER_HAPPY10 0x2c9 -#define BTN_TRIGGER_HAPPY11 0x2ca -#define BTN_TRIGGER_HAPPY12 0x2cb -#define BTN_TRIGGER_HAPPY13 0x2cc -#define BTN_TRIGGER_HAPPY14 0x2cd -#define BTN_TRIGGER_HAPPY15 0x2ce -#define BTN_TRIGGER_HAPPY16 0x2cf -#define BTN_TRIGGER_HAPPY17 0x2d0 -#define BTN_TRIGGER_HAPPY18 0x2d1 -#define BTN_TRIGGER_HAPPY19 0x2d2 -#define BTN_TRIGGER_HAPPY20 0x2d3 -#define BTN_TRIGGER_HAPPY21 0x2d4 -#define BTN_TRIGGER_HAPPY22 0x2d5 -#define BTN_TRIGGER_HAPPY23 0x2d6 -#define BTN_TRIGGER_HAPPY24 0x2d7 -#define BTN_TRIGGER_HAPPY25 0x2d8 -#define BTN_TRIGGER_HAPPY26 0x2d9 -#define BTN_TRIGGER_HAPPY27 0x2da -#define BTN_TRIGGER_HAPPY28 0x2db -#define BTN_TRIGGER_HAPPY29 0x2dc -#define BTN_TRIGGER_HAPPY30 0x2dd -#define BTN_TRIGGER_HAPPY31 0x2de -#define BTN_TRIGGER_HAPPY32 0x2df -#define BTN_TRIGGER_HAPPY33 0x2e0 -#define BTN_TRIGGER_HAPPY34 0x2e1 -#define BTN_TRIGGER_HAPPY35 0x2e2 -#define BTN_TRIGGER_HAPPY36 0x2e3 -#define BTN_TRIGGER_HAPPY37 0x2e4 -#define BTN_TRIGGER_HAPPY38 0x2e5 -#define BTN_TRIGGER_HAPPY39 0x2e6 -#define BTN_TRIGGER_HAPPY40 0x2e7 - -/* We avoid low common keys in module aliases so they don't get huge. */ -#define KEY_MIN_INTERESTING KEY_MUTE -#define KEY_MAX 0x2ff -#define KEY_CNT (KEY_MAX+1) - -/* - * Relative axes - */ - -#define REL_X 0x00 -#define REL_Y 0x01 -#define REL_Z 0x02 -#define REL_RX 0x03 -#define REL_RY 0x04 -#define REL_RZ 0x05 -#define REL_HWHEEL 0x06 -#define REL_DIAL 0x07 -#define REL_WHEEL 0x08 -#define REL_MISC 0x09 -/* - * 0x0a is reserved and should not be used in input drivers. - * It was used by HID as REL_MISC+1 and userspace needs to detect if - * the next REL_* event is correct or is just REL_MISC + n. - * We define here REL_RESERVED so userspace can rely on it and detect - * the situation described above. - */ -#define REL_RESERVED 0x0a -#define REL_WHEEL_HI_RES 0x0b -#define REL_HWHEEL_HI_RES 0x0c -#define REL_MAX 0x0f -#define REL_CNT (REL_MAX+1) - -/* - * Absolute axes - */ - -#define ABS_X 0x00 -#define ABS_Y 0x01 -#define ABS_Z 0x02 -#define ABS_RX 0x03 -#define ABS_RY 0x04 -#define ABS_RZ 0x05 -#define ABS_THROTTLE 0x06 -#define ABS_RUDDER 0x07 -#define ABS_WHEEL 0x08 -#define ABS_GAS 0x09 -#define ABS_BRAKE 0x0a -#define ABS_HAT0X 0x10 -#define ABS_HAT0Y 0x11 -#define ABS_HAT1X 0x12 -#define ABS_HAT1Y 0x13 -#define ABS_HAT2X 0x14 -#define ABS_HAT2Y 0x15 -#define ABS_HAT3X 0x16 -#define ABS_HAT3Y 0x17 -#define ABS_PRESSURE 0x18 -#define ABS_DISTANCE 0x19 -#define ABS_TILT_X 0x1a -#define ABS_TILT_Y 0x1b -#define ABS_TOOL_WIDTH 0x1c - -#define ABS_VOLUME 0x20 -#define ABS_PROFILE 0x21 - -#define ABS_MISC 0x28 - -/* - * 0x2e is reserved and should not be used in input drivers. - * It was used by HID as ABS_MISC+6 and userspace needs to detect if - * the next ABS_* event is correct or is just ABS_MISC + n. - * We define here ABS_RESERVED so userspace can rely on it and detect - * the situation described above. - */ -#define ABS_RESERVED 0x2e - -#define ABS_MT_SLOT 0x2f /* MT slot being modified */ -#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */ -#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */ -#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */ -#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */ -#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */ -#define ABS_MT_POSITION_X 0x35 /* Center X touch position */ -#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */ -#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */ -#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */ -#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */ -#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */ -#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */ -#define ABS_MT_TOOL_X 0x3c /* Center X tool position */ -#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */ - - -#define ABS_MAX 0x3f -#define ABS_CNT (ABS_MAX+1) - -/* - * Switch events - */ - -#define SW_LID 0x00 /* set = lid shut */ -#define SW_TABLET_MODE 0x01 /* set = tablet mode */ -#define SW_HEADPHONE_INSERT 0x02 /* set = inserted */ -#define SW_RFKILL_ALL 0x03 /* rfkill master switch, type "any" - set = radio enabled */ -#define SW_RADIO SW_RFKILL_ALL /* deprecated */ -#define SW_MICROPHONE_INSERT 0x04 /* set = inserted */ -#define SW_DOCK 0x05 /* set = plugged into dock */ -#define SW_LINEOUT_INSERT 0x06 /* set = inserted */ -#define SW_JACK_PHYSICAL_INSERT 0x07 /* set = mechanical switch set */ -#define SW_VIDEOOUT_INSERT 0x08 /* set = inserted */ -#define SW_CAMERA_LENS_COVER 0x09 /* set = lens covered */ -#define SW_KEYPAD_SLIDE 0x0a /* set = keypad slide out */ -#define SW_FRONT_PROXIMITY 0x0b /* set = front proximity sensor active */ -#define SW_ROTATE_LOCK 0x0c /* set = rotate locked/disabled */ -#define SW_LINEIN_INSERT 0x0d /* set = inserted */ -#define SW_MUTE_DEVICE 0x0e /* set = device disabled */ -#define SW_PEN_INSERTED 0x0f /* set = pen inserted */ -#define SW_MACHINE_COVER 0x10 /* set = cover closed */ -#define SW_MAX 0x10 -#define SW_CNT (SW_MAX+1) - -/* - * Misc events - */ - -#define MSC_SERIAL 0x00 -#define MSC_PULSELED 0x01 -#define MSC_GESTURE 0x02 -#define MSC_RAW 0x03 -#define MSC_SCAN 0x04 -#define MSC_TIMESTAMP 0x05 -#define MSC_MAX 0x07 -#define MSC_CNT (MSC_MAX+1) - -/* - * LEDs - */ - -#define LED_NUML 0x00 -#define LED_CAPSL 0x01 -#define LED_SCROLLL 0x02 -#define LED_COMPOSE 0x03 -#define LED_KANA 0x04 -#define LED_SLEEP 0x05 -#define LED_SUSPEND 0x06 -#define LED_MUTE 0x07 -#define LED_MISC 0x08 -#define LED_MAIL 0x09 -#define LED_CHARGING 0x0a -#define LED_MAX 0x0f -#define LED_CNT (LED_MAX+1) - -/* - * Autorepeat values - */ - -#define REP_DELAY 0x00 -#define REP_PERIOD 0x01 -#define REP_MAX 0x01 -#define REP_CNT (REP_MAX+1) - -/* - * Sounds - */ - -#define SND_CLICK 0x00 -#define SND_BELL 0x01 -#define SND_TONE 0x02 -#define SND_MAX 0x07 -#define SND_CNT (SND_MAX+1) - -#endif diff --git a/nativelib/src/main/cpp/include/linux/freebsd/input.h b/nativelib/src/main/cpp/include/linux/freebsd/input.h deleted file mode 100644 index 2f4d401568..0000000000 --- a/nativelib/src/main/cpp/include/linux/freebsd/input.h +++ /dev/null @@ -1,513 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * Copyright (c) 1999-2002 Vojtech Pavlik - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - */ -#ifndef _UAPI_INPUT_H -#define _UAPI_INPUT_H - - -#ifndef __KERNEL__ -#include -#include -#include -#endif - -#include "input-event-codes.h" - -/* - * The event structure itself - * Note that __USE_TIME_BITS64 is defined by libc based on - * application's request to use 64 bit time_t. - */ - -struct input_event { -#if 1 /* (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__) */ - struct timeval time; -#define input_event_sec time.tv_sec -#define input_event_usec time.tv_usec -#else - __kernel_ulong_t __sec; -#if defined(__sparc__) && defined(__arch64__) - unsigned int __usec; - unsigned int __pad; -#else - __kernel_ulong_t __usec; -#endif -#define input_event_sec __sec -#define input_event_usec __usec -#endif - uint16_t type; - uint16_t code; - int32_t value; -}; - -/* - * Protocol version. - */ - -#define EV_VERSION 0x010001 - -/* - * IOCTLs (0x00 - 0x7f) - */ - -struct input_id { - uint16_t bustype; - uint16_t vendor; - uint16_t product; - uint16_t version; -}; - -/** - * struct input_absinfo - used by EVIOCGABS/EVIOCSABS ioctls - * @value: latest reported value for the axis. - * @minimum: specifies minimum value for the axis. - * @maximum: specifies maximum value for the axis. - * @fuzz: specifies fuzz value that is used to filter noise from - * the event stream. - * @flat: values that are within this value will be discarded by - * joydev interface and reported as 0 instead. - * @resolution: specifies resolution for the values reported for - * the axis. - * - * Note that input core does not clamp reported values to the - * [minimum, maximum] limits, such task is left to userspace. - * - * The default resolution for main axes (ABS_X, ABS_Y, ABS_Z) - * is reported in units per millimeter (units/mm), resolution - * for rotational axes (ABS_RX, ABS_RY, ABS_RZ) is reported - * in units per radian. - * When INPUT_PROP_ACCELEROMETER is set the resolution changes. - * The main axes (ABS_X, ABS_Y, ABS_Z) are then reported in - * in units per g (units/g) and in units per degree per second - * (units/deg/s) for rotational axes (ABS_RX, ABS_RY, ABS_RZ). - */ -struct input_absinfo { - int32_t value; - int32_t minimum; - int32_t maximum; - int32_t fuzz; - int32_t flat; - int32_t resolution; -}; - -/** - * struct input_keymap_entry - used by EVIOCGKEYCODE/EVIOCSKEYCODE ioctls - * @scancode: scancode represented in machine-endian form. - * @len: length of the scancode that resides in @scancode buffer. - * @index: index in the keymap, may be used instead of scancode - * @flags: allows to specify how kernel should handle the request. For - * example, setting INPUT_KEYMAP_BY_INDEX flag indicates that kernel - * should perform lookup in keymap by @index instead of @scancode - * @keycode: key code assigned to this scancode - * - * The structure is used to retrieve and modify keymap data. Users have - * option of performing lookup either by @scancode itself or by @index - * in keymap entry. EVIOCGKEYCODE will also return scancode or index - * (depending on which element was used to perform lookup). - */ -struct input_keymap_entry { -#define INPUT_KEYMAP_BY_INDEX (1 << 0) - uint8_t flags; - uint8_t len; - uint16_t index; - uint32_t keycode; - uint8_t scancode[32]; -}; - -struct input_mask { - uint32_t type; - uint32_t codes_size; - uint64_t codes_ptr; -}; - -#define EVIOCGVERSION _IOR('E', 0x01, int) /* get driver version */ -#define EVIOCGID _IOR('E', 0x02, struct input_id) /* get device ID */ -#define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) /* get repeat settings */ -#define EVIOCSREP _IOW('E', 0x03, unsigned int[2]) /* set repeat settings */ - -#define EVIOCGKEYCODE _IOWR('E', 0x04, unsigned int[2]) /* get keycode */ -#define EVIOCGKEYCODE_V2 _IOWR('E', 0x04, struct input_keymap_entry) -#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2]) /* set keycode */ -#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry) - -#define EVIOCGNAME(len) _IOC(IOC_OUT, 'E', 0x06, len) /* get device name */ -#define EVIOCGPHYS(len) _IOC(IOC_OUT, 'E', 0x07, len) /* get physical location */ -#define EVIOCGUNIQ(len) _IOC(IOC_OUT, 'E', 0x08, len) /* get unique identifier */ -#define EVIOCGPROP(len) _IOC(IOC_OUT, 'E', 0x09, len) /* get device properties */ - -/** - * EVIOCGMTSLOTS(len) - get MT slot values - * @len: size of the data buffer in bytes - * - * The ioctl buffer argument should be binary equivalent to - * - * struct input_mt_request_layout { - * uint32_t code; - * int32_t values[num_slots]; - * }; - * - * where num_slots is the (arbitrary) number of MT slots to extract. - * - * The ioctl size argument (len) is the size of the buffer, which - * should satisfy len = (num_slots + 1) * sizeof(int32_t). If len is - * too small to fit all available slots, the first num_slots are - * returned. - * - * Before the call, code is set to the wanted ABS_MT event type. On - * return, values[] is filled with the slot values for the specified - * ABS_MT code. - * - * If the request code is not an ABS_MT value, -EINVAL is returned. - */ -#define EVIOCGMTSLOTS(len) _IOC(IOC_INOUT, 'E', 0x0a, len) - -#define EVIOCGKEY(len) _IOC(IOC_OUT, 'E', 0x18, len) /* get global key state */ -#define EVIOCGLED(len) _IOC(IOC_OUT, 'E', 0x19, len) /* get all LEDs */ -#define EVIOCGSND(len) _IOC(IOC_OUT, 'E', 0x1a, len) /* get all sounds status */ -#define EVIOCGSW(len) _IOC(IOC_OUT, 'E', 0x1b, len) /* get all switch states */ - -#define EVIOCGBIT(ev,len) _IOC(IOC_OUT, 'E', 0x20 + (ev), len) /* get event bits */ -#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) /* get abs value/limits */ -#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo) /* set abs value/limits */ - -#define EVIOCSFF _IOW('E', 0x80, struct ff_effect) /* send a force effect to a force feedback device */ -#define EVIOCRMFF _IOWINT('E', 0x81) /* Erase a force effect */ -#define EVIOCGEFFECTS _IOR('E', 0x84, int) /* Report number of effects playable at the same time */ - -#define EVIOCGRAB _IOWINT('E', 0x90) /* Grab/Release device */ -#define EVIOCREVOKE _IOWINT('E', 0x91) /* Revoke device access */ - -/** - * EVIOCGMASK - Retrieve current event mask - * - * This ioctl allows user to retrieve the current event mask for specific - * event type. The argument must be of type "struct input_mask" and - * specifies the event type to query, the address of the receive buffer and - * the size of the receive buffer. - * - * The event mask is a per-client mask that specifies which events are - * forwarded to the client. Each event code is represented by a single bit - * in the event mask. If the bit is set, the event is passed to the client - * normally. Otherwise, the event is filtered and will never be queued on - * the client's receive buffer. - * - * Event masks do not affect global state of the input device. They only - * affect the file descriptor they are applied to. - * - * The default event mask for a client has all bits set, i.e. all events - * are forwarded to the client. If the kernel is queried for an unknown - * event type or if the receive buffer is larger than the number of - * event codes known to the kernel, the kernel returns all zeroes for those - * codes. - * - * At maximum, codes_size bytes are copied. - * - * This ioctl may fail with ENODEV in case the file is revoked, EFAULT - * if the receive-buffer points to invalid memory, or EINVAL if the kernel - * does not implement the ioctl. - */ -#define EVIOCGMASK _IOW('E', 0x92, struct input_mask) /* Get event-masks */ - -/** - * EVIOCSMASK - Set event mask - * - * This ioctl is the counterpart to EVIOCGMASK. Instead of receiving the - * current event mask, this changes the client's event mask for a specific - * type. See EVIOCGMASK for a description of event-masks and the - * argument-type. - * - * This ioctl provides full forward compatibility. If the passed event type - * is unknown to the kernel, or if the number of event codes specified in - * the mask is bigger than what is known to the kernel, the ioctl is still - * accepted and applied. However, any unknown codes are left untouched and - * stay cleared. That means, the kernel always filters unknown codes - * regardless of what the client requests. If the new mask doesn't cover - * all known event-codes, all remaining codes are automatically cleared and - * thus filtered. - * - * This ioctl may fail with ENODEV in case the file is revoked. EFAULT is - * returned if the receive-buffer points to invalid memory. EINVAL is returned - * if the kernel does not implement the ioctl. - */ -#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) /* Set event-masks */ - -#define EVIOCSCLOCKID _IOW('E', 0xa0, int) /* Set clockid to be used for timestamps */ - -/* - * IDs. - */ - -#define ID_BUS 0 -#define ID_VENDOR 1 -#define ID_PRODUCT 2 -#define ID_VERSION 3 - -#define BUS_PCI 0x01 -#define BUS_ISAPNP 0x02 -#define BUS_USB 0x03 -#define BUS_HIL 0x04 -#define BUS_BLUETOOTH 0x05 -#define BUS_VIRTUAL 0x06 - -#define BUS_ISA 0x10 -#define BUS_I8042 0x11 -#define BUS_XTKBD 0x12 -#define BUS_RS232 0x13 -#define BUS_GAMEPORT 0x14 -#define BUS_PARPORT 0x15 -#define BUS_AMIGA 0x16 -#define BUS_ADB 0x17 -#define BUS_I2C 0x18 -#define BUS_HOST 0x19 -#define BUS_GSC 0x1A -#define BUS_ATARI 0x1B -#define BUS_SPI 0x1C -#define BUS_RMI 0x1D -#define BUS_CEC 0x1E -#define BUS_INTEL_ISHTP 0x1F - -/* - * MT_TOOL types - */ -#define MT_TOOL_FINGER 0x00 -#define MT_TOOL_PEN 0x01 -#define MT_TOOL_PALM 0x02 -#define MT_TOOL_DIAL 0x0a -#define MT_TOOL_MAX 0x0f - -/* - * Values describing the status of a force-feedback effect - */ -#define FF_STATUS_STOPPED 0x00 -#define FF_STATUS_PLAYING 0x01 -#define FF_STATUS_MAX 0x01 - -/* - * Structures used in ioctls to upload effects to a device - * They are pieces of a bigger structure (called ff_effect) - */ - -/* - * All duration values are expressed in ms. Values above 32767 ms (0x7fff) - * should not be used and have unspecified results. - */ - -/** - * struct ff_replay - defines scheduling of the force-feedback effect - * @length: duration of the effect - * @delay: delay before effect should start playing - */ -struct ff_replay { - uint16_t length; - uint16_t delay; -}; - -/** - * struct ff_trigger - defines what triggers the force-feedback effect - * @button: number of the button triggering the effect - * @interval: controls how soon the effect can be re-triggered - */ -struct ff_trigger { - uint16_t button; - uint16_t interval; -}; - -/** - * struct ff_envelope - generic force-feedback effect envelope - * @attack_length: duration of the attack (ms) - * @attack_level: level at the beginning of the attack - * @fade_length: duration of fade (ms) - * @fade_level: level at the end of fade - * - * The @attack_level and @fade_level are absolute values; when applying - * envelope force-feedback core will convert to positive/negative - * value based on polarity of the default level of the effect. - * Valid range for the attack and fade levels is 0x0000 - 0x7fff - */ -struct ff_envelope { - uint16_t attack_length; - uint16_t attack_level; - uint16_t fade_length; - uint16_t fade_level; -}; - -/** - * struct ff_constant_effect - defines parameters of a constant force-feedback effect - * @level: strength of the effect; may be negative - * @envelope: envelope data - */ -struct ff_constant_effect { - int16_t level; - struct ff_envelope envelope; -}; - -/** - * struct ff_ramp_effect - defines parameters of a ramp force-feedback effect - * @start_level: beginning strength of the effect; may be negative - * @end_level: final strength of the effect; may be negative - * @envelope: envelope data - */ -struct ff_ramp_effect { - int16_t start_level; - int16_t end_level; - struct ff_envelope envelope; -}; - -/** - * struct ff_condition_effect - defines a spring or friction force-feedback effect - * @right_saturation: maximum level when joystick moved all way to the right - * @left_saturation: same for the left side - * @right_coeff: controls how fast the force grows when the joystick moves - * to the right - * @left_coeff: same for the left side - * @deadband: size of the dead zone, where no force is produced - * @center: position of the dead zone - */ -struct ff_condition_effect { - uint16_t right_saturation; - uint16_t left_saturation; - - int16_t right_coeff; - int16_t left_coeff; - - uint16_t deadband; - int16_t center; -}; - -/** - * struct ff_periodic_effect - defines parameters of a periodic force-feedback effect - * @waveform: kind of the effect (wave) - * @period: period of the wave (ms) - * @magnitude: peak value - * @offset: mean value of the wave (roughly) - * @phase: 'horizontal' shift - * @envelope: envelope data - * @custom_len: number of samples (FF_CUSTOM only) - * @custom_data: buffer of samples (FF_CUSTOM only) - * - * Known waveforms - FF_SQUARE, FF_TRIANGLE, FF_SINE, FF_SAW_UP, - * FF_SAW_DOWN, FF_CUSTOM. The exact syntax FF_CUSTOM is undefined - * for the time being as no driver supports it yet. - * - * Note: the data pointed by custom_data is copied by the driver. - * You can therefore dispose of the memory after the upload/update. - */ -struct ff_periodic_effect { - uint16_t waveform; - uint16_t period; - int16_t magnitude; - int16_t offset; - uint16_t phase; - - struct ff_envelope envelope; - - uint32_t custom_len; - int16_t *custom_data; -}; - -/** - * struct ff_rumble_effect - defines parameters of a periodic force-feedback effect - * @strong_magnitude: magnitude of the heavy motor - * @weak_magnitude: magnitude of the light one - * - * Some rumble pads have two motors of different weight. Strong_magnitude - * represents the magnitude of the vibration generated by the heavy one. - */ -struct ff_rumble_effect { - uint16_t strong_magnitude; - uint16_t weak_magnitude; -}; - -/** - * struct ff_effect - defines force feedback effect - * @type: type of the effect (FF_CONSTANT, FF_PERIODIC, FF_RAMP, FF_SPRING, - * FF_FRICTION, FF_DAMPER, FF_RUMBLE, FF_INERTIA, or FF_CUSTOM) - * @id: an unique id assigned to an effect - * @direction: direction of the effect - * @trigger: trigger conditions (struct ff_trigger) - * @replay: scheduling of the effect (struct ff_replay) - * @u: effect-specific structure (one of ff_constant_effect, ff_ramp_effect, - * ff_periodic_effect, ff_condition_effect, ff_rumble_effect) further - * defining effect parameters - * - * This structure is sent through ioctl from the application to the driver. - * To create a new effect application should set its @id to -1; the kernel - * will return assigned @id which can later be used to update or delete - * this effect. - * - * Direction of the effect is encoded as follows: - * 0 deg -> 0x0000 (down) - * 90 deg -> 0x4000 (left) - * 180 deg -> 0x8000 (up) - * 270 deg -> 0xC000 (right) - */ -struct ff_effect { - uint16_t type; - int16_t id; - uint16_t direction; - struct ff_trigger trigger; - struct ff_replay replay; - - union { - struct ff_constant_effect constant; - struct ff_ramp_effect ramp; - struct ff_periodic_effect periodic; - struct ff_condition_effect condition[2]; /* One for each axis */ - struct ff_rumble_effect rumble; - } u; -}; - -/* - * Force feedback effect types - */ - -#define FF_RUMBLE 0x50 -#define FF_PERIODIC 0x51 -#define FF_CONSTANT 0x52 -#define FF_SPRING 0x53 -#define FF_FRICTION 0x54 -#define FF_DAMPER 0x55 -#define FF_INERTIA 0x56 -#define FF_RAMP 0x57 - -#define FF_EFFECT_MIN FF_RUMBLE -#define FF_EFFECT_MAX FF_RAMP - -/* - * Force feedback periodic effect types - */ - -#define FF_SQUARE 0x58 -#define FF_TRIANGLE 0x59 -#define FF_SINE 0x5a -#define FF_SAW_UP 0x5b -#define FF_SAW_DOWN 0x5c -#define FF_CUSTOM 0x5d - -#define FF_WAVEFORM_MIN FF_SQUARE -#define FF_WAVEFORM_MAX FF_CUSTOM - -/* - * Set ff device properties - */ - -#define FF_GAIN 0x60 -#define FF_AUTOCENTER 0x61 - -/* - * ff->playback(effect_id = FF_GAIN) is the first effect_id to - * cause a collision with another ff method, in this case ff->set_gain(). - * Therefore the greatest safe value for effect_id is FF_GAIN - 1, - * and thus the total number of effects should never exceed FF_GAIN. - */ -#define FF_MAX_EFFECTS FF_GAIN - -#define FF_MAX 0x7f -#define FF_CNT (FF_MAX+1) - -#endif /* _UAPI_INPUT_H */ diff --git a/nativelib/src/main/cpp/include/linux/freebsd/uinput.h b/nativelib/src/main/cpp/include/linux/freebsd/uinput.h deleted file mode 100644 index fc6253c7fc..0000000000 --- a/nativelib/src/main/cpp/include/linux/freebsd/uinput.h +++ /dev/null @@ -1,232 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ -/* - * User level driver support for input subsystem - * - * Heavily based on evdev.c by Vojtech Pavlik - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Author: Aristeu Sergio Rozanski Filho - * - * Changes/Revisions: - * 0.5 08/13/2015 (David Herrmann & - * Benjamin Tissoires ) - * - add UI_DEV_SETUP ioctl - * - add UI_ABS_SETUP ioctl - * - add UI_GET_VERSION ioctl - * 0.4 01/09/2014 (Benjamin Tissoires ) - * - add UI_GET_SYSNAME ioctl - * 0.3 24/05/2006 (Anssi Hannula ) - * - update ff support for the changes in kernel interface - * - add UINPUT_VERSION - * 0.2 16/10/2004 (Micah Dowty ) - * - added force feedback support - * - added UI_SET_PHYS - * 0.1 20/06/2002 - * - first public version - */ -#ifndef _UAPI__UINPUT_H_ -#define _UAPI__UINPUT_H_ - -#include -#include - -#define UINPUT_VERSION 5 -#define UINPUT_MAX_NAME_SIZE 80 - -struct uinput_ff_upload { - uint32_t request_id; - int32_t retval; - struct ff_effect effect; - struct ff_effect old; -}; - -struct uinput_ff_erase { - uint32_t request_id; - int32_t retval; - uint32_t effect_id; -}; - -/* ioctl */ -#define UINPUT_IOCTL_BASE 'U' -#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1) -#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2) - -struct uinput_setup { - struct input_id id; - char name[UINPUT_MAX_NAME_SIZE]; - uint32_t ff_effects_max; -}; - -/** - * UI_DEV_SETUP - Set device parameters for setup - * - * This ioctl sets parameters for the input device to be created. It - * supersedes the old "struct uinput_user_dev" method, which wrote this data - * via write(). To actually set the absolute axes UI_ABS_SETUP should be - * used. - * - * The ioctl takes a "struct uinput_setup" object as argument. The fields of - * this object are as follows: - * id: See the description of "struct input_id". This field is - * copied unchanged into the new device. - * name: This is used unchanged as name for the new device. - * ff_effects_max: This limits the maximum numbers of force-feedback effects. - * See below for a description of FF with uinput. - * - * This ioctl can be called multiple times and will overwrite previous values. - * If this ioctl fails with -EINVAL, it is recommended to use the old - * "uinput_user_dev" method via write() as a fallback, in case you run on an - * old kernel that does not support this ioctl. - * - * This ioctl may fail with -EINVAL if it is not supported or if you passed - * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the - * passed uinput_setup object cannot be read/written. - * If this call fails, partial data may have already been applied to the - * internal device. - */ -#define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup) - -struct uinput_abs_setup { - uint16_t code; /* axis code */ - /* uint16_t filler; */ - struct input_absinfo absinfo; -}; - -/** - * UI_ABS_SETUP - Set absolute axis information for the device to setup - * - * This ioctl sets one absolute axis information for the input device to be - * created. It supersedes the old "struct uinput_user_dev" method, which wrote - * part of this data and the content of UI_DEV_SETUP via write(). - * - * The ioctl takes a "struct uinput_abs_setup" object as argument. The fields - * of this object are as follows: - * code: The corresponding input code associated with this axis - * (ABS_X, ABS_Y, etc...) - * absinfo: See "struct input_absinfo" for a description of this field. - * This field is copied unchanged into the kernel for the - * specified axis. If the axis is not enabled via - * UI_SET_ABSBIT, this ioctl will enable it. - * - * This ioctl can be called multiple times and will overwrite previous values. - * If this ioctl fails with -EINVAL, it is recommended to use the old - * "uinput_user_dev" method via write() as a fallback, in case you run on an - * old kernel that does not support this ioctl. - * - * This ioctl may fail with -EINVAL if it is not supported or if you passed - * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the - * passed uinput_setup object cannot be read/written. - * If this call fails, partial data may have already been applied to the - * internal device. - */ -#define UI_ABS_SETUP _IOW(UINPUT_IOCTL_BASE, 4, struct uinput_abs_setup) - -#define UI_SET_EVBIT _IOWINT(UINPUT_IOCTL_BASE, 100) -#define UI_SET_KEYBIT _IOWINT(UINPUT_IOCTL_BASE, 101) -#define UI_SET_RELBIT _IOWINT(UINPUT_IOCTL_BASE, 102) -#define UI_SET_ABSBIT _IOWINT(UINPUT_IOCTL_BASE, 103) -#define UI_SET_MSCBIT _IOWINT(UINPUT_IOCTL_BASE, 104) -#define UI_SET_LEDBIT _IOWINT(UINPUT_IOCTL_BASE, 105) -#define UI_SET_SNDBIT _IOWINT(UINPUT_IOCTL_BASE, 106) -#define UI_SET_FFBIT _IOWINT(UINPUT_IOCTL_BASE, 107) -#define UI_SET_PHYS _IO(UINPUT_IOCTL_BASE, 108) -#define UI_SET_SWBIT _IOWINT(UINPUT_IOCTL_BASE, 109) -#define UI_SET_PROPBIT _IOWINT(UINPUT_IOCTL_BASE, 110) - -#define UI_BEGIN_FF_UPLOAD _IOWR(UINPUT_IOCTL_BASE, 200, struct uinput_ff_upload) -#define UI_END_FF_UPLOAD _IOW(UINPUT_IOCTL_BASE, 201, struct uinput_ff_upload) -#define UI_BEGIN_FF_ERASE _IOWR(UINPUT_IOCTL_BASE, 202, struct uinput_ff_erase) -#define UI_END_FF_ERASE _IOW(UINPUT_IOCTL_BASE, 203, struct uinput_ff_erase) - -/** - * UI_GET_SYSNAME - get the sysfs name of the created uinput device - * - * @return the sysfs name of the created virtual input device. - * The complete sysfs path is then /sys/devices/virtual/input/--NAME-- - * Usually, it is in the form "inputN" - */ -#define UI_GET_SYSNAME(len) _IOC(IOC_OUT, UINPUT_IOCTL_BASE, 44, len) - -/** - * UI_GET_VERSION - Return version of uinput protocol - * - * This writes uinput protocol version implemented by the kernel into - * the integer pointed to by the ioctl argument. The protocol version - * is hard-coded in the kernel and is independent of the uinput device. - */ -#define UI_GET_VERSION _IOR(UINPUT_IOCTL_BASE, 45, unsigned int) - -/* - * To write a force-feedback-capable driver, the upload_effect - * and erase_effect callbacks in input_dev must be implemented. - * The uinput driver will generate a fake input event when one of - * these callbacks are invoked. The userspace code then uses - * ioctls to retrieve additional parameters and send the return code. - * The callback blocks until this return code is sent. - * - * The described callback mechanism is only used if ff_effects_max - * is set. - * - * To implement upload_effect(): - * 1. Wait for an event with type == EV_UINPUT and code == UI_FF_UPLOAD. - * A request ID will be given in 'value'. - * 2. Allocate a uinput_ff_upload struct, fill in request_id with - * the 'value' from the EV_UINPUT event. - * 3. Issue a UI_BEGIN_FF_UPLOAD ioctl, giving it the - * uinput_ff_upload struct. It will be filled in with the - * ff_effects passed to upload_effect(). - * 4. Perform the effect upload, and place a return code back into - the uinput_ff_upload struct. - * 5. Issue a UI_END_FF_UPLOAD ioctl, also giving it the - * uinput_ff_upload_effect struct. This will complete execution - * of our upload_effect() handler. - * - * To implement erase_effect(): - * 1. Wait for an event with type == EV_UINPUT and code == UI_FF_ERASE. - * A request ID will be given in 'value'. - * 2. Allocate a uinput_ff_erase struct, fill in request_id with - * the 'value' from the EV_UINPUT event. - * 3. Issue a UI_BEGIN_FF_ERASE ioctl, giving it the - * uinput_ff_erase struct. It will be filled in with the - * effect ID passed to erase_effect(). - * 4. Perform the effect erasure, and place a return code back - * into the uinput_ff_erase struct. - * 5. Issue a UI_END_FF_ERASE ioctl, also giving it the - * uinput_ff_erase_effect struct. This will complete execution - * of our erase_effect() handler. - */ - -/* - * This is the new event type, used only by uinput. - * 'code' is UI_FF_UPLOAD or UI_FF_ERASE, and 'value' - * is the unique request ID. This number was picked - * arbitrarily, above EV_MAX (since the input system - * never sees it) but in the range of a 16-bit int. - */ -#define EV_UINPUT 0x0101 -#define UI_FF_UPLOAD 1 -#define UI_FF_ERASE 2 - -struct uinput_user_dev { - char name[UINPUT_MAX_NAME_SIZE]; - struct input_id id; - uint32_t ff_effects_max; - int32_t absmax[ABS_CNT]; - int32_t absmin[ABS_CNT]; - int32_t absfuzz[ABS_CNT]; - int32_t absflat[ABS_CNT]; -}; -#endif /* _UAPI__UINPUT_H_ */ diff --git a/nativelib/src/main/cpp/include/linux/input.h b/nativelib/src/main/cpp/include/linux/input.h deleted file mode 100644 index 03c512efb0..0000000000 --- a/nativelib/src/main/cpp/include/linux/input.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifdef __linux__ -#include "linux/input.h" -#elif __FreeBSD__ -#include "freebsd/input.h" -#endif diff --git a/nativelib/src/main/cpp/include/linux/linux/input-event-codes.h b/nativelib/src/main/cpp/include/linux/linux/input-event-codes.h deleted file mode 100644 index 4f93d5ecea..0000000000 --- a/nativelib/src/main/cpp/include/linux/linux/input-event-codes.h +++ /dev/null @@ -1,787 +0,0 @@ -/* - * This file is auto-generated. Modifications will be lost. - * - * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/ - * for more information. - */ -#ifndef _UAPI_INPUT_EVENT_CODES_H -#define _UAPI_INPUT_EVENT_CODES_H -#define INPUT_PROP_POINTER 0x00 -#define INPUT_PROP_DIRECT 0x01 -#define INPUT_PROP_BUTTONPAD 0x02 -#define INPUT_PROP_SEMI_MT 0x03 -#define INPUT_PROP_TOPBUTTONPAD 0x04 -#define INPUT_PROP_POINTING_STICK 0x05 -#define INPUT_PROP_ACCELEROMETER 0x06 -#define INPUT_PROP_MAX 0x1f -#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) -#define EV_SYN 0x00 -#define EV_KEY 0x01 -#define EV_REL 0x02 -#define EV_ABS 0x03 -#define EV_MSC 0x04 -#define EV_SW 0x05 -#define EV_LED 0x11 -#define EV_SND 0x12 -#define EV_REP 0x14 -#define EV_FF 0x15 -#define EV_PWR 0x16 -#define EV_FF_STATUS 0x17 -#define EV_MAX 0x1f -#define EV_CNT (EV_MAX + 1) -#define SYN_REPORT 0 -#define SYN_CONFIG 1 -#define SYN_MT_REPORT 2 -#define SYN_DROPPED 3 -#define SYN_MAX 0xf -#define SYN_CNT (SYN_MAX + 1) -#define KEY_RESERVED 0 -#define KEY_ESC 1 -#define KEY_1 2 -#define KEY_2 3 -#define KEY_3 4 -#define KEY_4 5 -#define KEY_5 6 -#define KEY_6 7 -#define KEY_7 8 -#define KEY_8 9 -#define KEY_9 10 -#define KEY_0 11 -#define KEY_MINUS 12 -#define KEY_EQUAL 13 -#define KEY_BACKSPACE 14 -#define KEY_TAB 15 -#define KEY_Q 16 -#define KEY_W 17 -#define KEY_E 18 -#define KEY_R 19 -#define KEY_T 20 -#define KEY_Y 21 -#define KEY_U 22 -#define KEY_I 23 -#define KEY_O 24 -#define KEY_P 25 -#define KEY_LEFTBRACE 26 -#define KEY_RIGHTBRACE 27 -#define KEY_ENTER 28 -#define KEY_LEFTCTRL 29 -#define KEY_A 30 -#define KEY_S 31 -#define KEY_D 32 -#define KEY_F 33 -#define KEY_G 34 -#define KEY_H 35 -#define KEY_J 36 -#define KEY_K 37 -#define KEY_L 38 -#define KEY_SEMICOLON 39 -#define KEY_APOSTROPHE 40 -#define KEY_GRAVE 41 -#define KEY_LEFTSHIFT 42 -#define KEY_BACKSLASH 43 -#define KEY_Z 44 -#define KEY_X 45 -#define KEY_C 46 -#define KEY_V 47 -#define KEY_B 48 -#define KEY_N 49 -#define KEY_M 50 -#define KEY_COMMA 51 -#define KEY_DOT 52 -#define KEY_SLASH 53 -#define KEY_RIGHTSHIFT 54 -#define KEY_KPASTERISK 55 -#define KEY_LEFTALT 56 -#define KEY_SPACE 57 -#define KEY_CAPSLOCK 58 -#define KEY_F1 59 -#define KEY_F2 60 -#define KEY_F3 61 -#define KEY_F4 62 -#define KEY_F5 63 -#define KEY_F6 64 -#define KEY_F7 65 -#define KEY_F8 66 -#define KEY_F9 67 -#define KEY_F10 68 -#define KEY_NUMLOCK 69 -#define KEY_SCROLLLOCK 70 -#define KEY_KP7 71 -#define KEY_KP8 72 -#define KEY_KP9 73 -#define KEY_KPMINUS 74 -#define KEY_KP4 75 -#define KEY_KP5 76 -#define KEY_KP6 77 -#define KEY_KPPLUS 78 -#define KEY_KP1 79 -#define KEY_KP2 80 -#define KEY_KP3 81 -#define KEY_KP0 82 -#define KEY_KPDOT 83 -#define KEY_ZENKAKUHANKAKU 85 -#define KEY_102ND 86 -#define KEY_F11 87 -#define KEY_F12 88 -#define KEY_RO 89 -#define KEY_KATAKANA 90 -#define KEY_HIRAGANA 91 -#define KEY_HENKAN 92 -#define KEY_KATAKANAHIRAGANA 93 -#define KEY_MUHENKAN 94 -#define KEY_KPJPCOMMA 95 -#define KEY_KPENTER 96 -#define KEY_RIGHTCTRL 97 -#define KEY_KPSLASH 98 -#define KEY_SYSRQ 99 -#define KEY_RIGHTALT 100 -#define KEY_LINEFEED 101 -#define KEY_HOME 102 -#define KEY_UP 103 -#define KEY_PAGEUP 104 -#define KEY_LEFT 105 -#define KEY_RIGHT 106 -#define KEY_END 107 -#define KEY_DOWN 108 -#define KEY_PAGEDOWN 109 -#define KEY_INSERT 110 -#define KEY_DELETE 111 -#define KEY_MACRO 112 -#define KEY_MUTE 113 -#define KEY_VOLUMEDOWN 114 -#define KEY_VOLUMEUP 115 -#define KEY_POWER 116 -#define KEY_KPEQUAL 117 -#define KEY_KPPLUSMINUS 118 -#define KEY_PAUSE 119 -#define KEY_SCALE 120 -#define KEY_KPCOMMA 121 -#define KEY_HANGEUL 122 -#define KEY_HANGUEL KEY_HANGEUL -#define KEY_HANJA 123 -#define KEY_YEN 124 -#define KEY_LEFTMETA 125 -#define KEY_RIGHTMETA 126 -#define KEY_COMPOSE 127 -#define KEY_STOP 128 -#define KEY_AGAIN 129 -#define KEY_PROPS 130 -#define KEY_UNDO 131 -#define KEY_FRONT 132 -#define KEY_COPY 133 -#define KEY_OPEN 134 -#define KEY_PASTE 135 -#define KEY_FIND 136 -#define KEY_CUT 137 -#define KEY_HELP 138 -#define KEY_MENU 139 -#define KEY_CALC 140 -#define KEY_SETUP 141 -#define KEY_SLEEP 142 -#define KEY_WAKEUP 143 -#define KEY_FILE 144 -#define KEY_SENDFILE 145 -#define KEY_DELETEFILE 146 -#define KEY_XFER 147 -#define KEY_PROG1 148 -#define KEY_PROG2 149 -#define KEY_WWW 150 -#define KEY_MSDOS 151 -#define KEY_COFFEE 152 -#define KEY_SCREENLOCK KEY_COFFEE -#define KEY_ROTATE_DISPLAY 153 -#define KEY_DIRECTION KEY_ROTATE_DISPLAY -#define KEY_CYCLEWINDOWS 154 -#define KEY_MAIL 155 -#define KEY_BOOKMARKS 156 -#define KEY_COMPUTER 157 -#define KEY_BACK 158 -#define KEY_FORWARD 159 -#define KEY_CLOSECD 160 -#define KEY_EJECTCD 161 -#define KEY_EJECTCLOSECD 162 -#define KEY_NEXTSONG 163 -#define KEY_PLAYPAUSE 164 -#define KEY_PREVIOUSSONG 165 -#define KEY_STOPCD 166 -#define KEY_RECORD 167 -#define KEY_REWIND 168 -#define KEY_PHONE 169 -#define KEY_ISO 170 -#define KEY_CONFIG 171 -#define KEY_HOMEPAGE 172 -#define KEY_REFRESH 173 -#define KEY_EXIT 174 -#define KEY_MOVE 175 -#define KEY_EDIT 176 -#define KEY_SCROLLUP 177 -#define KEY_SCROLLDOWN 178 -#define KEY_KPLEFTPAREN 179 -#define KEY_KPRIGHTPAREN 180 -#define KEY_NEW 181 -#define KEY_REDO 182 -#define KEY_F13 183 -#define KEY_F14 184 -#define KEY_F15 185 -#define KEY_F16 186 -#define KEY_F17 187 -#define KEY_F18 188 -#define KEY_F19 189 -#define KEY_F20 190 -#define KEY_F21 191 -#define KEY_F22 192 -#define KEY_F23 193 -#define KEY_F24 194 -#define KEY_PLAYCD 200 -#define KEY_PAUSECD 201 -#define KEY_PROG3 202 -#define KEY_PROG4 203 -#define KEY_ALL_APPLICATIONS 204 -#define KEY_DASHBOARD KEY_ALL_APPLICATIONS -#define KEY_SUSPEND 205 -#define KEY_CLOSE 206 -#define KEY_PLAY 207 -#define KEY_FASTFORWARD 208 -#define KEY_BASSBOOST 209 -#define KEY_PRINT 210 -#define KEY_HP 211 -#define KEY_CAMERA 212 -#define KEY_SOUND 213 -#define KEY_QUESTION 214 -#define KEY_EMAIL 215 -#define KEY_CHAT 216 -#define KEY_SEARCH 217 -#define KEY_CONNECT 218 -#define KEY_FINANCE 219 -#define KEY_SPORT 220 -#define KEY_SHOP 221 -#define KEY_ALTERASE 222 -#define KEY_CANCEL 223 -#define KEY_BRIGHTNESSDOWN 224 -#define KEY_BRIGHTNESSUP 225 -#define KEY_MEDIA 226 -#define KEY_SWITCHVIDEOMODE 227 -#define KEY_KBDILLUMTOGGLE 228 -#define KEY_KBDILLUMDOWN 229 -#define KEY_KBDILLUMUP 230 -#define KEY_SEND 231 -#define KEY_REPLY 232 -#define KEY_FORWARDMAIL 233 -#define KEY_SAVE 234 -#define KEY_DOCUMENTS 235 -#define KEY_BATTERY 236 -#define KEY_BLUETOOTH 237 -#define KEY_WLAN 238 -#define KEY_UWB 239 -#define KEY_UNKNOWN 240 -#define KEY_VIDEO_NEXT 241 -#define KEY_VIDEO_PREV 242 -#define KEY_BRIGHTNESS_CYCLE 243 -#define KEY_BRIGHTNESS_AUTO 244 -#define KEY_BRIGHTNESS_ZERO KEY_BRIGHTNESS_AUTO -#define KEY_DISPLAY_OFF 245 -#define KEY_WWAN 246 -#define KEY_WIMAX KEY_WWAN -#define KEY_RFKILL 247 -#define KEY_MICMUTE 248 -#define BTN_MISC 0x100 -#define BTN_0 0x100 -#define BTN_1 0x101 -#define BTN_2 0x102 -#define BTN_3 0x103 -#define BTN_4 0x104 -#define BTN_5 0x105 -#define BTN_6 0x106 -#define BTN_7 0x107 -#define BTN_8 0x108 -#define BTN_9 0x109 -#define BTN_MOUSE 0x110 -#define BTN_LEFT 0x110 -#define BTN_RIGHT 0x111 -#define BTN_MIDDLE 0x112 -#define BTN_SIDE 0x113 -#define BTN_EXTRA 0x114 -#define BTN_FORWARD 0x115 -#define BTN_BACK 0x116 -#define BTN_TASK 0x117 -#define BTN_JOYSTICK 0x120 -#define BTN_TRIGGER 0x120 -#define BTN_THUMB 0x121 -#define BTN_THUMB2 0x122 -#define BTN_TOP 0x123 -#define BTN_TOP2 0x124 -#define BTN_PINKIE 0x125 -#define BTN_BASE 0x126 -#define BTN_BASE2 0x127 -#define BTN_BASE3 0x128 -#define BTN_BASE4 0x129 -#define BTN_BASE5 0x12a -#define BTN_BASE6 0x12b -#define BTN_DEAD 0x12f -#define BTN_GAMEPAD 0x130 -#define BTN_SOUTH 0x130 -#define BTN_A BTN_SOUTH -#define BTN_EAST 0x131 -#define BTN_B BTN_EAST -#define BTN_C 0x132 -#define BTN_NORTH 0x133 -#define BTN_X BTN_NORTH -#define BTN_WEST 0x134 -#define BTN_Y BTN_WEST -#define BTN_Z 0x135 -#define BTN_TL 0x136 -#define BTN_TR 0x137 -#define BTN_TL2 0x138 -#define BTN_TR2 0x139 -#define BTN_SELECT 0x13a -#define BTN_START 0x13b -#define BTN_MODE 0x13c -#define BTN_THUMBL 0x13d -#define BTN_THUMBR 0x13e -#define BTN_DIGI 0x140 -#define BTN_TOOL_PEN 0x140 -#define BTN_TOOL_RUBBER 0x141 -#define BTN_TOOL_BRUSH 0x142 -#define BTN_TOOL_PENCIL 0x143 -#define BTN_TOOL_AIRBRUSH 0x144 -#define BTN_TOOL_FINGER 0x145 -#define BTN_TOOL_MOUSE 0x146 -#define BTN_TOOL_LENS 0x147 -#define BTN_TOOL_QUINTTAP 0x148 -#define BTN_STYLUS3 0x149 -#define BTN_TOUCH 0x14a -#define BTN_STYLUS 0x14b -#define BTN_STYLUS2 0x14c -#define BTN_TOOL_DOUBLETAP 0x14d -#define BTN_TOOL_TRIPLETAP 0x14e -#define BTN_TOOL_QUADTAP 0x14f -#define BTN_WHEEL 0x150 -#define BTN_GEAR_DOWN 0x150 -#define BTN_GEAR_UP 0x151 -#define KEY_OK 0x160 -#define KEY_SELECT 0x161 -#define KEY_GOTO 0x162 -#define KEY_CLEAR 0x163 -#define KEY_POWER2 0x164 -#define KEY_OPTION 0x165 -#define KEY_INFO 0x166 -#define KEY_TIME 0x167 -#define KEY_VENDOR 0x168 -#define KEY_ARCHIVE 0x169 -#define KEY_PROGRAM 0x16a -#define KEY_CHANNEL 0x16b -#define KEY_FAVORITES 0x16c -#define KEY_EPG 0x16d -#define KEY_PVR 0x16e -#define KEY_MHP 0x16f -#define KEY_LANGUAGE 0x170 -#define KEY_TITLE 0x171 -#define KEY_SUBTITLE 0x172 -#define KEY_ANGLE 0x173 -#define KEY_FULL_SCREEN 0x174 -#define KEY_ZOOM KEY_FULL_SCREEN -#define KEY_MODE 0x175 -#define KEY_KEYBOARD 0x176 -#define KEY_ASPECT_RATIO 0x177 -#define KEY_SCREEN KEY_ASPECT_RATIO -#define KEY_PC 0x178 -#define KEY_TV 0x179 -#define KEY_TV2 0x17a -#define KEY_VCR 0x17b -#define KEY_VCR2 0x17c -#define KEY_SAT 0x17d -#define KEY_SAT2 0x17e -#define KEY_CD 0x17f -#define KEY_TAPE 0x180 -#define KEY_RADIO 0x181 -#define KEY_TUNER 0x182 -#define KEY_PLAYER 0x183 -#define KEY_TEXT 0x184 -#define KEY_DVD 0x185 -#define KEY_AUX 0x186 -#define KEY_MP3 0x187 -#define KEY_AUDIO 0x188 -#define KEY_VIDEO 0x189 -#define KEY_DIRECTORY 0x18a -#define KEY_LIST 0x18b -#define KEY_MEMO 0x18c -#define KEY_CALENDAR 0x18d -#define KEY_RED 0x18e -#define KEY_GREEN 0x18f -#define KEY_YELLOW 0x190 -#define KEY_BLUE 0x191 -#define KEY_CHANNELUP 0x192 -#define KEY_CHANNELDOWN 0x193 -#define KEY_FIRST 0x194 -#define KEY_LAST 0x195 -#define KEY_AB 0x196 -#define KEY_NEXT 0x197 -#define KEY_RESTART 0x198 -#define KEY_SLOW 0x199 -#define KEY_SHUFFLE 0x19a -#define KEY_BREAK 0x19b -#define KEY_PREVIOUS 0x19c -#define KEY_DIGITS 0x19d -#define KEY_TEEN 0x19e -#define KEY_TWEN 0x19f -#define KEY_VIDEOPHONE 0x1a0 -#define KEY_GAMES 0x1a1 -#define KEY_ZOOMIN 0x1a2 -#define KEY_ZOOMOUT 0x1a3 -#define KEY_ZOOMRESET 0x1a4 -#define KEY_WORDPROCESSOR 0x1a5 -#define KEY_EDITOR 0x1a6 -#define KEY_SPREADSHEET 0x1a7 -#define KEY_GRAPHICSEDITOR 0x1a8 -#define KEY_PRESENTATION 0x1a9 -#define KEY_DATABASE 0x1aa -#define KEY_NEWS 0x1ab -#define KEY_VOICEMAIL 0x1ac -#define KEY_ADDRESSBOOK 0x1ad -#define KEY_MESSENGER 0x1ae -#define KEY_DISPLAYTOGGLE 0x1af -#define KEY_BRIGHTNESS_TOGGLE KEY_DISPLAYTOGGLE -#define KEY_SPELLCHECK 0x1b0 -#define KEY_LOGOFF 0x1b1 -#define KEY_DOLLAR 0x1b2 -#define KEY_EURO 0x1b3 -#define KEY_FRAMEBACK 0x1b4 -#define KEY_FRAMEFORWARD 0x1b5 -#define KEY_CONTEXT_MENU 0x1b6 -#define KEY_MEDIA_REPEAT 0x1b7 -#define KEY_10CHANNELSUP 0x1b8 -#define KEY_10CHANNELSDOWN 0x1b9 -#define KEY_IMAGES 0x1ba -#define KEY_NOTIFICATION_CENTER 0x1bc -#define KEY_PICKUP_PHONE 0x1bd -#define KEY_HANGUP_PHONE 0x1be -#define KEY_DEL_EOL 0x1c0 -#define KEY_DEL_EOS 0x1c1 -#define KEY_INS_LINE 0x1c2 -#define KEY_DEL_LINE 0x1c3 -#define KEY_FN 0x1d0 -#define KEY_FN_ESC 0x1d1 -#define KEY_FN_F1 0x1d2 -#define KEY_FN_F2 0x1d3 -#define KEY_FN_F3 0x1d4 -#define KEY_FN_F4 0x1d5 -#define KEY_FN_F5 0x1d6 -#define KEY_FN_F6 0x1d7 -#define KEY_FN_F7 0x1d8 -#define KEY_FN_F8 0x1d9 -#define KEY_FN_F9 0x1da -#define KEY_FN_F10 0x1db -#define KEY_FN_F11 0x1dc -#define KEY_FN_F12 0x1dd -#define KEY_FN_1 0x1de -#define KEY_FN_2 0x1df -#define KEY_FN_D 0x1e0 -#define KEY_FN_E 0x1e1 -#define KEY_FN_F 0x1e2 -#define KEY_FN_S 0x1e3 -#define KEY_FN_B 0x1e4 -#define KEY_FN_RIGHT_SHIFT 0x1e5 -#define KEY_BRL_DOT1 0x1f1 -#define KEY_BRL_DOT2 0x1f2 -#define KEY_BRL_DOT3 0x1f3 -#define KEY_BRL_DOT4 0x1f4 -#define KEY_BRL_DOT5 0x1f5 -#define KEY_BRL_DOT6 0x1f6 -#define KEY_BRL_DOT7 0x1f7 -#define KEY_BRL_DOT8 0x1f8 -#define KEY_BRL_DOT9 0x1f9 -#define KEY_BRL_DOT10 0x1fa -#define KEY_NUMERIC_0 0x200 -#define KEY_NUMERIC_1 0x201 -#define KEY_NUMERIC_2 0x202 -#define KEY_NUMERIC_3 0x203 -#define KEY_NUMERIC_4 0x204 -#define KEY_NUMERIC_5 0x205 -#define KEY_NUMERIC_6 0x206 -#define KEY_NUMERIC_7 0x207 -#define KEY_NUMERIC_8 0x208 -#define KEY_NUMERIC_9 0x209 -#define KEY_NUMERIC_STAR 0x20a -#define KEY_NUMERIC_POUND 0x20b -#define KEY_NUMERIC_A 0x20c -#define KEY_NUMERIC_B 0x20d -#define KEY_NUMERIC_C 0x20e -#define KEY_NUMERIC_D 0x20f -#define KEY_CAMERA_FOCUS 0x210 -#define KEY_WPS_BUTTON 0x211 -#define KEY_TOUCHPAD_TOGGLE 0x212 -#define KEY_TOUCHPAD_ON 0x213 -#define KEY_TOUCHPAD_OFF 0x214 -#define KEY_CAMERA_ZOOMIN 0x215 -#define KEY_CAMERA_ZOOMOUT 0x216 -#define KEY_CAMERA_UP 0x217 -#define KEY_CAMERA_DOWN 0x218 -#define KEY_CAMERA_LEFT 0x219 -#define KEY_CAMERA_RIGHT 0x21a -#define KEY_ATTENDANT_ON 0x21b -#define KEY_ATTENDANT_OFF 0x21c -#define KEY_ATTENDANT_TOGGLE 0x21d -#define KEY_LIGHTS_TOGGLE 0x21e -#define BTN_DPAD_UP 0x220 -#define BTN_DPAD_DOWN 0x221 -#define BTN_DPAD_LEFT 0x222 -#define BTN_DPAD_RIGHT 0x223 -#define KEY_ALS_TOGGLE 0x230 -#define KEY_ROTATE_LOCK_TOGGLE 0x231 -#define KEY_REFRESH_RATE_TOGGLE 0x232 -#define KEY_BUTTONCONFIG 0x240 -#define KEY_TASKMANAGER 0x241 -#define KEY_JOURNAL 0x242 -#define KEY_CONTROLPANEL 0x243 -#define KEY_APPSELECT 0x244 -#define KEY_SCREENSAVER 0x245 -#define KEY_VOICECOMMAND 0x246 -#define KEY_ASSISTANT 0x247 -#define KEY_KBD_LAYOUT_NEXT 0x248 -#define KEY_EMOJI_PICKER 0x249 -#define KEY_DICTATE 0x24a -#define KEY_CAMERA_ACCESS_ENABLE 0x24b -#define KEY_CAMERA_ACCESS_DISABLE 0x24c -#define KEY_CAMERA_ACCESS_TOGGLE 0x24d -#define KEY_ACCESSIBILITY 0x24e -#define KEY_DO_NOT_DISTURB 0x24f -#define KEY_BRIGHTNESS_MIN 0x250 -#define KEY_BRIGHTNESS_MAX 0x251 -#define KEY_KBDINPUTASSIST_PREV 0x260 -#define KEY_KBDINPUTASSIST_NEXT 0x261 -#define KEY_KBDINPUTASSIST_PREVGROUP 0x262 -#define KEY_KBDINPUTASSIST_NEXTGROUP 0x263 -#define KEY_KBDINPUTASSIST_ACCEPT 0x264 -#define KEY_KBDINPUTASSIST_CANCEL 0x265 -#define KEY_RIGHT_UP 0x266 -#define KEY_RIGHT_DOWN 0x267 -#define KEY_LEFT_UP 0x268 -#define KEY_LEFT_DOWN 0x269 -#define KEY_ROOT_MENU 0x26a -#define KEY_MEDIA_TOP_MENU 0x26b -#define KEY_NUMERIC_11 0x26c -#define KEY_NUMERIC_12 0x26d -#define KEY_AUDIO_DESC 0x26e -#define KEY_3D_MODE 0x26f -#define KEY_NEXT_FAVORITE 0x270 -#define KEY_STOP_RECORD 0x271 -#define KEY_PAUSE_RECORD 0x272 -#define KEY_VOD 0x273 -#define KEY_UNMUTE 0x274 -#define KEY_FASTREVERSE 0x275 -#define KEY_SLOWREVERSE 0x276 -#define KEY_DATA 0x277 -#define KEY_ONSCREEN_KEYBOARD 0x278 -#define KEY_PRIVACY_SCREEN_TOGGLE 0x279 -#define KEY_SELECTIVE_SCREENSHOT 0x27a -#define KEY_NEXT_ELEMENT 0x27b -#define KEY_PREVIOUS_ELEMENT 0x27c -#define KEY_AUTOPILOT_ENGAGE_TOGGLE 0x27d -#define KEY_MARK_WAYPOINT 0x27e -#define KEY_SOS 0x27f -#define KEY_NAV_CHART 0x280 -#define KEY_FISHING_CHART 0x281 -#define KEY_SINGLE_RANGE_RADAR 0x282 -#define KEY_DUAL_RANGE_RADAR 0x283 -#define KEY_RADAR_OVERLAY 0x284 -#define KEY_TRADITIONAL_SONAR 0x285 -#define KEY_CLEARVU_SONAR 0x286 -#define KEY_SIDEVU_SONAR 0x287 -#define KEY_NAV_INFO 0x288 -#define KEY_BRIGHTNESS_MENU 0x289 -#define KEY_MACRO1 0x290 -#define KEY_MACRO2 0x291 -#define KEY_MACRO3 0x292 -#define KEY_MACRO4 0x293 -#define KEY_MACRO5 0x294 -#define KEY_MACRO6 0x295 -#define KEY_MACRO7 0x296 -#define KEY_MACRO8 0x297 -#define KEY_MACRO9 0x298 -#define KEY_MACRO10 0x299 -#define KEY_MACRO11 0x29a -#define KEY_MACRO12 0x29b -#define KEY_MACRO13 0x29c -#define KEY_MACRO14 0x29d -#define KEY_MACRO15 0x29e -#define KEY_MACRO16 0x29f -#define KEY_MACRO17 0x2a0 -#define KEY_MACRO18 0x2a1 -#define KEY_MACRO19 0x2a2 -#define KEY_MACRO20 0x2a3 -#define KEY_MACRO21 0x2a4 -#define KEY_MACRO22 0x2a5 -#define KEY_MACRO23 0x2a6 -#define KEY_MACRO24 0x2a7 -#define KEY_MACRO25 0x2a8 -#define KEY_MACRO26 0x2a9 -#define KEY_MACRO27 0x2aa -#define KEY_MACRO28 0x2ab -#define KEY_MACRO29 0x2ac -#define KEY_MACRO30 0x2ad -#define KEY_MACRO_RECORD_START 0x2b0 -#define KEY_MACRO_RECORD_STOP 0x2b1 -#define KEY_MACRO_PRESET_CYCLE 0x2b2 -#define KEY_MACRO_PRESET1 0x2b3 -#define KEY_MACRO_PRESET2 0x2b4 -#define KEY_MACRO_PRESET3 0x2b5 -#define KEY_KBD_LCD_MENU1 0x2b8 -#define KEY_KBD_LCD_MENU2 0x2b9 -#define KEY_KBD_LCD_MENU3 0x2ba -#define KEY_KBD_LCD_MENU4 0x2bb -#define KEY_KBD_LCD_MENU5 0x2bc -#define BTN_TRIGGER_HAPPY 0x2c0 -#define BTN_TRIGGER_HAPPY1 0x2c0 -#define BTN_TRIGGER_HAPPY2 0x2c1 -#define BTN_TRIGGER_HAPPY3 0x2c2 -#define BTN_TRIGGER_HAPPY4 0x2c3 -#define BTN_TRIGGER_HAPPY5 0x2c4 -#define BTN_TRIGGER_HAPPY6 0x2c5 -#define BTN_TRIGGER_HAPPY7 0x2c6 -#define BTN_TRIGGER_HAPPY8 0x2c7 -#define BTN_TRIGGER_HAPPY9 0x2c8 -#define BTN_TRIGGER_HAPPY10 0x2c9 -#define BTN_TRIGGER_HAPPY11 0x2ca -#define BTN_TRIGGER_HAPPY12 0x2cb -#define BTN_TRIGGER_HAPPY13 0x2cc -#define BTN_TRIGGER_HAPPY14 0x2cd -#define BTN_TRIGGER_HAPPY15 0x2ce -#define BTN_TRIGGER_HAPPY16 0x2cf -#define BTN_TRIGGER_HAPPY17 0x2d0 -#define BTN_TRIGGER_HAPPY18 0x2d1 -#define BTN_TRIGGER_HAPPY19 0x2d2 -#define BTN_TRIGGER_HAPPY20 0x2d3 -#define BTN_TRIGGER_HAPPY21 0x2d4 -#define BTN_TRIGGER_HAPPY22 0x2d5 -#define BTN_TRIGGER_HAPPY23 0x2d6 -#define BTN_TRIGGER_HAPPY24 0x2d7 -#define BTN_TRIGGER_HAPPY25 0x2d8 -#define BTN_TRIGGER_HAPPY26 0x2d9 -#define BTN_TRIGGER_HAPPY27 0x2da -#define BTN_TRIGGER_HAPPY28 0x2db -#define BTN_TRIGGER_HAPPY29 0x2dc -#define BTN_TRIGGER_HAPPY30 0x2dd -#define BTN_TRIGGER_HAPPY31 0x2de -#define BTN_TRIGGER_HAPPY32 0x2df -#define BTN_TRIGGER_HAPPY33 0x2e0 -#define BTN_TRIGGER_HAPPY34 0x2e1 -#define BTN_TRIGGER_HAPPY35 0x2e2 -#define BTN_TRIGGER_HAPPY36 0x2e3 -#define BTN_TRIGGER_HAPPY37 0x2e4 -#define BTN_TRIGGER_HAPPY38 0x2e5 -#define BTN_TRIGGER_HAPPY39 0x2e6 -#define BTN_TRIGGER_HAPPY40 0x2e7 -#define KEY_MIN_INTERESTING KEY_MUTE -#define KEY_MAX 0x2ff -#define KEY_CNT (KEY_MAX + 1) -#define REL_X 0x00 -#define REL_Y 0x01 -#define REL_Z 0x02 -#define REL_RX 0x03 -#define REL_RY 0x04 -#define REL_RZ 0x05 -#define REL_HWHEEL 0x06 -#define REL_DIAL 0x07 -#define REL_WHEEL 0x08 -#define REL_MISC 0x09 -#define REL_RESERVED 0x0a -#define REL_WHEEL_HI_RES 0x0b -#define REL_HWHEEL_HI_RES 0x0c -#define REL_MAX 0x0f -#define REL_CNT (REL_MAX + 1) -#define ABS_X 0x00 -#define ABS_Y 0x01 -#define ABS_Z 0x02 -#define ABS_RX 0x03 -#define ABS_RY 0x04 -#define ABS_RZ 0x05 -#define ABS_THROTTLE 0x06 -#define ABS_RUDDER 0x07 -#define ABS_WHEEL 0x08 -#define ABS_GAS 0x09 -#define ABS_BRAKE 0x0a -#define ABS_HAT0X 0x10 -#define ABS_HAT0Y 0x11 -#define ABS_HAT1X 0x12 -#define ABS_HAT1Y 0x13 -#define ABS_HAT2X 0x14 -#define ABS_HAT2Y 0x15 -#define ABS_HAT3X 0x16 -#define ABS_HAT3Y 0x17 -#define ABS_PRESSURE 0x18 -#define ABS_DISTANCE 0x19 -#define ABS_TILT_X 0x1a -#define ABS_TILT_Y 0x1b -#define ABS_TOOL_WIDTH 0x1c -#define ABS_VOLUME 0x20 -#define ABS_PROFILE 0x21 -#define ABS_MISC 0x28 -#define ABS_RESERVED 0x2e -#define ABS_MT_SLOT 0x2f -#define ABS_MT_TOUCH_MAJOR 0x30 -#define ABS_MT_TOUCH_MINOR 0x31 -#define ABS_MT_WIDTH_MAJOR 0x32 -#define ABS_MT_WIDTH_MINOR 0x33 -#define ABS_MT_ORIENTATION 0x34 -#define ABS_MT_POSITION_X 0x35 -#define ABS_MT_POSITION_Y 0x36 -#define ABS_MT_TOOL_TYPE 0x37 -#define ABS_MT_BLOB_ID 0x38 -#define ABS_MT_TRACKING_ID 0x39 -#define ABS_MT_PRESSURE 0x3a -#define ABS_MT_DISTANCE 0x3b -#define ABS_MT_TOOL_X 0x3c -#define ABS_MT_TOOL_Y 0x3d -#define ABS_MAX 0x3f -#define ABS_CNT (ABS_MAX + 1) -#define SW_LID 0x00 -#define SW_TABLET_MODE 0x01 -#define SW_HEADPHONE_INSERT 0x02 -#define SW_RFKILL_ALL 0x03 -#define SW_RADIO SW_RFKILL_ALL -#define SW_MICROPHONE_INSERT 0x04 -#define SW_DOCK 0x05 -#define SW_LINEOUT_INSERT 0x06 -#define SW_JACK_PHYSICAL_INSERT 0x07 -#define SW_VIDEOOUT_INSERT 0x08 -#define SW_CAMERA_LENS_COVER 0x09 -#define SW_KEYPAD_SLIDE 0x0a -#define SW_FRONT_PROXIMITY 0x0b -#define SW_ROTATE_LOCK 0x0c -#define SW_LINEIN_INSERT 0x0d -#define SW_MUTE_DEVICE 0x0e -#define SW_PEN_INSERTED 0x0f -#define SW_MACHINE_COVER 0x10 -#define SW_MAX 0x10 -#define SW_CNT (SW_MAX + 1) -#define MSC_SERIAL 0x00 -#define MSC_PULSELED 0x01 -#define MSC_GESTURE 0x02 -#define MSC_RAW 0x03 -#define MSC_SCAN 0x04 -#define MSC_TIMESTAMP 0x05 -#define MSC_MAX 0x07 -#define MSC_CNT (MSC_MAX + 1) -#define LED_NUML 0x00 -#define LED_CAPSL 0x01 -#define LED_SCROLLL 0x02 -#define LED_COMPOSE 0x03 -#define LED_KANA 0x04 -#define LED_SLEEP 0x05 -#define LED_SUSPEND 0x06 -#define LED_MUTE 0x07 -#define LED_MISC 0x08 -#define LED_MAIL 0x09 -#define LED_CHARGING 0x0a -#define LED_MAX 0x0f -#define LED_CNT (LED_MAX + 1) -#define REP_DELAY 0x00 -#define REP_PERIOD 0x01 -#define REP_MAX 0x01 -#define REP_CNT (REP_MAX + 1) -#define SND_CLICK 0x00 -#define SND_BELL 0x01 -#define SND_TONE 0x02 -#define SND_MAX 0x07 -#define SND_CNT (SND_MAX + 1) -#endif diff --git a/nativelib/src/main/cpp/include/linux/linux/input.h b/nativelib/src/main/cpp/include/linux/linux/input.h deleted file mode 100644 index df687f6044..0000000000 --- a/nativelib/src/main/cpp/include/linux/linux/input.h +++ /dev/null @@ -1,207 +0,0 @@ -/* - * This file is auto-generated. Modifications will be lost. - * - * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/ - * for more information. - */ -#ifndef _UAPI_INPUT_H -#define _UAPI_INPUT_H -#include -#include -#include -#include -#include "input-event-codes.h" -struct input_event { -#if __BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64) - struct timeval time; -#define input_event_sec time.tv_sec -#define input_event_usec time.tv_usec -#else - __kernel_ulong_t __sec; -#if defined(__sparc__) && defined(__arch64__) - unsigned int __usec; - unsigned int __pad; -#else - __kernel_ulong_t __usec; -#endif -#define input_event_sec __sec -#define input_event_usec __usec -#endif - __u16 type; - __u16 code; - __s32 value; -}; -#define EV_VERSION 0x010001 -struct input_id { - __u16 bustype; - __u16 vendor; - __u16 product; - __u16 version; -}; -struct input_absinfo { - __s32 value; - __s32 minimum; - __s32 maximum; - __s32 fuzz; - __s32 flat; - __s32 resolution; -}; -struct input_keymap_entry { -#define INPUT_KEYMAP_BY_INDEX (1 << 0) - __u8 flags; - __u8 len; - __u16 index; - __u32 keycode; - __u8 scancode[32]; -}; -struct input_mask { - __u32 type; - __u32 codes_size; - __u64 codes_ptr; -}; -#define EVIOCGVERSION _IOR('E', 0x01, int) -#define EVIOCGID _IOR('E', 0x02, struct input_id) -#define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) -#define EVIOCSREP _IOW('E', 0x03, unsigned int[2]) -#define EVIOCGKEYCODE _IOR('E', 0x04, unsigned int[2]) -#define EVIOCGKEYCODE_V2 _IOR('E', 0x04, struct input_keymap_entry) -#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2]) -#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry) -#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) -#define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len) -#define EVIOCGUNIQ(len) _IOC(_IOC_READ, 'E', 0x08, len) -#define EVIOCGPROP(len) _IOC(_IOC_READ, 'E', 0x09, len) -#define EVIOCGMTSLOTS(len) _IOC(_IOC_READ, 'E', 0x0a, len) -#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len) -#define EVIOCGLED(len) _IOC(_IOC_READ, 'E', 0x19, len) -#define EVIOCGSND(len) _IOC(_IOC_READ, 'E', 0x1a, len) -#define EVIOCGSW(len) _IOC(_IOC_READ, 'E', 0x1b, len) -#define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len) -#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) -#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo) -#define EVIOCSFF _IOW('E', 0x80, struct ff_effect) -#define EVIOCRMFF _IOW('E', 0x81, int) -#define EVIOCGEFFECTS _IOR('E', 0x84, int) -#define EVIOCGRAB _IOW('E', 0x90, int) -#define EVIOCREVOKE _IOW('E', 0x91, int) -#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) -#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) -#define EVIOCSCLOCKID _IOW('E', 0xa0, int) -#define ID_BUS 0 -#define ID_VENDOR 1 -#define ID_PRODUCT 2 -#define ID_VERSION 3 -#define BUS_PCI 0x01 -#define BUS_ISAPNP 0x02 -#define BUS_USB 0x03 -#define BUS_HIL 0x04 -#define BUS_BLUETOOTH 0x05 -#define BUS_VIRTUAL 0x06 -#define BUS_ISA 0x10 -#define BUS_I8042 0x11 -#define BUS_XTKBD 0x12 -#define BUS_RS232 0x13 -#define BUS_GAMEPORT 0x14 -#define BUS_PARPORT 0x15 -#define BUS_AMIGA 0x16 -#define BUS_ADB 0x17 -#define BUS_I2C 0x18 -#define BUS_HOST 0x19 -#define BUS_GSC 0x1A -#define BUS_ATARI 0x1B -#define BUS_SPI 0x1C -#define BUS_RMI 0x1D -#define BUS_CEC 0x1E -#define BUS_INTEL_ISHTP 0x1F -#define BUS_AMD_SFH 0x20 -#define MT_TOOL_FINGER 0x00 -#define MT_TOOL_PEN 0x01 -#define MT_TOOL_PALM 0x02 -#define MT_TOOL_DIAL 0x0a -#define MT_TOOL_MAX 0x0f -#define FF_STATUS_STOPPED 0x00 -#define FF_STATUS_PLAYING 0x01 -#define FF_STATUS_MAX 0x01 -struct ff_replay { - __u16 length; - __u16 delay; -}; -struct ff_trigger { - __u16 button; - __u16 interval; -}; -struct ff_envelope { - __u16 attack_length; - __u16 attack_level; - __u16 fade_length; - __u16 fade_level; -}; -struct ff_constant_effect { - __s16 level; - struct ff_envelope envelope; -}; -struct ff_ramp_effect { - __s16 start_level; - __s16 end_level; - struct ff_envelope envelope; -}; -struct ff_condition_effect { - __u16 right_saturation; - __u16 left_saturation; - __s16 right_coeff; - __s16 left_coeff; - __u16 deadband; - __s16 center; -}; -struct ff_periodic_effect { - __u16 waveform; - __u16 period; - __s16 magnitude; - __s16 offset; - __u16 phase; - struct ff_envelope envelope; - __u32 custom_len; - __s16 * custom_data; -}; -struct ff_rumble_effect { - __u16 strong_magnitude; - __u16 weak_magnitude; -}; -struct ff_effect { - __u16 type; - __s16 id; - __u16 direction; - struct ff_trigger trigger; - struct ff_replay replay; - union { - struct ff_constant_effect constant; - struct ff_ramp_effect ramp; - struct ff_periodic_effect periodic; - struct ff_condition_effect condition[2]; - struct ff_rumble_effect rumble; - } u; -}; -#define FF_RUMBLE 0x50 -#define FF_PERIODIC 0x51 -#define FF_CONSTANT 0x52 -#define FF_SPRING 0x53 -#define FF_FRICTION 0x54 -#define FF_DAMPER 0x55 -#define FF_INERTIA 0x56 -#define FF_RAMP 0x57 -#define FF_EFFECT_MIN FF_RUMBLE -#define FF_EFFECT_MAX FF_RAMP -#define FF_SQUARE 0x58 -#define FF_TRIANGLE 0x59 -#define FF_SINE 0x5a -#define FF_SAW_UP 0x5b -#define FF_SAW_DOWN 0x5c -#define FF_CUSTOM 0x5d -#define FF_WAVEFORM_MIN FF_SQUARE -#define FF_WAVEFORM_MAX FF_CUSTOM -#define FF_GAIN 0x60 -#define FF_AUTOCENTER 0x61 -#define FF_MAX_EFFECTS FF_GAIN -#define FF_MAX 0x7f -#define FF_CNT (FF_MAX + 1) -#endif diff --git a/nativelib/src/main/cpp/include/linux/linux/uinput.h b/nativelib/src/main/cpp/include/linux/linux/uinput.h deleted file mode 100644 index 434f02db66..0000000000 --- a/nativelib/src/main/cpp/include/linux/linux/uinput.h +++ /dev/null @@ -1,231 +0,0 @@ -/* - * User level driver support for input subsystem - * - * Heavily based on evdev.c by Vojtech Pavlik - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * Author: Aristeu Sergio Rozanski Filho - * - * Changes/Revisions: - * 0.5 08/13/2015 (David Herrmann & - * Benjamin Tissoires ) - * - add UI_DEV_SETUP ioctl - * - add UI_ABS_SETUP ioctl - * - add UI_GET_VERSION ioctl - * 0.4 01/09/2014 (Benjamin Tissoires ) - * - add UI_GET_SYSNAME ioctl - * 0.3 24/05/2006 (Anssi Hannula ) - * - update ff support for the changes in kernel interface - * - add UINPUT_VERSION - * 0.2 16/10/2004 (Micah Dowty ) - * - added force feedback support - * - added UI_SET_PHYS - * 0.1 20/06/2002 - * - first public version - */ -#ifndef __UINPUT_H_ -#define __UINPUT_H_ - -#include -#include - -#define UINPUT_VERSION 5 -#define UINPUT_MAX_NAME_SIZE 80 - -struct uinput_ff_upload { - __u32 request_id; - __s32 retval; - struct ff_effect effect; - struct ff_effect old; -}; - -struct uinput_ff_erase { - __u32 request_id; - __s32 retval; - __u32 effect_id; -}; - -/* ioctl */ -#define UINPUT_IOCTL_BASE 'U' -#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1) -#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2) - -struct uinput_setup { - struct input_id id; - char name[UINPUT_MAX_NAME_SIZE]; - __u32 ff_effects_max; -}; - -/** - * UI_DEV_SETUP - Set device parameters for setup - * - * This ioctl sets parameters for the input device to be created. It - * supersedes the old "struct uinput_user_dev" method, which wrote this data - * via write(). To actually set the absolute axes UI_ABS_SETUP should be - * used. - * - * The ioctl takes a "struct uinput_setup" object as argument. The fields of - * this object are as follows: - * id: See the description of "struct input_id". This field is - * copied unchanged into the new device. - * name: This is used unchanged as name for the new device. - * ff_effects_max: This limits the maximum numbers of force-feedback effects. - * See below for a description of FF with uinput. - * - * This ioctl can be called multiple times and will overwrite previous values. - * If this ioctl fails with -EINVAL, it is recommended to use the old - * "uinput_user_dev" method via write() as a fallback, in case you run on an - * old kernel that does not support this ioctl. - * - * This ioctl may fail with -EINVAL if it is not supported or if you passed - * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the - * passed uinput_setup object cannot be read/written. - * If this call fails, partial data may have already been applied to the - * internal device. - */ -#define UI_DEV_SETUP _IOW(UINPUT_IOCTL_BASE, 3, struct uinput_setup) - -struct uinput_abs_setup { - __u16 code; /* axis code */ - /* __u16 filler; */ - struct input_absinfo absinfo; -}; - -/** - * UI_ABS_SETUP - Set absolute axis information for the device to setup - * - * This ioctl sets one absolute axis information for the input device to be - * created. It supersedes the old "struct uinput_user_dev" method, which wrote - * part of this data and the content of UI_DEV_SETUP via write(). - * - * The ioctl takes a "struct uinput_abs_setup" object as argument. The fields - * of this object are as follows: - * code: The corresponding input code associated with this axis - * (ABS_X, ABS_Y, etc...) - * absinfo: See "struct input_absinfo" for a description of this field. - * This field is copied unchanged into the kernel for the - * specified axis. If the axis is not enabled via - * UI_SET_ABSBIT, this ioctl will enable it. - * - * This ioctl can be called multiple times and will overwrite previous values. - * If this ioctl fails with -EINVAL, it is recommended to use the old - * "uinput_user_dev" method via write() as a fallback, in case you run on an - * old kernel that does not support this ioctl. - * - * This ioctl may fail with -EINVAL if it is not supported or if you passed - * incorrect values, -ENOMEM if the kernel runs out of memory or -EFAULT if the - * passed uinput_setup object cannot be read/written. - * If this call fails, partial data may have already been applied to the - * internal device. - */ -#define UI_ABS_SETUP _IOW(UINPUT_IOCTL_BASE, 4, struct uinput_abs_setup) - -#define UI_SET_EVBIT _IOW(UINPUT_IOCTL_BASE, 100, int) -#define UI_SET_KEYBIT _IOW(UINPUT_IOCTL_BASE, 101, int) -#define UI_SET_RELBIT _IOW(UINPUT_IOCTL_BASE, 102, int) -#define UI_SET_ABSBIT _IOW(UINPUT_IOCTL_BASE, 103, int) -#define UI_SET_MSCBIT _IOW(UINPUT_IOCTL_BASE, 104, int) -#define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int) -#define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int) -#define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int) -#define UI_SET_PHYS _IOW(UINPUT_IOCTL_BASE, 108, char*) -#define UI_SET_SWBIT _IOW(UINPUT_IOCTL_BASE, 109, int) -#define UI_SET_PROPBIT _IOW(UINPUT_IOCTL_BASE, 110, int) - -#define UI_BEGIN_FF_UPLOAD _IOWR(UINPUT_IOCTL_BASE, 200, struct uinput_ff_upload) -#define UI_END_FF_UPLOAD _IOW(UINPUT_IOCTL_BASE, 201, struct uinput_ff_upload) -#define UI_BEGIN_FF_ERASE _IOWR(UINPUT_IOCTL_BASE, 202, struct uinput_ff_erase) -#define UI_END_FF_ERASE _IOW(UINPUT_IOCTL_BASE, 203, struct uinput_ff_erase) - -/** - * UI_GET_SYSNAME - get the sysfs name of the created uinput device - * - * @return the sysfs name of the created virtual input device. - * The complete sysfs path is then /sys/devices/virtual/input/--NAME-- - * Usually, it is in the form "inputN" - */ -#define UI_GET_SYSNAME(len) _IOC(_IOC_READ, UINPUT_IOCTL_BASE, 44, len) - -/** - * UI_GET_VERSION - Return version of uinput protocol - * - * This writes uinput protocol version implemented by the kernel into - * the integer pointed to by the ioctl argument. The protocol version - * is hard-coded in the kernel and is independent of the uinput device. - */ -#define UI_GET_VERSION _IOR(UINPUT_IOCTL_BASE, 45, unsigned int) - -/* - * To write a force-feedback-capable driver, the upload_effect - * and erase_effect callbacks in input_dev must be implemented. - * The uinput driver will generate a fake input event when one of - * these callbacks are invoked. The userspace code then uses - * ioctls to retrieve additional parameters and send the return code. - * The callback blocks until this return code is sent. - * - * The described callback mechanism is only used if ff_effects_max - * is set. - * - * To implement upload_effect(): - * 1. Wait for an event with type == EV_UINPUT and code == UI_FF_UPLOAD. - * A request ID will be given in 'value'. - * 2. Allocate a uinput_ff_upload struct, fill in request_id with - * the 'value' from the EV_UINPUT event. - * 3. Issue a UI_BEGIN_FF_UPLOAD ioctl, giving it the - * uinput_ff_upload struct. It will be filled in with the - * ff_effects passed to upload_effect(). - * 4. Perform the effect upload, and place a return code back into - the uinput_ff_upload struct. - * 5. Issue a UI_END_FF_UPLOAD ioctl, also giving it the - * uinput_ff_upload_effect struct. This will complete execution - * of our upload_effect() handler. - * - * To implement erase_effect(): - * 1. Wait for an event with type == EV_UINPUT and code == UI_FF_ERASE. - * A request ID will be given in 'value'. - * 2. Allocate a uinput_ff_erase struct, fill in request_id with - * the 'value' from the EV_UINPUT event. - * 3. Issue a UI_BEGIN_FF_ERASE ioctl, giving it the - * uinput_ff_erase struct. It will be filled in with the - * effect ID passed to erase_effect(). - * 4. Perform the effect erasure, and place a return code back - * into the uinput_ff_erase struct. - * 5. Issue a UI_END_FF_ERASE ioctl, also giving it the - * uinput_ff_erase_effect struct. This will complete execution - * of our erase_effect() handler. - */ - -/* - * This is the new event type, used only by uinput. - * 'code' is UI_FF_UPLOAD or UI_FF_ERASE, and 'value' - * is the unique request ID. This number was picked - * arbitrarily, above EV_MAX (since the input system - * never sees it) but in the range of a 16-bit int. - */ -#define EV_UINPUT 0x0101 -#define UI_FF_UPLOAD 1 -#define UI_FF_ERASE 2 - -struct uinput_user_dev { - char name[UINPUT_MAX_NAME_SIZE]; - struct input_id id; - __u32 ff_effects_max; - __s32 absmax[ABS_CNT]; - __s32 absmin[ABS_CNT]; - __s32 absfuzz[ABS_CNT]; - __s32 absflat[ABS_CNT]; -}; -#endif /* __UINPUT_H_ */ diff --git a/nativelib/src/main/cpp/include/linux/uinput.h b/nativelib/src/main/cpp/include/linux/uinput.h deleted file mode 100644 index 1ef4e3ba91..0000000000 --- a/nativelib/src/main/cpp/include/linux/uinput.h +++ /dev/null @@ -1,5 +0,0 @@ -#ifdef __linux__ -#include "linux/uinput.h" -#elif __FreeBSD__ -#include "freebsd/uinput.h" -#endif From 00eefa3f488d26653b2f535cabd11be3a7413a28 Mon Sep 17 00:00:00 2001 From: sds100 Date: Fri, 16 May 2025 00:12:01 +0200 Subject: [PATCH 95/95] #1394 create separate libraries for the evdev service and the ADB starter --- app/build.gradle | 1 + .../sds100/keymapper/BaseMainActivity.kt | 2 +- nativelib/build.gradle.kts | 8 -- .../nativelib/ExampleInstrumentedTest.kt | 24 ------ nativelib/src/main/cpp/CMakeLists.txt | 49 ----------- .../keymapper/nativelib/ExampleUnitTest.kt | 17 ---- privstarter/.gitignore | 2 + privstarter/build.gradle.kts | 78 +++++++++++++++++ privstarter/consumer-rules.pro | 0 privstarter/proguard-rules.pro | 21 +++++ privstarter/src/main/AndroidManifest.xml | 4 + privstarter/src/main/cpp/CMakeLists.txt | 86 +++++++++++++++++++ .../src/main/cpp/adb_pairing.cpp | 0 .../src/main/cpp/adb_pairing.h | 0 .../src/main/cpp/android.cpp | 0 .../src/main/cpp/android.h | 0 .../src/main/cpp/cgroup.cpp | 0 .../src/main/cpp/cgroup.h | 0 .../src/main/cpp/helper.cpp | 0 privstarter/src/main/cpp/logging.h | 30 +++++++ .../src/main/cpp/misc.cpp | 0 .../src/main/cpp/misc.h | 0 privstarter/src/main/cpp/privstarter.cpp | 9 ++ .../src/main/cpp/selinux.cpp | 0 .../src/main/cpp/selinux.h | 0 .../src/main/cpp/starter.cpp | 0 .../keymapper/privstarter}/adb/AdbClient.kt | 0 .../privstarter}/adb/AdbException.kt | 0 .../keymapper/privstarter}/adb/AdbKey.kt | 0 .../keymapper/privstarter}/adb/AdbMdns.kt | 0 .../keymapper/privstarter}/adb/AdbMessage.kt | 0 .../privstarter}/adb/AdbPairingClient.kt | 0 .../privstarter}/adb/AdbPairingService.kt | 2 +- .../keymapper/privstarter}/adb/AdbProtocol.kt | 0 .../keymapper/privstarter}/ktx/Context.kt | 0 .../sds100/keymapper/privstarter}/ktx/Log.kt | 0 .../keymapper/privstarter}/starter/Starter.kt | 2 +- .../src/main/res/raw/start.sh | 0 .../src/main/res/values/strings.xml | 0 settings.gradle | 1 + 40 files changed, 235 insertions(+), 101 deletions(-) delete mode 100644 nativelib/src/androidTest/java/io/github/sds100/keymapper/nativelib/ExampleInstrumentedTest.kt delete mode 100644 nativelib/src/test/java/io/github/sds100/keymapper/nativelib/ExampleUnitTest.kt create mode 100644 privstarter/.gitignore create mode 100644 privstarter/build.gradle.kts create mode 100644 privstarter/consumer-rules.pro create mode 100644 privstarter/proguard-rules.pro create mode 100644 privstarter/src/main/AndroidManifest.xml create mode 100644 privstarter/src/main/cpp/CMakeLists.txt rename {nativelib => privstarter}/src/main/cpp/adb_pairing.cpp (100%) rename {nativelib => privstarter}/src/main/cpp/adb_pairing.h (100%) rename {nativelib => privstarter}/src/main/cpp/android.cpp (100%) rename {nativelib => privstarter}/src/main/cpp/android.h (100%) rename {nativelib => privstarter}/src/main/cpp/cgroup.cpp (100%) rename {nativelib => privstarter}/src/main/cpp/cgroup.h (100%) rename {nativelib => privstarter}/src/main/cpp/helper.cpp (100%) create mode 100644 privstarter/src/main/cpp/logging.h rename {nativelib => privstarter}/src/main/cpp/misc.cpp (100%) rename {nativelib => privstarter}/src/main/cpp/misc.h (100%) create mode 100644 privstarter/src/main/cpp/privstarter.cpp rename {nativelib => privstarter}/src/main/cpp/selinux.cpp (100%) rename {nativelib => privstarter}/src/main/cpp/selinux.h (100%) rename {nativelib => privstarter}/src/main/cpp/starter.cpp (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/adb/AdbClient.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/adb/AdbException.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/adb/AdbKey.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/adb/AdbMdns.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/adb/AdbMessage.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/adb/AdbPairingClient.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/adb/AdbPairingService.kt (99%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/adb/AdbProtocol.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/ktx/Context.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/ktx/Log.kt (100%) rename {nativelib/src/main/java/io/github/sds100/keymapper/nativelib => privstarter/src/main/java/io/github/sds100/keymapper/privstarter}/starter/Starter.kt (98%) rename {nativelib => privstarter}/src/main/res/raw/start.sh (100%) rename {nativelib => privstarter}/src/main/res/values/strings.xml (100%) diff --git a/app/build.gradle b/app/build.gradle index e6561ff3e6..298cf2d825 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -163,6 +163,7 @@ android { dependencies { implementation fileTree(include: ["*.jar"], dir: "libs") implementation project(':nativelib') + implementation project(':privstarter') compileOnly project(":systemstubs") diff --git a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt index 8e05826d7e..667dd55bd8 100644 --- a/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt +++ b/app/src/main/java/io/github/sds100/keymapper/BaseMainActivity.kt @@ -246,7 +246,7 @@ abstract class BaseMainActivity : AppCompatActivity() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // startPairingService() -// startAdb("127.0.0.1", 35051) + startAdb("127.0.0.1", 33697) } // val userServiceArgs = diff --git a/nativelib/build.gradle.kts b/nativelib/build.gradle.kts index 8c3f037c53..78a158aa84 100644 --- a/nativelib/build.gradle.kts +++ b/nativelib/build.gradle.kts @@ -35,7 +35,6 @@ android { buildFeatures { aidl = true - prefab = true } externalNativeBuild { @@ -68,12 +67,8 @@ dependencies { compileOnly(project(":systemstubs")) implementation("org.conscrypt:conscrypt-android:2.5.3") implementation("androidx.core:core-ktx:1.16.0") - implementation("androidx.appcompat:appcompat:1.7.0") - implementation("com.google.android.material:material:1.12.0") // From Shizuku :manager module build.gradle file. - implementation("io.github.vvb2060.ndk:boringssl:20250114") - implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0") implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3") implementation("org.bouncycastle:bcpkix-jdk15on:1.70") implementation("me.zhanghai.android.appiconloader:appiconloader:1.5.0") @@ -85,7 +80,4 @@ dependencies { // annotationProcessor("dev.rikka.tools.refine:annotation-processor:4.3.0") // compileOnly("dev.rikka.tools.refine:annotation:4.3.0") - testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.2.1") - androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") } diff --git a/nativelib/src/androidTest/java/io/github/sds100/keymapper/nativelib/ExampleInstrumentedTest.kt b/nativelib/src/androidTest/java/io/github/sds100/keymapper/nativelib/ExampleInstrumentedTest.kt deleted file mode 100644 index 9a8dddd089..0000000000 --- a/nativelib/src/androidTest/java/io/github/sds100/keymapper/nativelib/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.github.sds100.keymapper.nativelib - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("io.github.sds100.keymapper.nativelib.test", appContext.packageName) - } -} \ No newline at end of file diff --git a/nativelib/src/main/cpp/CMakeLists.txt b/nativelib/src/main/cpp/CMakeLists.txt index fe6cab703b..417847fe0c 100644 --- a/nativelib/src/main/cpp/CMakeLists.txt +++ b/nativelib/src/main/cpp/CMakeLists.txt @@ -11,55 +11,6 @@ cmake_minimum_required(VERSION 3.22.1) # build script scope). project("nativelib") -# FROM SHIZUKU -set(CMAKE_CXX_STANDARD 17) - -set(C_FLAGS "-Werror=format -fdata-sections -ffunction-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics") -set(LINKER_FLAGS "-Wl,--hash-style=both") - -if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - message("Builing Release...") - - set(C_FLAGS "${C_FLAGS} -O2 -fvisibility=hidden -fvisibility-inlines-hidden") - set(LINKER_FLAGS "${LINKER_FLAGS} -Wl,-exclude-libs,ALL -Wl,--gc-sections") -else() - message("Builing Debug...") - - add_definitions(-DDEBUG) -endif () - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_FLAGS}") - -set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") -set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") - -find_library(log-lib log) -find_package(boringssl REQUIRED CONFIG) -find_package(cxx REQUIRED CONFIG) - -add_executable(libshizuku.so - starter.cpp misc.cpp selinux.cpp cgroup.cpp android.cpp) - -target_link_libraries(libshizuku.so ${log-lib} cxx::cxx) - -if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - add_custom_command(TARGET libshizuku.so POST_BUILD - COMMAND ${CMAKE_STRIP} --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libshizuku.so") -endif () - -add_library(adb SHARED - adb_pairing.cpp misc.cpp) - -target_link_libraries(adb ${log-lib} boringssl::crypto_static cxx::cxx) - -if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - add_custom_command(TARGET adb POST_BUILD - COMMAND ${CMAKE_STRIP} --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libadb.so") -endif () - -# END FROM SHIZUKU - # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. diff --git a/nativelib/src/test/java/io/github/sds100/keymapper/nativelib/ExampleUnitTest.kt b/nativelib/src/test/java/io/github/sds100/keymapper/nativelib/ExampleUnitTest.kt deleted file mode 100644 index 1f0be0cbd6..0000000000 --- a/nativelib/src/test/java/io/github/sds100/keymapper/nativelib/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package io.github.sds100.keymapper.nativelib - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/privstarter/.gitignore b/privstarter/.gitignore new file mode 100644 index 0000000000..c591fdeb45 --- /dev/null +++ b/privstarter/.gitignore @@ -0,0 +1,2 @@ +/build +.cxx \ No newline at end of file diff --git a/privstarter/build.gradle.kts b/privstarter/build.gradle.kts new file mode 100644 index 0000000000..6814a832a1 --- /dev/null +++ b/privstarter/build.gradle.kts @@ -0,0 +1,78 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "io.github.sds100.keymapper.privstarter" + compileSdk = 35 + + defaultConfig { + minSdk = 21 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + + externalNativeBuild { + cmake { + // -DANDROID_STL=none is required by Rikka's library: https://github.com/RikkaW/libcxx-prefab + // -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON is required to get the app running on the Android 15. This is related to the new 16kB page size support. + arguments("-DANDROID_STL=none", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON") + } + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + buildFeatures { + prefab = true + } + + externalNativeBuild { + cmake { + path("src/main/cpp/CMakeLists.txt") + version = "3.22.1" + } + } + + packaging { + jniLibs { + useLegacyPackaging = false + + // This is required on Android 15. Otherwise a java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH/DT_GNU_HASH error is thrown. + keepDebugSymbols.add("**/*.so") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = "11" + } +} + +dependencies { + + implementation("org.conscrypt:conscrypt-android:2.5.3") + implementation("androidx.core:core-ktx:1.16.0") + implementation("androidx.appcompat:appcompat:1.7.0") + + // From Shizuku :manager module build.gradle file. + implementation("io.github.vvb2060.ndk:boringssl:20250114") + implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0") + implementation("org.lsposed.hiddenapibypass:hiddenapibypass:4.3") + implementation("org.bouncycastle:bcpkix-jdk15on:1.70") + implementation("me.zhanghai.android.appiconloader:appiconloader:1.5.0") + implementation("dev.rikka.rikkax.core:core-ktx:1.4.1") +} \ No newline at end of file diff --git a/privstarter/consumer-rules.pro b/privstarter/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/privstarter/proguard-rules.pro b/privstarter/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/privstarter/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/privstarter/src/main/AndroidManifest.xml b/privstarter/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a5918e68ab --- /dev/null +++ b/privstarter/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/privstarter/src/main/cpp/CMakeLists.txt b/privstarter/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000..f9d9670ce2 --- /dev/null +++ b/privstarter/src/main/cpp/CMakeLists.txt @@ -0,0 +1,86 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("privstarter") + +# FROM SHIZUKU +set(CMAKE_CXX_STANDARD 17) + +set(C_FLAGS "-Werror=format -fdata-sections -ffunction-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics") +set(LINKER_FLAGS "-Wl,--hash-style=both") + +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + message("Builing Release...") + + set(C_FLAGS "${C_FLAGS} -O2 -fvisibility=hidden -fvisibility-inlines-hidden") + set(LINKER_FLAGS "${LINKER_FLAGS} -Wl,-exclude-libs,ALL -Wl,--gc-sections") +else() + message("Builing Debug...") + + add_definitions(-DDEBUG) +endif () + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_FLAGS}") + +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") +set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") + +find_library(log-lib log) +find_package(boringssl REQUIRED CONFIG) +find_package(cxx REQUIRED CONFIG) + +add_executable(libshizuku.so + starter.cpp misc.cpp selinux.cpp cgroup.cpp android.cpp) + +target_link_libraries(libshizuku.so ${log-lib} cxx::cxx) + +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + add_custom_command(TARGET libshizuku.so POST_BUILD + COMMAND ${CMAKE_STRIP} --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libshizuku.so") +endif () + +add_library(adb SHARED + adb_pairing.cpp misc.cpp) + +target_link_libraries(adb ${log-lib} boringssl::crypto_static cxx::cxx) + +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + add_custom_command(TARGET adb POST_BUILD + COMMAND ${CMAKE_STRIP} --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libadb.so") +endif () + +# END FROM SHIZUKU +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. +# +# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +# is preferred for the same purpose. +# +# In order to load a library into your app from Java/Kotlin, you must call +# System.loadLibrary() and pass the name of the library defined here; +# for GameActivity/NativeActivity derived applications, the same library name must be +# used in the AndroidManifest.xml file. +add_library(${CMAKE_PROJECT_NAME} SHARED + # List C/C++ source files with relative paths to this CMakeLists.txt. + privstarter.cpp) + +# Specifies libraries CMake should link to your target library. You +# can link libraries from various origins, such as libraries defined in this +# build script, prebuilt third-party libraries, or Android system libraries. +target_link_libraries(${CMAKE_PROJECT_NAME} + # List libraries link to the target library + android + log) + diff --git a/nativelib/src/main/cpp/adb_pairing.cpp b/privstarter/src/main/cpp/adb_pairing.cpp similarity index 100% rename from nativelib/src/main/cpp/adb_pairing.cpp rename to privstarter/src/main/cpp/adb_pairing.cpp diff --git a/nativelib/src/main/cpp/adb_pairing.h b/privstarter/src/main/cpp/adb_pairing.h similarity index 100% rename from nativelib/src/main/cpp/adb_pairing.h rename to privstarter/src/main/cpp/adb_pairing.h diff --git a/nativelib/src/main/cpp/android.cpp b/privstarter/src/main/cpp/android.cpp similarity index 100% rename from nativelib/src/main/cpp/android.cpp rename to privstarter/src/main/cpp/android.cpp diff --git a/nativelib/src/main/cpp/android.h b/privstarter/src/main/cpp/android.h similarity index 100% rename from nativelib/src/main/cpp/android.h rename to privstarter/src/main/cpp/android.h diff --git a/nativelib/src/main/cpp/cgroup.cpp b/privstarter/src/main/cpp/cgroup.cpp similarity index 100% rename from nativelib/src/main/cpp/cgroup.cpp rename to privstarter/src/main/cpp/cgroup.cpp diff --git a/nativelib/src/main/cpp/cgroup.h b/privstarter/src/main/cpp/cgroup.h similarity index 100% rename from nativelib/src/main/cpp/cgroup.h rename to privstarter/src/main/cpp/cgroup.h diff --git a/nativelib/src/main/cpp/helper.cpp b/privstarter/src/main/cpp/helper.cpp similarity index 100% rename from nativelib/src/main/cpp/helper.cpp rename to privstarter/src/main/cpp/helper.cpp diff --git a/privstarter/src/main/cpp/logging.h b/privstarter/src/main/cpp/logging.h new file mode 100644 index 0000000000..91a750f4a0 --- /dev/null +++ b/privstarter/src/main/cpp/logging.h @@ -0,0 +1,30 @@ +#ifndef _LOGGING_H +#define _LOGGING_H + +#include +#include "android/log.h" + +#ifndef LOG_TAG +#define LOG_TAG "Key Mapper" +#endif + +#ifndef NO_LOG +#ifndef NO_DEBUG_LOG +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#else +#define LOGD(...) +#endif +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) +#else +#define LOGD(...) +#define LOGV(...) +#define LOGI(...) +#define LOGW(...) +#define LOGE(...) +#define PLOGE(fmt, args...) +#endif +#endif // _LOGGING_H diff --git a/nativelib/src/main/cpp/misc.cpp b/privstarter/src/main/cpp/misc.cpp similarity index 100% rename from nativelib/src/main/cpp/misc.cpp rename to privstarter/src/main/cpp/misc.cpp diff --git a/nativelib/src/main/cpp/misc.h b/privstarter/src/main/cpp/misc.h similarity index 100% rename from nativelib/src/main/cpp/misc.h rename to privstarter/src/main/cpp/misc.h diff --git a/privstarter/src/main/cpp/privstarter.cpp b/privstarter/src/main/cpp/privstarter.cpp new file mode 100644 index 0000000000..303a24597b --- /dev/null +++ b/privstarter/src/main/cpp/privstarter.cpp @@ -0,0 +1,9 @@ +#include + +extern "C" JNIEXPORT jstring JNICALL +Java_io_github_sds100_keymapper_privstarter_NativeLib_stringFromJNI( + JNIEnv* env, + jobject /* this */) { + char* hello = "Hello from C++"; + return env->NewStringUTF(hello); +} \ No newline at end of file diff --git a/nativelib/src/main/cpp/selinux.cpp b/privstarter/src/main/cpp/selinux.cpp similarity index 100% rename from nativelib/src/main/cpp/selinux.cpp rename to privstarter/src/main/cpp/selinux.cpp diff --git a/nativelib/src/main/cpp/selinux.h b/privstarter/src/main/cpp/selinux.h similarity index 100% rename from nativelib/src/main/cpp/selinux.h rename to privstarter/src/main/cpp/selinux.h diff --git a/nativelib/src/main/cpp/starter.cpp b/privstarter/src/main/cpp/starter.cpp similarity index 100% rename from nativelib/src/main/cpp/starter.cpp rename to privstarter/src/main/cpp/starter.cpp diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbClient.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbClient.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbClient.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbClient.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbException.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbException.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbException.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbException.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbKey.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbKey.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbKey.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMdns.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbMdns.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMdns.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbMdns.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMessage.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbMessage.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbMessage.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbMessage.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbPairingClient.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingClient.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbPairingClient.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingService.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbPairingService.kt similarity index 99% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingService.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbPairingService.kt index 3ccb27de7e..9f4e23c8d3 100644 --- a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbPairingService.kt +++ b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbPairingService.kt @@ -18,7 +18,7 @@ import android.preference.PreferenceManager import android.util.Log import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer -import io.github.sds100.keymapper.nativelib.R +import io.github.sds100.keymapper.privstarter.R import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbProtocol.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbProtocol.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/adb/AdbProtocol.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/adb/AdbProtocol.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Context.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/ktx/Context.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Context.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/ktx/Context.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Log.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/ktx/Log.kt similarity index 100% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/ktx/Log.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/ktx/Log.kt diff --git a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/starter/Starter.kt similarity index 98% rename from nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt rename to privstarter/src/main/java/io/github/sds100/keymapper/privstarter/starter/Starter.kt index 83cb7718d4..543167ece2 100644 --- a/nativelib/src/main/java/io/github/sds100/keymapper/nativelib/starter/Starter.kt +++ b/privstarter/src/main/java/io/github/sds100/keymapper/privstarter/starter/Starter.kt @@ -6,7 +6,7 @@ import android.os.UserManager import android.system.ErrnoException import android.system.Os import androidx.annotation.RequiresApi -import io.github.sds100.keymapper.nativelib.R +import io.github.sds100.keymapper.privstarter.R import moe.shizuku.manager.ktx.createDeviceProtectedStorageContextCompat import moe.shizuku.manager.ktx.logd import moe.shizuku.manager.ktx.loge diff --git a/nativelib/src/main/res/raw/start.sh b/privstarter/src/main/res/raw/start.sh similarity index 100% rename from nativelib/src/main/res/raw/start.sh rename to privstarter/src/main/res/raw/start.sh diff --git a/nativelib/src/main/res/values/strings.xml b/privstarter/src/main/res/values/strings.xml similarity index 100% rename from nativelib/src/main/res/values/strings.xml rename to privstarter/src/main/res/values/strings.xml diff --git a/settings.gradle b/settings.gradle index ad704f8629..5895cdcfca 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,4 @@ include ':app' include ':systemstubs' include ':nativelib' +include ':privstarter'