├── .crowdin └── crowdin.yml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── new.md ├── dependabot.yml └── workflows │ ├── build-apk.yml │ ├── build-docs.yml │ ├── crowdin-action.yml │ └── release-apk.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── Android.mk ├── Application.mk ├── build.gradle ├── hidden-api │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ └── java │ │ └── android │ │ ├── app │ │ ├── IActivityManager.java │ │ └── IProcessObserver.java │ │ └── os │ │ └── ServiceManager.java ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── xtr │ │ └── keymapper │ │ ├── ActivityObserver.aidl │ │ ├── IRemoteService.aidl │ │ ├── IRemoteServiceCallback.aidl │ │ ├── OnKeyEventListener.aidl │ │ └── keymap │ │ ├── KeymapConfig.aidl │ │ └── KeymapProfile.aidl │ ├── cpp │ ├── evdev_common.cpp │ ├── evdev_common.h │ ├── getevent.c │ ├── mouse_cursor.cpp │ ├── mouse_cursor.h │ ├── mouse_read.c │ ├── touchpad_direct.cpp │ └── touchpad_relative.cpp │ ├── java │ ├── com │ │ └── genymobile │ │ │ └── scrcpy │ │ │ ├── Point.java │ │ │ ├── Pointer.java │ │ │ └── PointersState.java │ └── xtr │ │ └── keymapper │ │ ├── InputEventCodes.java │ │ ├── Server.java │ │ ├── TouchPointer.java │ │ ├── Utils.java │ │ ├── activity │ │ ├── DisplaySelectorActivity.kt │ │ ├── ImportExportActivity.java │ │ ├── InfoActivity.java │ │ ├── MainActivity.java │ │ └── ui │ │ │ └── theme │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ │ ├── dpad │ │ ├── Dpad.java │ │ ├── DpadHandler.java │ │ └── DpadKeyCodes.java │ │ ├── editor │ │ ├── EditorActivity.java │ │ ├── EditorCallback.java │ │ ├── EditorService.java │ │ ├── EditorUI.java │ │ ├── MacroDialog.java │ │ ├── ResizeableDpadView.java │ │ ├── SettingsOverlay.java │ │ └── ShowKeymapService.java │ │ ├── floatingkeys │ │ ├── FloatingActionKey.java │ │ ├── MovableFloatingActionKey.java │ │ └── MovableFrameLayout.java │ │ ├── keymap │ │ ├── KeymapConfig.java │ │ ├── KeymapProfile.java │ │ ├── KeymapProfileKey.java │ │ └── KeymapProfiles.java │ │ ├── macro │ │ ├── Macro.java │ │ ├── MacroIdUtils.java │ │ ├── MacroSharedPreferences.java │ │ ├── MacroStatus.java │ │ └── MacroView.java │ │ ├── mouse │ │ ├── MouseAimConfig.java │ │ ├── MouseAimHandler.java │ │ ├── MousePinchZoom.java │ │ └── MouseWheelZoom.java │ │ ├── profiles │ │ ├── ProfileSelector.java │ │ ├── ProfilesApps.java │ │ ├── ProfilesViewAdapter.java │ │ └── ProfilesViewFragment.java │ │ ├── server │ │ ├── ActivityObserverService.java │ │ ├── IInputInterface.java │ │ ├── Input.java │ │ ├── InputService.java │ │ ├── RemoteService.java │ │ ├── RemoteServiceHelper.java │ │ ├── RemoteServiceShell.java │ │ └── RootRemoteService.java │ │ ├── service │ │ └── InputListenerService.java │ │ ├── swipekey │ │ ├── SwipeKey.java │ │ ├── SwipeKeyHandler.java │ │ ├── SwipeKeyOverlay.java │ │ └── SwipeKeyView.java │ │ └── touchpointer │ │ ├── KeyEventHandler.java │ │ ├── MouseEventHandler.java │ │ ├── PidProvider.java │ │ └── PointerId.java │ └── res │ ├── drawable │ ├── baseline_360_24.xml │ ├── crosshair.png │ ├── dpad.png │ ├── ic_baseline_add_24.xml │ ├── ic_baseline_done_36.xml │ ├── ic_baseline_help_outline_24.xml │ ├── ic_baseline_keyboard_arrow_down_24.xml │ ├── ic_baseline_keyboard_arrow_left_24.xml │ ├── ic_baseline_keyboard_arrow_right_24.xml │ ├── ic_baseline_keyboard_arrow_up_24.xml │ ├── ic_baseline_mouse_36.xml │ ├── ic_baseline_settings_24.xml │ ├── ic_baseline_speed_24.xml │ ├── ic_baseline_touch_app_24.xml │ ├── ic_baseline_zoom_out_map_24.xml │ ├── ic_create_white_36dp.xml │ ├── ic_pointer_arrow.xml │ ├── ic_start_button.xml │ ├── key.png │ ├── newkey.png │ ├── pointer_arrow.png │ └── resize_handle.xml │ ├── layout-land │ ├── activity_import_export.xml │ ├── activity_main.xml │ └── app_view.xml │ ├── layout │ ├── activity_import_export.xml │ ├── activity_info.xml │ ├── activity_main.xml │ ├── activity_main_controls.xml │ ├── app_view.xml │ ├── crosshair.xml │ ├── cursor.xml │ ├── dpad.xml │ ├── dpad_arrows.xml │ ├── floating_key.xml │ ├── fragment_profiles_apps.xml │ ├── fragment_profiles_view.xml │ ├── keymap_editor_item.xml │ ├── keymap_editor_layout.xml │ ├── loading.xml │ ├── macro_dialog_layout.xml │ ├── macro_list_item.xml │ ├── macro_status_layout.xml │ ├── mouse_aim_config.xml │ ├── profile_row_item.xml │ ├── profile_row_item2.xml │ ├── resizable.xml │ ├── settings.xml │ ├── swipe_key.xml │ └── text_field.xml │ ├── menu │ ├── bottom_navigation_menu.xml │ └── keymap_editor_menu.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ ├── mipmap-hdpi │ ├── ic_launcher_background.png │ └── ic_launcher_foreground.png │ ├── mipmap-mdpi │ ├── ic_launcher_background.png │ └── ic_launcher_foreground.png │ ├── mipmap-xhdpi │ ├── ic_launcher_background.png │ └── ic_launcher_foreground.png │ ├── mipmap-xxhdpi │ ├── ic_launcher_background.png │ └── ic_launcher_foreground.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher_background.png │ └── ic_launcher_foreground.png │ ├── values-ar │ └── strings.xml │ ├── values-pa │ └── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ ├── styles.xml │ └── themes.xml │ └── xml │ └── method.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystore.properties └── settings.gradle /.crowdin/crowdin.yml: -------------------------------------------------------------------------------- 1 | "project_id_env": "CROWDIN_PROJECT_ID" 2 | "api_token_env": "CROWDIN_PERSONAL_TOKEN" 3 | 4 | "base_path": ".." 5 | 6 | "preserve_hierarchy": true 7 | 8 | "files": [ 9 | { 10 | "source": "app/src/main/res/values/strings.xml", 11 | "translation": "app/src/main/res/values-%android_code%/%original_file_name%" 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Xtr126 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the issue** 11 | A clear and concise description of what the bug/ request is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Device (please complete the following information):** 27 | - OS: [e.g. Bliss OS] 28 | - Android version [e.g. 12.1] 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/app" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | registries: 13 | - maven-google 14 | 15 | registries: 16 | maven-google: 17 | type: maven-repository 18 | url: "https://dl.google.com/dl/android/maven2/" 19 | -------------------------------------------------------------------------------- /.github/workflows/build-apk.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload APK 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - dev 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Set up JDK 21 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: '21' 21 | distribution: 'temurin' 22 | cache: gradle 23 | 24 | - name: Grant execute permission for gradlew 25 | run: chmod +x gradlew 26 | 27 | - name: Write key 28 | run: | 29 | if [ ! -z "${{ secrets.KEYSTORE }}" ]; then 30 | { 31 | echo storePassword='${{ secrets.KEYSTORE_PASSWORD }}' 32 | echo keyPassword='${{ secrets.KEY_PASSWORD }}' 33 | echo keyAlias='${{ secrets.KEY_ALIAS }}' 34 | echo storeFile='../keystore.jks' 35 | } > keystore.properties 36 | echo '${{ secrets.KEYSTORE }}' | base64 -d > keystore.jks 37 | fi 38 | 39 | - name: Build with Gradle 40 | run: ./gradlew app:assembleDebug 41 | 42 | - uses: actions/upload-artifact@v4 43 | with: 44 | name: Debug APK 45 | path: app/build/outputs/apk/debug/*.apk 46 | 47 | - name: Build with Gradle 48 | run: ./gradlew app:assembleRelease 49 | 50 | - uses: actions/upload-artifact@v4 51 | with: 52 | name: Release APK 53 | path: app/build/outputs/apk/release/*.apk 54 | 55 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | name: Build Documentation 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout your repository using git 11 | uses: actions/checkout@v4 12 | with: 13 | repository: Xtr126/XtMapper-docs 14 | 15 | - name: Install, build, and upload your site 16 | uses: withastro/action@v2 17 | with: 18 | node-version: 20 19 | package-manager: pnpm@latest 20 | -------------------------------------------------------------------------------- /.github/workflows/crowdin-action.yml: -------------------------------------------------------------------------------- 1 | name: Crowdin Action 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - '**strings.xml' 8 | 9 | jobs: 10 | synchronize-with-crowdin: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: crowdin action 18 | uses: crowdin/github-action@v2 19 | with: 20 | upload_sources: true 21 | upload_translations: true 22 | download_translations: true 23 | localization_branch_name: l10n_dev 24 | create_pull_request: true 25 | pull_request_title: 'New Crowdin Translations' 26 | pull_request_body: 'New Crowdin translations by [Crowdin GH Action](https://github.com/crowdin/github-action)' 27 | pull_request_base_branch_name: 'dev' 28 | config: ".crowdin/crowdin.yml" 29 | 30 | env: 31 | # A classic GitHub Personal Access Token with the 'repo' scope selected (the user should have write access to the repository). 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | 34 | # A numeric ID, found at https://crowdin.com/project//tools/api 35 | CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} 36 | 37 | # Visit https://crowdin.com/settings#api-key to create this token 38 | CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} 39 | -------------------------------------------------------------------------------- /.github/workflows/release-apk.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release APK 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | build-docs: 11 | uses: ./.github/workflows/build-docs.yml 12 | 13 | build: 14 | needs: 15 | - build-docs 16 | 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - name: Set up JDK 21 22 | uses: actions/setup-java@v4 23 | with: 24 | java-version: '21' 25 | distribution: 'temurin' 26 | cache: gradle 27 | 28 | - name: Grant execute permission for gradlew 29 | run: chmod +x gradlew 30 | 31 | - name: Write key 32 | run: | 33 | if [ ! -z "${{ secrets.KEYSTORE }}" ]; then 34 | { 35 | echo storePassword='${{ secrets.KEYSTORE_PASSWORD }}' 36 | echo keyPassword='${{ secrets.KEY_PASSWORD }}' 37 | echo keyAlias='${{ secrets.KEY_ALIAS }}' 38 | echo storeFile='../keystore.jks' 39 | } > keystore.properties 40 | echo '${{ secrets.KEYSTORE }}' | base64 -d > keystore.jks 41 | fi 42 | 43 | - name: Download artifacts 44 | uses: actions/download-artifact@v4 45 | 46 | - name: Move artifact to assets 47 | run: | 48 | cd ./github-pages 49 | tar xvf artifact.tar && rm artifact.tar 50 | cd ../ 51 | mkdir ./app/src/main/assets 52 | mv ./github-pages ./app/src/main/assets/XtMapper-docs 53 | 54 | - name: Build with Gradle 55 | run: ./gradlew app:assembleRelease 56 | 57 | - name: Upload binaries to release 58 | uses: svenstaro/upload-release-action@v2 59 | with: 60 | repo_token: ${{ secrets.GITHUB_TOKEN }} 61 | file: 'app/build/outputs/apk/release/*.apk' 62 | asset_name: app-release.zip 63 | tag: v2.4.1 64 | overwrite: true 65 | file_glob: true 66 | draft: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.apk 3 | .gradle 4 | /local.properties 5 | /.idea 6 | /build 7 | /app/build 8 | output*.json 9 | keystore.properties 10 | keystore.jks 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | XtMapper 7 |

8 |

9 | XtMapper, a free and open source keymapper.
10 | Play your Android games with keyboard and mouse
11 |

12 | 13 |

14 | 15 | 16 | 17 |

18 | 19 | ## About and features 20 | https://xtr126.github.io/XtMapper-docs/guides/about 21 | [Watch video on YouTube](https://www.youtube.com/watch?v=Slcu43xBV3M) 22 | 23 | ## Screenshots 24 | 25 | | | | | 26 | | ------------- | ------------- | ------------- | 27 | | | | | 28 | ## Development 29 | 30 | ### Build 31 | - Run `./gradlew assembleDebug` or `./gradlew.bat assembleDebug` at the base directory of the project 32 | 33 | ## Help and support 34 | To report any bugs to help improve XtMapper please create an issue at https://github.com/Xtr126/XtMapper/issues 35 | 36 | To share your thoughts on XtMapper/ ask any questions please create a post at https://github.com/Xtr126/XtMapper/discussions 37 | 38 | ## Using on waydroid 39 | https://xtr126.github.io/XtMapper-docs/waydroid 40 | ## Credits 41 | @guobao2333 - [Chinese translation](https://github.com/Xtr126/XtMapper/pull/101) 42 | @muhammadbahaa2001 - [Arabic translation](https://github.com/Xtr126/XtMapper/pull/106) 43 | @KSMaan45 - [Punjabi translation](https://github.com/Xtr126/XtMapper/pull/109) 44 | 45 | 46 | Help us translate on [Crowdin](https://crowdin.com/project/xtmapper/) or GitHub 47 | 48 | 49 | And everyone else not mentioned here who took their time reporting bugs and making suggestions. 50 | 51 | Open source libraries used: 52 | - [Starlight](https://github.com/withastro/starlight) - Documentation framework 53 | - [Material Design Components](https://github.com/material-components/material-components-android) - User interface 54 | - [FloatingActionButtonSpeedDial](https://github.com/leinardi/FloatingActionButtonSpeedDial) - Controls in editor 55 | - [libsu](https://github.com/topjohnwu/libsu) - RootService 56 | - [Logo](https://github.com/Xtr126/XtMapper/assets/80520774/2093a10b-f63f-4687-a4c9-d803f66d4e82) - Made with [Blender](https://www.blender.org/) 57 | 58 | 59 | [Some code](./app/src/main/java/com/genymobile/scrcpy) from the [scrcpy](https://github.com/Genymobile/scrcpy) project was used for implementing multi-touch support in the keymapper. 60 | [Get it on IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/xtr.keymapper) 63 | ## Copyright and License 64 | The source code is licensed under the GPL v3. 65 | ``` 66 | XtMapper 67 | Copyright (C) 2022 Xtr126 68 | 69 | This program is free software; you can redistribute it and/or modify 70 | it under the terms of the GNU General Public License as published by 71 | the Free Software Foundation; version 3. 72 | 73 | This program is distributed in the hope that it will be useful, 74 | but WITHOUT ANY WARRANTY; without even the implied warranty of 75 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 76 | GNU General Public License for more details. 77 | 78 | You should have received a copy of the GNU General Public License 79 | along with this program. If not, see https://www.gnu.org/licenses/. 80 | ``` 81 | Do not publish unofficial APKs to the play store. It hurts open source projects like ours. 82 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /src/main/assets/XtMapper-docs/ 3 | -------------------------------------------------------------------------------- /app/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | JNI_SRC_PATH := $(LOCAL_PATH)/src/main/cpp 4 | 5 | include $(CLEAR_VARS) 6 | 7 | LOCAL_MODULE := getevent 8 | LOCAL_SRC_FILES := $(JNI_SRC_PATH)/getevent.c 9 | include $(BUILD_SHARED_LIBRARY) 10 | 11 | include $(CLEAR_VARS) 12 | 13 | LOCAL_MODULE := mouse_read 14 | LOCAL_SRC_FILES := $(JNI_SRC_PATH)/mouse_read.c 15 | include $(BUILD_SHARED_LIBRARY) 16 | 17 | include $(CLEAR_VARS) 18 | 19 | LOCAL_MODULE := mouse_cursor 20 | LOCAL_SRC_FILES := $(JNI_SRC_PATH)/mouse_cursor.cpp 21 | include $(BUILD_SHARED_LIBRARY) 22 | 23 | include $(CLEAR_VARS) 24 | 25 | LOCAL_MODULE := evdev_common 26 | LOCAL_SRC_FILES := $(JNI_SRC_PATH)/evdev_common.cpp 27 | include $(BUILD_SHARED_LIBRARY) 28 | 29 | include $(CLEAR_VARS) 30 | 31 | LOCAL_MODULE := touchpad_direct 32 | LOCAL_SRC_FILES := $(JNI_SRC_PATH)/touchpad_direct.cpp 33 | LOCAL_SHARED_LIBRARIES := evdev_common 34 | include $(BUILD_SHARED_LIBRARY) 35 | 36 | include $(CLEAR_VARS) 37 | 38 | LOCAL_MODULE := touchpad_relative 39 | LOCAL_SRC_FILES := $(JNI_SRC_PATH)/touchpad_relative.cpp 40 | LOCAL_SHARED_LIBRARIES := evdev_common 41 | include $(BUILD_SHARED_LIBRARY) 42 | -------------------------------------------------------------------------------- /app/Application.mk: -------------------------------------------------------------------------------- 1 | APP_STL := c++_shared -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'org.jetbrains.kotlin.plugin.compose' 5 | } 6 | 7 | // Create a variable called keystorePropertiesFile, and initialize it to your 8 | // keystore.properties file, in the rootProject folder. 9 | def keystorePropertiesFile = rootProject.file("keystore.properties") 10 | 11 | // Initialize a new Properties() object called keystoreProperties. 12 | def keystoreProperties = new Properties() 13 | 14 | // Load your keystore.properties file into the keystoreProperties object. 15 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 16 | 17 | android { 18 | namespace 'xtr.keymapper' 19 | compileSdk 35 20 | 21 | defaultConfig { 22 | applicationId "xtr.keymapper" 23 | minSdk 28 24 | targetSdk 34 25 | versionCode 21 26 | versionName '2.4.1' 27 | 28 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 29 | 30 | aaptOptions { 31 | ignoreAssetsPattern '' 32 | } 33 | } 34 | 35 | signingConfigs { 36 | release { 37 | keyAlias keystoreProperties['keyAlias'] 38 | keyPassword keystoreProperties['keyPassword'] 39 | storeFile file(keystoreProperties['storeFile']) 40 | storePassword keystoreProperties['storePassword'] 41 | } 42 | } 43 | buildTypes { 44 | release { 45 | minifyEnabled true 46 | shrinkResources true 47 | 48 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 49 | signingConfig signingConfigs.release 50 | } 51 | debug { 52 | applicationIdSuffix ".debug" 53 | resValue("string", "app_name", "XtMapper (Debug)") 54 | } 55 | 56 | } 57 | 58 | externalNativeBuild { 59 | ndkBuild { 60 | path 'Android.mk' 61 | } 62 | } 63 | 64 | applicationVariants.configureEach { 65 | it.outputs.every { 66 | it.outputFileName = "XtMapper-${it.name}-v${defaultConfig.versionName}.apk" 67 | } 68 | } 69 | 70 | compileOptions { 71 | sourceCompatibility JavaVersion.VERSION_21 72 | targetCompatibility JavaVersion.VERSION_21 73 | } 74 | buildFeatures { 75 | viewBinding true 76 | aidl true 77 | buildConfig true 78 | compose true 79 | } 80 | packagingOptions.jniLibs.useLegacyPackaging true 81 | 82 | ndkVersion '28.0.13004108' 83 | 84 | dependenciesInfo { 85 | // Disables dependency metadata when building APKs. 86 | includeInApk = false 87 | // Disables dependency metadata when building Android App Bundles. 88 | includeInBundle = false 89 | } 90 | } 91 | 92 | dependencies { 93 | implementation 'androidx.appcompat:appcompat:1.7.0' 94 | implementation "androidx.constraintlayout:constraintlayout:2.2.1" 95 | implementation "androidx.coordinatorlayout:coordinatorlayout:1.3.0" 96 | implementation 'com.google.android.material:material:1.12.0' 97 | implementation 'androidx.webkit:webkit:1.13.0' 98 | implementation "com.github.topjohnwu.libsu:core:5.3.0" 99 | implementation "com.github.topjohnwu.libsu:service:5.3.0" 100 | implementation "dev.rikka.shizuku:api:13.1.5" 101 | implementation "dev.rikka.shizuku:provider:13.1.5" 102 | 103 | def composeBom = platform('androidx.compose:compose-bom:2025.05.00') 104 | implementation composeBom 105 | 106 | // Optional - Integration with activities 107 | implementation 'androidx.activity:activity-compose:1.10.1' 108 | 109 | // Material Design 3 110 | implementation 'androidx.compose.material3:material3' 111 | 112 | // Android Studio Preview support 113 | implementation 'androidx.compose.ui:ui-tooling-preview' 114 | debugImplementation 'androidx.compose.ui:ui-tooling' 115 | 116 | compileOnly project(path: ':app:hidden-api') 117 | 118 | } 119 | -------------------------------------------------------------------------------- /app/hidden-api/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/hidden-api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | namespace 'android' 7 | compileSdk 35 8 | 9 | defaultConfig { 10 | consumerProguardFiles "consumer-rules.pro" 11 | } 12 | 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_21 15 | targetCompatibility JavaVersion.VERSION_21 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /app/hidden-api/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/hidden-api/consumer-rules.pro -------------------------------------------------------------------------------- /app/hidden-api/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/hidden-api/src/main/java/android/app/IActivityManager.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | import android.os.RemoteException; 7 | 8 | import java.util.List; 9 | 10 | public interface IActivityManager extends IInterface { 11 | List getTasks(int maxNum) 12 | throws RemoteException; 13 | 14 | void registerProcessObserver(IProcessObserver observer) 15 | throws RemoteException; 16 | 17 | abstract class Stub extends Binder implements IActivityManager { 18 | 19 | public static IActivityManager asInterface(IBinder obj) { 20 | throw new RuntimeException("STUB"); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/hidden-api/src/main/java/android/app/IProcessObserver.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.Binder; 4 | 5 | public interface IProcessObserver { 6 | void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) throws android.os.RemoteException; 7 | void onForegroundServicesChanged(int pid, int uid, int serviceTypes) throws android.os.RemoteException; 8 | void onProcessDied(int pid, int uid) throws android.os.RemoteException; 9 | 10 | abstract class Stub extends Binder implements IProcessObserver { 11 | } 12 | } -------------------------------------------------------------------------------- /app/hidden-api/src/main/java/android/os/ServiceManager.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class ServiceManager { 4 | /** 5 | * Returns a reference to a service with the given name. 6 | * 7 | * @param name the name of the service to get 8 | * @return a reference to the service, or null if the service doesn't exist 9 | */ 10 | public static IBinder getService(String name) { 11 | throw new RuntimeException("STUB"); 12 | } 13 | 14 | /** 15 | * Place a new @a service called @a name into the service 16 | * manager. 17 | * 18 | * @param name the name of the new service 19 | * @param service the service object 20 | */ 21 | public static void addService(String name, IBinder service) throws Exception { 22 | throw new RuntimeException("STUB"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | 9 | # Uncomment this to preserve the line number information for 10 | # debugging stack traces. 11 | -keepattributes SourceFile,LineNumberTable 12 | 13 | -keep class xtr.keymapper.** { *; } 14 | -keep class xtr.keymapper.**$* { *; } 15 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 32 | 33 | 37 | 38 | 41 | 42 | 46 | 47 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 68 | 69 | 73 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/aidl/xtr/keymapper/ActivityObserver.aidl: -------------------------------------------------------------------------------- 1 | // ActivityObserverCallback.aidl 2 | package xtr.keymapper; 3 | 4 | // Declare any non-default types here with import statements 5 | 6 | interface ActivityObserver { 7 | void onForegroundActivitiesChanged(String packageName); 8 | } -------------------------------------------------------------------------------- /app/src/main/aidl/xtr/keymapper/IRemoteService.aidl: -------------------------------------------------------------------------------- 1 | package xtr.keymapper; 2 | 3 | import xtr.keymapper.IRemoteServiceCallback; 4 | import xtr.keymapper.OnKeyEventListener; 5 | import xtr.keymapper.ActivityObserver; 6 | 7 | import xtr.keymapper.keymap.KeymapConfig; 8 | import xtr.keymapper.keymap.KeymapProfile; 9 | 10 | interface IRemoteService { 11 | void destroy() = 16777114; // Destroy method defined by Shizuku server 12 | 13 | 14 | void startServer(in KeymapProfile profile, in KeymapConfig keymapConfig, IRemoteServiceCallback cb, int screenWidth, int screenHeight, int displayId) = 2; 15 | void stopServer() = 3; 16 | 17 | void registerOnKeyEventListener(OnKeyEventListener l) = 4; 18 | void unregisterOnKeyEventListener(OnKeyEventListener l) = 5; 19 | 20 | void registerActivityObserver(ActivityObserver callback) = 6; 21 | void unregisterActivityObserver(ActivityObserver callback) = 7; 22 | 23 | void resumeMouse() = 8; 24 | void pauseMouse() = 9; 25 | void reloadKeymap() = 10; 26 | boolean isActive() = 11; 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/aidl/xtr/keymapper/IRemoteServiceCallback.aidl: -------------------------------------------------------------------------------- 1 | package xtr.keymapper; 2 | 3 | import xtr.keymapper.keymap.KeymapConfig; 4 | import xtr.keymapper.keymap.KeymapProfile; 5 | 6 | interface IRemoteServiceCallback { 7 | void launchEditor(); 8 | void alertMouseAimActivated(); 9 | KeymapProfile requestKeymapProfile(); 10 | KeymapConfig requestKeymapConfig(); 11 | void switchProfiles(); 12 | 13 | void enablePointer(); 14 | void disablePointer(); 15 | void setCursorX(int x); 16 | void setCursorY(int y); 17 | } -------------------------------------------------------------------------------- /app/src/main/aidl/xtr/keymapper/OnKeyEventListener.aidl: -------------------------------------------------------------------------------- 1 | package xtr.keymapper; 2 | 3 | interface OnKeyEventListener { 4 | void onKeyEvent(String event); 5 | } -------------------------------------------------------------------------------- /app/src/main/aidl/xtr/keymapper/keymap/KeymapConfig.aidl: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.keymap; 2 | 3 | parcelable KeymapConfig; -------------------------------------------------------------------------------- /app/src/main/aidl/xtr/keymapper/keymap/KeymapProfile.aidl: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.keymap; 2 | 3 | parcelable KeymapProfile; -------------------------------------------------------------------------------- /app/src/main/cpp/evdev_common.cpp: -------------------------------------------------------------------------------- 1 | #include "mouse_cursor.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "evdev_common.h" 17 | 18 | using std::string; 19 | 20 | std::vector ListInputDevices() { 21 | const string input_directory = "/dev/input"; 22 | std::vector filenames; 23 | struct DIR * directory = opendir(input_directory.c_str()); 24 | 25 | struct dirent *entry; 26 | while ((entry = readdir(directory))) { 27 | if (entry->d_name[0] == 'e') // is eventX 28 | filenames.push_back(input_directory + "/" + entry->d_name); 29 | } 30 | return filenames; 31 | } 32 | 33 | std::vector scanTouchpadDevices() { 34 | std::vector evdevNames = ListInputDevices(); 35 | std::vector touchpadDeviceFds; 36 | 37 | printf("I: Searching for touchpad devices...\n"); 38 | for (auto & evdev : evdevNames) { 39 | int device_fd = open(evdev.c_str(), O_RDWR); 40 | if (device_fd < 0) { 41 | perror("opening device"); 42 | } 43 | 44 | // Get device name 45 | char dev_name[24]; 46 | if(!ioctl(device_fd, EVIOCGNAME(sizeof(dev_name) - 1), &dev_name)) { 47 | perror("get device name"); 48 | close(device_fd); 49 | continue; 50 | } 51 | 52 | if(!HasSpecificAbs(device_fd, ABS_X) || !HasSpecificAbs(device_fd, ABS_Y)) { 53 | printf("%s: no ABS_X or ABS_Y events found\n", dev_name); 54 | printf("Not a touch device\n"); 55 | close(device_fd); 56 | continue; 57 | } 58 | 59 | if (!HasInputProp(device_fd, INPUT_PROP_POINTER)) { 60 | printf("%s: INPUT_PROP_POINTER not set\n", dev_name); 61 | if (!HasSpecificKey(device_fd, BTN_MOUSE)) { 62 | printf("BTN_MOUSE not found\n"); 63 | printf("Not a touchpad device\n"); 64 | close(device_fd); 65 | continue; 66 | } 67 | } 68 | 69 | 70 | // Check for virtual devices 71 | if (strcmp(x_virtual_tablet, dev_name) == 0 || strcmp(x_virtual_mouse, dev_name) == 0) { 72 | printf("skipping %s\n", dev_name); 73 | close(device_fd); 74 | continue; 75 | } 76 | else if (x_virtual_touch == dev_name) { 77 | touchpadDeviceFds.clear(); 78 | return touchpadDeviceFds; 79 | } 80 | 81 | printf("I: Add touchpad device: %s %s\n", dev_name, evdev.c_str()); 82 | ioctl(device_fd, EVIOCGRAB, (void *)1); 83 | 84 | touchpadDeviceFds.push_back(device_fd); 85 | } 86 | return touchpadDeviceFds; 87 | } 88 | 89 | bool HasSpecificAbs(int device_fd, unsigned int abs) { 90 | unsigned long nchar = KEY_MAX / 8 + 1; 91 | unsigned char bits[nchar]; 92 | // Get the bit fields of available abs events. 93 | ioctl(device_fd, EVIOCGBIT(EV_ABS, sizeof(bits)), &bits); 94 | return bits[abs/8] & (1 << (abs % 8)); 95 | } 96 | 97 | bool HasSpecificKey(int device_fd, unsigned int key) { 98 | unsigned long nchar = KEY_MAX / 8 + 1; 99 | unsigned char bits[nchar]; 100 | // Get the bit fields of available keys. 101 | ioctl(device_fd, EVIOCGBIT(EV_KEY, sizeof(bits)), &bits); 102 | return bits[key/8] & (1 << (key % 8)); 103 | } 104 | 105 | bool HasInputProp(int device_fd, unsigned int input_prop) { 106 | unsigned long nchar = INPUT_PROP_MAX / 8 + 1; 107 | unsigned char bits[nchar]; 108 | // Get the bit fields of available keys. 109 | ioctl(device_fd, EVIOCGPROP(sizeof(bits)), &bits); 110 | return bits[input_prop/8] & (1 << (input_prop % 8)); 111 | } 112 | 113 | bool HasEventType(int device_fd, unsigned int type) { 114 | unsigned long evbit = 0; 115 | // Get the bit field of available event types. 116 | ioctl(device_fd, EVIOCGBIT(0, sizeof(evbit)), &evbit); 117 | return evbit & (1 << type); 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/cpp/evdev_common.h: -------------------------------------------------------------------------------- 1 | using std::string; 2 | 3 | const char *x_virtual_touch = "x-virtual-touch"; 4 | const char *x_virtual_mouse = "x-virtual-mouse"; 5 | 6 | std::vector ListInputDevices(); 7 | 8 | std::vector scanTouchpadDevices(); 9 | 10 | bool HasSpecificAbs(int device_fd, unsigned int abs); 11 | 12 | bool HasSpecificKey(int device_fd, unsigned int key); 13 | 14 | bool HasInputProp(int device_fd, unsigned int input_prop); 15 | 16 | bool HasEventType(int device_fd, unsigned int type); 17 | -------------------------------------------------------------------------------- /app/src/main/cpp/getevent.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static void __attribute ((constructor)) 4 | getevent(void) { 5 | setlinebuf(stdout); 6 | } -------------------------------------------------------------------------------- /app/src/main/cpp/mouse_cursor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "mouse_cursor.h" 10 | 11 | int uinput_fd = -1; 12 | struct input_event ie {}; 13 | const char* device_name = x_virtual_tablet; 14 | 15 | void setAbsMinMax(int width, int height) { 16 | struct uinput_abs_setup uinputAbsSetup {}; 17 | 18 | memset(&uinputAbsSetup, 0x00, sizeof(uinputAbsSetup)); 19 | uinputAbsSetup.code = ABS_X; 20 | uinputAbsSetup.absinfo = input_absinfo {0, 0, width, 0 , 0}; 21 | ioctl(uinput_fd, UI_ABS_SETUP, &uinputAbsSetup); 22 | 23 | uinputAbsSetup.code = ABS_Y; 24 | uinputAbsSetup.absinfo = input_absinfo {0, 0, height, 0 , 0}; 25 | ioctl(uinput_fd, UI_ABS_SETUP, &uinputAbsSetup); 26 | } 27 | 28 | extern "C" JNIEXPORT jint JNICALL 29 | Java_xtr_keymapper_server_InputService_initMouseCursor 30 | (JNIEnv * /*env*/, jobject /*obj*/, jint width, jint height) { 31 | struct uinput_setup uinputSetup {}; 32 | uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 33 | 34 | if (uinput_fd <= 0) return uinput_fd; 35 | 36 | memset(&ie, 0, sizeof(struct input_event)); 37 | memset(&uinputSetup, 0x00, sizeof(uinputSetup)); 38 | 39 | strncpy(uinputSetup.name, device_name, strlen(device_name)); 40 | uinputSetup.id.version = 1; 41 | uinputSetup.id.bustype = BUS_VIRTUAL; 42 | setAbsMinMax(width, height); 43 | 44 | ioctl(uinput_fd, UI_SET_EVBIT, EV_ABS); 45 | ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY); 46 | ioctl(uinput_fd, UI_SET_EVBIT, EV_REL); 47 | ioctl(uinput_fd, UI_SET_PROPBIT, INPUT_PROP_POINTER); 48 | 49 | ioctl(uinput_fd, UI_SET_ABSBIT, ABS_X); 50 | ioctl(uinput_fd, UI_SET_ABSBIT, ABS_Y); 51 | ioctl(uinput_fd, UI_SET_RELBIT, REL_WHEEL); 52 | 53 | ioctl(uinput_fd, UI_SET_KEYBIT, BTN_MOUSE); 54 | ioctl(uinput_fd, UI_SET_KEYBIT, BTN_RIGHT); 55 | ioctl(uinput_fd, UI_SET_KEYBIT, BTN_WHEEL); 56 | 57 | ioctl(uinput_fd, UI_DEV_SETUP, &uinputSetup); 58 | 59 | if(ioctl(uinput_fd, UI_DEV_CREATE)) { 60 | close(uinput_fd); 61 | return -1; 62 | } 63 | return 1; 64 | } 65 | 66 | void report() { 67 | ie.type = EV_SYN; 68 | ie.code = SYN_REPORT; 69 | ie.value = 0; 70 | write(uinput_fd, &ie, sizeof(struct input_event)); 71 | } 72 | 73 | extern "C" JNIEXPORT void JNICALL 74 | Java_xtr_keymapper_server_InputService_cursorSetX 75 | (JNIEnv * /*env*/, jobject /*obj*/, jint x) { 76 | ie.type = EV_ABS; 77 | ie.code = ABS_X; 78 | ie.value = x; 79 | write(uinput_fd, &ie, sizeof(struct input_event)); 80 | report(); 81 | } 82 | 83 | extern "C" JNIEXPORT void JNICALL 84 | Java_xtr_keymapper_server_InputService_cursorSetY 85 | (JNIEnv * /*env*/, jobject /*obj*/, jint y) { 86 | ie.type = EV_ABS; 87 | ie.code = ABS_Y; 88 | ie.value = y; 89 | write(uinput_fd, &ie, sizeof(struct input_event)); 90 | report(); 91 | } 92 | 93 | extern "C" JNIEXPORT void JNICALL 94 | Java_xtr_keymapper_server_InputService_destroyUinputDev (JNIEnv * /*env*/, jobject /*obj*/) { 95 | if (uinput_fd > 0) { 96 | ioctl(uinput_fd, UI_DEV_DESTROY); 97 | close(uinput_fd); 98 | uinput_fd = -1; 99 | } 100 | } -------------------------------------------------------------------------------- /app/src/main/cpp/mouse_cursor.h: -------------------------------------------------------------------------------- 1 | #define x_virtual_tablet "x-virtual-tablet" -------------------------------------------------------------------------------- /app/src/main/cpp/touchpad_direct.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "mouse_cursor.h" 18 | #include "evdev_common.h" 19 | 20 | using std::string; 21 | 22 | struct input_event ie {}; 23 | 24 | std::atomic running = false; 25 | 26 | std::thread looper; 27 | 28 | std::vector poll_fds; 29 | std::vector uinput_fds; 30 | 31 | void SetAbsInfoFrom(int device_fd, int uinput_fd) { 32 | for(int abs_i = ABS_X; abs_i <= ABS_MAX; abs_i++) { 33 | if(HasSpecificAbs(device_fd, abs_i)) { 34 | struct input_absinfo absinfo {}; 35 | if (ioctl(device_fd, EVIOCGABS(abs_i), &absinfo) == 0) { 36 | struct uinput_abs_setup uinputAbsInfo {}; 37 | memset(&uinputAbsInfo, 0, sizeof(uinputAbsInfo)); 38 | uinputAbsInfo.code = abs_i; 39 | uinputAbsInfo.absinfo = absinfo; 40 | ioctl(uinput_fd, UI_ABS_SETUP, &uinputAbsInfo); 41 | } 42 | } 43 | } 44 | } 45 | 46 | void SetKeyBits(int device_fd, int uinput_fd) { 47 | for(int key_i = BTN_MOUSE; key_i <= KEY_MAX; key_i++) { 48 | if (HasSpecificKey(device_fd, key_i)) { 49 | ioctl(uinput_fd, UI_SET_KEYBIT, key_i); 50 | } 51 | } 52 | if(!HasSpecificKey(device_fd, BTN_TOUCH)) { 53 | ioctl(uinput_fd, UI_SET_KEYBIT, BTN_TOUCH); 54 | } 55 | } 56 | 57 | 58 | void SetEventTypeBits(int device_fd, int uinput_fd) { 59 | for(int ev_i = EV_SYN; ev_i <= EV_MAX; ev_i++) { 60 | if (HasEventType(device_fd, ev_i)) { 61 | ioctl(uinput_fd, UI_SET_EVBIT, ev_i); 62 | } 63 | } 64 | } 65 | 66 | int SetupUinputDevice(int device_fd) { 67 | struct uinput_setup uinputSetup {}; 68 | int uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 69 | 70 | if (uinput_fd <= 0) exit(EXIT_FAILURE); 71 | 72 | SetEventTypeBits(device_fd, uinput_fd); 73 | SetKeyBits(device_fd, uinput_fd); 74 | SetAbsInfoFrom(device_fd, uinput_fd); 75 | ioctl(uinput_fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); 76 | 77 | memset(&uinputSetup, 0, sizeof(uinputSetup)); 78 | 79 | strncpy(uinputSetup.name, x_virtual_touch, strlen(x_virtual_touch)); 80 | uinputSetup.id.version = 1; 81 | uinputSetup.id.bustype = BUS_VIRTUAL; 82 | ioctl(uinput_fd, UI_DEV_SETUP, &uinputSetup); 83 | 84 | if(ioctl(uinput_fd, UI_DEV_CREATE)) { 85 | close(uinput_fd); 86 | exit(EXIT_FAILURE); 87 | } 88 | return uinput_fd; 89 | } 90 | 91 | void start() 92 | { 93 | while(running) { 94 | if(poll(poll_fds.data(), poll_fds.size(), 1000) <= 0) 95 | continue; 96 | 97 | for (size_t i = 0; i < poll_fds.size(); i++) 98 | if (poll_fds[i].revents & POLLIN) 99 | if (read(poll_fds[i].fd, &ie, sizeof(ie)) == sizeof(struct input_event)){ 100 | if (ie.type == EV_KEY) ie.code = BTN_TOUCH; 101 | write(uinput_fds[i], &ie, sizeof(struct input_event)); 102 | } 103 | } 104 | } 105 | 106 | extern "C" 107 | JNIEXPORT void JNICALL 108 | Java_xtr_keymapper_server_InputService_startTouchpadDirect(JNIEnv *env, jobject thiz) { 109 | running = true; 110 | 111 | poll_fds.clear(); 112 | uinput_fds.clear(); 113 | 114 | std::vector touchpadDeviceFds = scanTouchpadDevices(); 115 | 116 | for (auto & evdev : touchpadDeviceFds) { 117 | poll_fds.push_back(pollfd{evdev, POLLIN, 0}); 118 | uinput_fds.push_back(SetupUinputDevice(evdev)); 119 | } 120 | 121 | if (poll_fds.empty()) { 122 | printf("I: No touchpad devices found\n"); 123 | return; 124 | } 125 | 126 | looper = std::thread(start); 127 | } 128 | 129 | extern "C" 130 | JNIEXPORT void JNICALL 131 | Java_xtr_keymapper_server_InputService_stopTouchpadDirect(JNIEnv *env, jobject thiz) { 132 | running = false; 133 | for (auto & poll_fd : poll_fds) { 134 | struct input_event ie {0, 0, EV_SYN, SYN_REPORT, 0}; 135 | write(poll_fd.fd, &ie, sizeof(struct input_event)); 136 | } 137 | for (size_t i = 0; i < poll_fds.size(); i++) { 138 | ioctl(uinput_fds[i], UI_DEV_DESTROY); 139 | close(uinput_fds[i]); 140 | close(poll_fds[i].fd); 141 | } 142 | looper.join(); 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/cpp/touchpad_relative.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "evdev_common.h" 17 | #include 18 | 19 | using std::string; 20 | 21 | std::vector poll_fds; 22 | std::vector uinput_fds; 23 | 24 | std::atomic running = false; 25 | 26 | std::thread looper; 27 | 28 | int SetupUinputDevice() { 29 | struct uinput_setup uinputSetup {}; 30 | int uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); 31 | 32 | if (uinput_fd <= 0) exit(EXIT_FAILURE); 33 | 34 | /* enable mouse buttons and relative events */ 35 | ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY); 36 | ioctl(uinput_fd, UI_SET_EVBIT, EV_REL); 37 | 38 | ioctl(uinput_fd, UI_SET_KEYBIT, BTN_MOUSE); 39 | ioctl(uinput_fd, UI_SET_KEYBIT, BTN_RIGHT); 40 | ioctl(uinput_fd, UI_SET_KEYBIT, BTN_MIDDLE); 41 | 42 | ioctl(uinput_fd, UI_SET_RELBIT, REL_X); 43 | ioctl(uinput_fd, UI_SET_RELBIT, REL_Y); 44 | 45 | memset(&uinputSetup, 0, sizeof(uinputSetup)); 46 | 47 | strncpy(uinputSetup.name, x_virtual_mouse, strlen(x_virtual_mouse)); 48 | uinputSetup.id.version = 1; 49 | uinputSetup.id.bustype = BUS_VIRTUAL; 50 | uinputSetup.id.vendor = 0x1234; /* sample vendor */ 51 | uinputSetup.id.product = 0x5678; /* sample product */ 52 | 53 | ioctl(uinput_fd, UI_DEV_SETUP, &uinputSetup); 54 | 55 | if(ioctl(uinput_fd, UI_DEV_CREATE)) { 56 | close(uinput_fd); 57 | exit(EXIT_FAILURE); 58 | } 59 | return uinput_fd; 60 | } 61 | 62 | void start() { 63 | signed int last_absX, last_absY; 64 | bool touch_down = false; 65 | struct input_event event {}, ie {}; 66 | while(true) { 67 | poll(poll_fds.data(), poll_fds.size(), -1); 68 | 69 | for (size_t i = 0; i < poll_fds.size(); i++) 70 | if (poll_fds[i].revents & POLLIN) 71 | if (read(poll_fds[i].fd, &event, sizeof(event)) == sizeof(struct input_event)){ 72 | switch (event.code) { 73 | case ABS_X: 74 | if (!touch_down || event.value <= 1) break; 75 | ie.type = EV_REL; 76 | ie.code = REL_X; 77 | ie.value = event.value - last_absX; 78 | write(uinput_fds[i], &ie, sizeof(ie)); 79 | last_absX = event.value; 80 | break; 81 | 82 | case ABS_Y: 83 | if (!touch_down || event.value <= 1) break; 84 | ie.type = EV_REL; 85 | ie.code = REL_Y; 86 | ie.value = event.value - last_absY; 87 | write(uinput_fds[i], &ie, sizeof(ie)); 88 | last_absY = event.value; 89 | break; 90 | 91 | case BTN_MOUSE: 92 | case BTN_RIGHT: 93 | case BTN_MIDDLE: 94 | write(uinput_fds[i], &event, sizeof(event)); 95 | break; 96 | 97 | case BTN_TOUCH: 98 | touch_down = event.value == 1; 99 | if (touch_down) printf("x %d\n", event.value); 100 | break; 101 | } 102 | ie = input_event{0, 0, EV_SYN, SYN_REPORT, 0}; 103 | write(uinput_fds[i], &ie, sizeof(ie)); 104 | } 105 | } 106 | } 107 | 108 | extern "C" 109 | JNIEXPORT void JNICALL 110 | Java_xtr_keymapper_server_InputService_startTouchpadRelative(JNIEnv *env, jobject thiz) { 111 | running = true; 112 | 113 | poll_fds.clear(); 114 | uinput_fds.clear(); 115 | 116 | std::vector touchpadDeviceFds = scanTouchpadDevices(); 117 | 118 | for (auto & evdev : touchpadDeviceFds) { 119 | poll_fds.push_back(pollfd{evdev, POLLIN, 0}); 120 | uinput_fds.push_back(SetupUinputDevice()); 121 | } 122 | 123 | if (poll_fds.empty()) { 124 | printf("I: No touchpad devices found\n"); 125 | return; 126 | } 127 | looper = std::thread(start); 128 | } 129 | 130 | extern "C" 131 | JNIEXPORT void JNICALL 132 | Java_xtr_keymapper_server_InputService_stopTouchpadRelative(JNIEnv *env, jobject thiz) { 133 | running = false; 134 | 135 | for (auto & poll_fd : poll_fds) { 136 | struct input_event ie{0, 0, EV_SYN, SYN_REPORT, 0}; 137 | write(poll_fd.fd, &ie, sizeof(struct input_event)); 138 | } 139 | for (size_t i = 0; i < poll_fds.size(); i++) { 140 | ioctl(uinput_fds[i], UI_DEV_DESTROY); 141 | close(uinput_fds[i]); 142 | close(poll_fds[i].fd); 143 | } 144 | looper.join(); 145 | } -------------------------------------------------------------------------------- /app/src/main/java/com/genymobile/scrcpy/Point.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy; 2 | 3 | import java.util.Objects; 4 | 5 | public class Point { 6 | private final float x; 7 | private final float y; 8 | 9 | public Point(float x, float y) { 10 | this.x = x; 11 | this.y = y; 12 | } 13 | 14 | public float getX() { 15 | return x; 16 | } 17 | 18 | public float getY() { 19 | return y; 20 | } 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) { 25 | return true; 26 | } 27 | if (o == null || getClass() != o.getClass()) { 28 | return false; 29 | } 30 | Point point = (Point) o; 31 | return x == point.x && y == point.y; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return Objects.hash(x, y); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "Point{" + "x=" + x + ", y=" + y + '}'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/genymobile/scrcpy/Pointer.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy; 2 | 3 | public class Pointer { 4 | 5 | /** 6 | * Pointer id as received from the client. 7 | */ 8 | private final long id; 9 | 10 | /** 11 | * Local pointer id, using the lowest possible values to fill the {@link android.view.MotionEvent.PointerProperties PointerProperties}. 12 | */ 13 | private final int localId; 14 | 15 | private Point point; 16 | private float pressure; 17 | private boolean up; 18 | 19 | public Pointer(long id, int localId) { 20 | this.id = id; 21 | this.localId = localId; 22 | } 23 | 24 | public long getId() { 25 | return id; 26 | } 27 | 28 | public int getLocalId() { 29 | return localId; 30 | } 31 | 32 | public Point getPoint() { 33 | return point; 34 | } 35 | 36 | public void setPoint(Point point) { 37 | this.point = point; 38 | } 39 | 40 | public float getPressure() { 41 | return pressure; 42 | } 43 | 44 | public void setPressure(float pressure) { 45 | this.pressure = pressure; 46 | } 47 | 48 | public boolean isUp() { 49 | return up; 50 | } 51 | 52 | public void setUp(boolean up) { 53 | this.up = up; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/genymobile/scrcpy/PointersState.java: -------------------------------------------------------------------------------- 1 | package com.genymobile.scrcpy; 2 | 3 | import android.view.MotionEvent; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class PointersState { 9 | 10 | public static final int MAX_POINTERS = 10; 11 | 12 | private final List pointers = new ArrayList<>(); 13 | 14 | private int indexOf(long id) { 15 | for (int i = 0; i < pointers.size(); ++i) { 16 | Pointer pointer = pointers.get(i); 17 | if (pointer.getId() == id) { 18 | return i; 19 | } 20 | } 21 | return -1; 22 | } 23 | 24 | private boolean isLocalIdAvailable(int localId) { 25 | for (int i = 0; i < pointers.size(); ++i) { 26 | Pointer pointer = pointers.get(i); 27 | if (pointer.getLocalId() == localId) { 28 | return false; 29 | } 30 | } 31 | return true; 32 | } 33 | 34 | private int nextUnusedLocalId() { 35 | for (int localId = 0; localId < MAX_POINTERS; ++localId) { 36 | if (isLocalIdAvailable(localId)) { 37 | return localId; 38 | } 39 | } 40 | return -1; 41 | } 42 | 43 | public Pointer get(int index) { 44 | return pointers.get(index); 45 | } 46 | 47 | public int getPointerIndex(long id) { 48 | int index = indexOf(id); 49 | if (index != -1) { 50 | // already exists, return it 51 | return index; 52 | } 53 | if (pointers.size() >= MAX_POINTERS) { 54 | // it's full 55 | return -1; 56 | } 57 | // id 0 is reserved for mouse events 58 | int localId = nextUnusedLocalId(); 59 | if (localId == -1) { 60 | throw new AssertionError("pointers.size() < maxFingers implies that a local id is available"); 61 | } 62 | Pointer pointer = new Pointer(id, localId); 63 | pointers.add(pointer); 64 | // return the index of the pointer 65 | return pointers.size() - 1; 66 | } 67 | 68 | /** 69 | * Initialize the motion event parameters. 70 | * 71 | * @param props the pointer properties 72 | * @param coords the pointer coordinates 73 | * @return The number of items initialized (the number of pointers). 74 | */ 75 | public int update(MotionEvent.PointerProperties[] props, MotionEvent.PointerCoords[] coords) { 76 | int count = pointers.size(); 77 | for (int i = 0; i < count; ++i) { 78 | Pointer pointer = pointers.get(i); 79 | 80 | // id 0 is reserved for mouse events 81 | props[i].id = pointer.getLocalId(); 82 | 83 | Point point = pointer.getPoint(); 84 | coords[i].x = point.getX(); 85 | coords[i].y = point.getY(); 86 | coords[i].pressure = pointer.getPressure(); 87 | } 88 | cleanUp(); 89 | return count; 90 | } 91 | 92 | /** 93 | * Remove all pointers which are UP. 94 | */ 95 | private void cleanUp() { 96 | for (int i = pointers.size() - 1; i >= 0; --i) { 97 | Pointer pointer = pointers.get(i); 98 | if (pointer.isUp()) { 99 | pointers.remove(i); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/InputEventCodes.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper; 2 | 3 | public class InputEventCodes { 4 | public static final int REL_X = 0; 5 | public static final int REL_Y = 1; 6 | public static final int REL_WHEEL = 8; 7 | public static final int BTN_MOUSE = 272; 8 | public static final int BTN_RIGHT = 273; 9 | public static final int BTN_MIDDLE = 274; 10 | public static final int BTN_SIDE = 275; 11 | public static final int BTN_EXTRA = 276; 12 | public static final String[] ARROW_KEYS = {"KEY_UP", "KEY_DOWN", "KEY_LEFT", "KEY_RIGHT"}; 13 | public static final String[] WASD_KEYS = {"KEY_W", "KEY_S", "KEY_A", "KEY_D"}; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/Server.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | import android.util.Log; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | import java.io.File; 12 | import java.io.FileReader; 13 | import java.io.FileWriter; 14 | import java.io.IOException; 15 | import java.nio.CharBuffer; 16 | 17 | import xtr.keymapper.activity.MainActivity; 18 | import xtr.keymapper.server.RemoteServiceShell; 19 | 20 | public class Server { 21 | 22 | private static void writeScript(StringBuffer linesToWrite, File scriptFile) throws IOException, InterruptedException { 23 | FileWriter fileWriter = new FileWriter(scriptFile); 24 | FileReader fileReader = new FileReader(scriptFile); 25 | 26 | CharBuffer target = CharBuffer.allocate((int) scriptFile.length()); 27 | fileReader.read(target); 28 | 29 | // Write script to disk only if file contents are not the same. 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 31 | if (linesToWrite.compareTo(new StringBuffer(target)) != 0) { 32 | fileWriter.write(linesToWrite.toString()); 33 | } 34 | } else { 35 | if (!linesToWrite.toString().equals(target.toString())) 36 | fileWriter.write(linesToWrite.toString()); 37 | } 38 | fileWriter.close(); 39 | fileReader.close(); 40 | } 41 | 42 | private static @NonNull StringBuffer generateScript(ApplicationInfo ai) { 43 | final String className = RemoteServiceShell.class.getName(); 44 | StringBuffer linesToWrite = new StringBuffer(); 45 | linesToWrite.append("#!/system/bin/sh\n"); 46 | linesToWrite.append("pkill -f ").append(className).append("\n"); 47 | linesToWrite.append("exec /system/bin/app_process"); 48 | linesToWrite.append(" -Djava.library.path=\"").append(ai.nativeLibraryDir) //path containing lib*.so 49 | .append("\" -Djava.class.path=\"").append(ai.publicSourceDir) // Absolute path to apk in /data/app 50 | .append("\" / ").append(className) 51 | .append(" \"$@\" \n"); 52 | return linesToWrite; 53 | } 54 | 55 | public static void setupServer(Context context, MainActivity.Callback mCallback) { 56 | File script = new File(context.getExternalFilesDir(null), "xtMapper.sh"); 57 | try { 58 | PackageManager pm = context.getPackageManager(); 59 | String packageName = context.getPackageName(); 60 | ApplicationInfo ai = pm.getApplicationInfo(packageName, 0); 61 | writeScript(generateScript(ai), script); 62 | } catch (IOException | InterruptedException | PackageManager.NameNotFoundException ex) { 63 | Log.e("Server", ex.toString()); 64 | mCallback.updateCmdView1("failed to write script: " + ex); 65 | } 66 | if (!script.exists()) mCallback.updateCmdView1("failed to write script: permission denied"); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/Utils.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper; 2 | 3 | import android.graphics.PixelFormat; 4 | import android.os.Build; 5 | import android.view.WindowManager; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.DataOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | 12 | public class Utils { 13 | public static final String alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 14 | 15 | /** 16 | * @param key input key code KEY_X 17 | * @return the index of X in alphabet 18 | */ 19 | public static int obtainIndex(String key) { 20 | return alphabet.indexOf(key.substring(4)); 21 | } 22 | 23 | public static BufferedReader geteventStream(String nativeLibraryDir) throws IOException { 24 | Process sh = Runtime.getRuntime().exec("sh"); 25 | DataOutputStream outputStream = new DataOutputStream(sh.getOutputStream()); 26 | 27 | outputStream.writeBytes("exec env LD_PRELOAD=" + nativeLibraryDir + "/libgetevent.so getevent -ql\n"); 28 | outputStream.flush(); 29 | 30 | return new BufferedReader(new InputStreamReader(sh.getInputStream())); 31 | } 32 | 33 | public static WindowManager.LayoutParams getPointerLayoutParams(int type) { 34 | WindowManager.LayoutParams mParams = new WindowManager.LayoutParams( 35 | WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, 36 | type, 37 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | 38 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 39 | WindowManager.LayoutParams.FLAG_FULLSCREEN | 40 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | 41 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR, 42 | // Make the underlying application window visible through the cursor 43 | PixelFormat.TRANSLUCENT); 44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 45 | mParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 46 | } 47 | return mParams; 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/activity/InfoActivity.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.activity; 2 | 3 | import android.net.Uri; 4 | import android.os.Bundle; 5 | import android.webkit.WebResourceRequest; 6 | import android.webkit.WebResourceResponse; 7 | import android.webkit.WebSettings; 8 | import android.webkit.WebView; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | import androidx.webkit.WebViewAssetLoader; 14 | import androidx.webkit.WebViewClientCompat; 15 | 16 | import xtr.keymapper.R; 17 | 18 | public class InfoActivity extends AppCompatActivity { 19 | 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | setContentView(R.layout.activity_info); 25 | WebView webView = findViewById(R.id.rootView); 26 | final WebViewAssetLoader assetLoader = new WebViewAssetLoader.Builder() 27 | .addPathHandler("/", new WebViewAssetLoader.AssetsPathHandler(this)) 28 | .build(); 29 | webView.setWebViewClient(new WebViewClientCompat() { 30 | @Override 31 | public void onReceivedHttpError(@NonNull WebView view, @NonNull WebResourceRequest request, @NonNull WebResourceResponse errorResponse) { 32 | if (!request.getUrl().getPath().contains("index.html")) 33 | webView.loadUrl(request.getUrl() + "/index.html"); 34 | super.onReceivedHttpError(view, request, errorResponse); 35 | } 36 | 37 | @Nullable 38 | @Override 39 | public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { 40 | Uri url = request.getUrl(); 41 | String urlPath = url.getPath(); 42 | if (urlPath.endsWith("/") && !urlPath.contains("index.html")) 43 | url = request.getUrl().buildUpon().appendPath("index.html").build(); 44 | 45 | return assetLoader.shouldInterceptRequest(url); 46 | } 47 | }); 48 | WebSettings webSettings = webView.getSettings(); 49 | webSettings.setJavaScriptEnabled(true); 50 | webSettings.setJavaScriptCanOpenWindowsAutomatically(true); 51 | webSettings.setDomStorageEnabled(true); 52 | webView.loadUrl("https://appassets.androidplatform.net/XtMapper-docs/index.html"); 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/activity/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.activity.ui.theme 2 | 3 | import android.os.Build 4 | import androidx.compose.foundation.isSystemInDarkTheme 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.dynamicDarkColorScheme 7 | import androidx.compose.material3.dynamicLightColorScheme 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.platform.LocalContext 10 | 11 | 12 | @Composable 13 | fun XtMapperTheme( 14 | darkTheme: Boolean = isSystemInDarkTheme(), 15 | // Dynamic color is available on Android 12+ 16 | dynamicColor: Boolean = true, 17 | content: @Composable () -> Unit 18 | ) { 19 | val colorScheme = when { 20 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 21 | val context = LocalContext.current 22 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 23 | } 24 | else -> MaterialTheme.colorScheme 25 | } 26 | 27 | MaterialTheme( 28 | colorScheme = colorScheme, 29 | typography = Typography, 30 | content = content 31 | ) 32 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/activity/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.activity.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/dpad/Dpad.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.dpad; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import xtr.keymapper.floatingkeys.MovableFrameLayout; 9 | 10 | public class Dpad implements Parcelable { 11 | private float viewX, viewY; 12 | private final int width, height; 13 | final float xOfCenter, yOfCenter; 14 | final float radius; 15 | public final DpadKeyCodes keycodes; 16 | public static final int MAX_DPADS = 2; 17 | public static final String TAG = "DPAD"; 18 | public static final String UDLR = "DPAD_UDLR"; 19 | private final String tag; 20 | 21 | public Dpad (MovableFrameLayout dpad, DpadKeyCodes keycodes, String tag) { 22 | this.keycodes = keycodes; 23 | this.tag = tag; 24 | viewX = dpad.getX(); 25 | viewY = dpad.getY(); 26 | radius = dpad.getPivotX(); 27 | xOfCenter = viewX + radius; 28 | yOfCenter = viewY + radius; 29 | width = dpad.getLayoutParams().width; 30 | height = dpad.getLayoutParams().height; 31 | } 32 | 33 | public Dpad (String[] data){ 34 | this.tag = data[0]; 35 | viewX = Float.parseFloat(data[1]); // x y coordinates for use in EditorUI 36 | viewY = Float.parseFloat(data[2]); 37 | radius = Float.parseFloat(data[3]); // radius of dpad 38 | xOfCenter = Float.parseFloat(data[4]); // absolute x position of pivot (center) 39 | yOfCenter = Float.parseFloat(data[5]); // absolute y position of pivot (center) 40 | width = Integer.parseInt(data[6]); 41 | height = Integer.parseInt(data[7]); 42 | keycodes = new DpadKeyCodes(new String[]{data[8], data[9], data[10], data[11]}); 43 | } 44 | 45 | protected Dpad(Parcel in) { 46 | tag = in.readString(); 47 | viewX = in.readFloat(); 48 | viewY = in.readFloat(); 49 | width = in.readInt(); 50 | height = in.readInt(); 51 | xOfCenter = in.readFloat(); 52 | yOfCenter = in.readFloat(); 53 | radius = in.readFloat(); 54 | keycodes = in.readParcelable(DpadKeyCodes.class.getClassLoader()); 55 | } 56 | 57 | public static final Creator CREATOR = new Creator<>() { 58 | @Override 59 | public Dpad createFromParcel(Parcel in) { 60 | return new Dpad(in); 61 | } 62 | 63 | @Override 64 | public Dpad[] newArray(int size) { 65 | return new Dpad[size]; 66 | } 67 | }; 68 | 69 | public String getData(){ 70 | return tag + " " + 71 | viewX + " " + 72 | viewY + " " + 73 | radius + " " + 74 | xOfCenter + " " + 75 | yOfCenter + " " + 76 | width + " " + 77 | height + " " + 78 | keycodes.Up + " " + 79 | keycodes.Down + " " + 80 | keycodes.Left + " " + 81 | keycodes.Right; 82 | } 83 | 84 | public float getX() { 85 | return viewX; 86 | } 87 | 88 | public float getY() { 89 | return viewY; 90 | } 91 | 92 | public int getWidth() { 93 | return width; 94 | } 95 | 96 | public void scale(float scaleX, float scaleY) { 97 | viewX *= scaleX; 98 | viewY *= scaleY; 99 | } 100 | 101 | public int getHeight() { 102 | return height; 103 | } 104 | 105 | @Override 106 | public int describeContents() { 107 | return 0; 108 | } 109 | 110 | @Override 111 | public void writeToParcel(@NonNull Parcel dest, int flags) { 112 | dest.writeString(tag); 113 | dest.writeFloat(viewX); 114 | dest.writeFloat(viewY); 115 | dest.writeInt(width); 116 | dest.writeInt(height); 117 | dest.writeFloat(xOfCenter); 118 | dest.writeFloat(yOfCenter); 119 | dest.writeFloat(radius); 120 | dest.writeParcelable(keycodes, flags); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/dpad/DpadKeyCodes.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.dpad; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import xtr.keymapper.databinding.DpadBinding; 9 | 10 | public class DpadKeyCodes implements Parcelable { 11 | public final String Up, Down, Left, Right; 12 | 13 | public DpadKeyCodes(String[] in){ 14 | Up = in[0]; 15 | Down = in[1]; 16 | Left = in[2]; 17 | Right = in[3]; 18 | } 19 | 20 | public DpadKeyCodes(DpadBinding binding){ 21 | Up = "KEY_" + binding.keyUp.getText(); 22 | Down = "KEY_" + binding.keyDown.getText(); 23 | Left = "KEY_" + binding.keyLeft.getText(); 24 | Right = "KEY_" + binding.keyRight.getText(); 25 | } 26 | 27 | protected DpadKeyCodes(Parcel in) { 28 | Up = in.readString(); 29 | Down = in.readString(); 30 | Left = in.readString(); 31 | Right = in.readString(); 32 | } 33 | 34 | public static final Creator CREATOR = new Creator<>() { 35 | @Override 36 | public DpadKeyCodes createFromParcel(Parcel in) { 37 | return new DpadKeyCodes(in); 38 | } 39 | 40 | @Override 41 | public DpadKeyCodes[] newArray(int size) { 42 | return new DpadKeyCodes[size]; 43 | } 44 | }; 45 | 46 | @Override 47 | public int describeContents() { 48 | return 0; 49 | } 50 | 51 | @Override 52 | public void writeToParcel(@NonNull Parcel dest, int flags) { 53 | dest.writeString(Up); 54 | dest.writeString(Down); 55 | dest.writeString(Left); 56 | dest.writeString(Right); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/editor/EditorActivity.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.editor; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.os.RemoteException; 6 | import android.provider.Settings; 7 | import android.util.Log; 8 | import android.view.WindowManager; 9 | 10 | import androidx.appcompat.app.AlertDialog; 11 | import androidx.core.view.WindowCompat; 12 | import androidx.core.view.WindowInsetsCompat; 13 | import androidx.core.view.WindowInsetsControllerCompat; 14 | 15 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; 16 | 17 | import xtr.keymapper.IRemoteService; 18 | import xtr.keymapper.R; 19 | import xtr.keymapper.activity.MainActivity; 20 | import xtr.keymapper.keymap.KeymapConfig; 21 | import xtr.keymapper.server.RemoteServiceHelper; 22 | 23 | public class EditorActivity extends Activity implements EditorCallback { 24 | public static final String PROFILE_NAME = "profile"; 25 | private EditorUI editor; 26 | private IRemoteService mService; 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | 32 | String selectedProfile = getIntent().getStringExtra(PROFILE_NAME); 33 | if (selectedProfile == null) { 34 | finish(); 35 | return; 36 | } 37 | 38 | WindowInsetsControllerCompat windowInsetsController = WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView()); 39 | windowInsetsController.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); 40 | windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()); 41 | RemoteServiceHelper.getInstance(this, service -> mService = service); 42 | 43 | if (editor != null) editor.hideView(); 44 | 45 | setTheme(R.style.Theme_XtMapper); 46 | 47 | editor = new EditorUI(this, this, selectedProfile, EditorUI.START_EDITOR); 48 | 49 | KeymapConfig keymapConfig = new KeymapConfig(this); 50 | if (Settings.canDrawOverlays(this)) { 51 | editor.open(keymapConfig.editorOverlay); 52 | } else { 53 | editor.open(false); 54 | MainActivity.checkOverlayPermission(this); 55 | } 56 | 57 | if (getEvent()) 58 | // Can receive key events from remote service 59 | try { 60 | mService.registerOnKeyEventListener(editor); 61 | mService.pauseMouse(); 62 | } catch (RemoteException e) { 63 | Log.e("editorActivity", e.getMessage(), e); 64 | } 65 | else { 66 | MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); 67 | 68 | builder.setMessage(R.string.dialog_alert_editor) 69 | .setPositiveButton(R.string.ok, (dialog, which) -> {}) 70 | .setTitle(R.string.dialog_alert_editor_title); 71 | AlertDialog dialog = builder.create(); 72 | if (keymapConfig.editorOverlay) dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); 73 | dialog.show(); 74 | } 75 | } 76 | 77 | @Override 78 | protected void onDestroy() { 79 | super.onDestroy(); 80 | if (getEvent()) try { 81 | mService.unregisterOnKeyEventListener(editor); 82 | mService.resumeMouse(); 83 | mService.reloadKeymap(); 84 | } catch (RemoteException ignored) { 85 | } 86 | editor = null; 87 | } 88 | 89 | @Override 90 | public void onHideView() { 91 | finish(); 92 | } 93 | 94 | @Override 95 | public boolean getEvent() { 96 | return mService != null; 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/editor/EditorCallback.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.editor; 2 | 3 | /** 4 | * Callback when editor UI is hidden 5 | */ 6 | public interface EditorCallback { 7 | void onHideView(); 8 | 9 | /** 10 | * @return true if getevent is running 11 | */ 12 | boolean getEvent(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/editor/EditorService.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.editor; 2 | 3 | import android.app.Service; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.IBinder; 7 | import android.os.RemoteException; 8 | import android.util.Log; 9 | 10 | import androidx.appcompat.view.ContextThemeWrapper; 11 | 12 | import xtr.keymapper.R; 13 | import xtr.keymapper.keymap.KeymapConfig; 14 | import xtr.keymapper.server.RemoteServiceHelper; 15 | 16 | public class EditorService extends Service { 17 | private EditorUI editor; 18 | 19 | private final EditorCallback editorCallback = new EditorCallback() { 20 | @Override 21 | public void onHideView() { 22 | RemoteServiceHelper.getInstance(EditorService.this, service -> { 23 | try { 24 | service.unregisterOnKeyEventListener(editor); 25 | service.resumeMouse(); 26 | service.reloadKeymap(); 27 | } catch (RemoteException e) { 28 | throw new RuntimeException(e); 29 | } 30 | }); 31 | editor = null; 32 | stopSelf(); 33 | } 34 | 35 | @Override 36 | public boolean getEvent() { 37 | return true; 38 | } 39 | }; 40 | 41 | 42 | @Override 43 | public int onStartCommand(Intent intent, int flags, int startId) { 44 | String selectedProfile = intent.getStringExtra(EditorActivity.PROFILE_NAME); 45 | 46 | if (selectedProfile == null) { 47 | selectedProfile = "Default"; 48 | } 49 | 50 | 51 | KeymapConfig keymapConfig = new KeymapConfig(this); 52 | if (keymapConfig.editorOverlay) { 53 | Context context = new ContextThemeWrapper(EditorService.this, R.style.Theme_XtMapper); 54 | editor = new EditorUI(context, editorCallback, selectedProfile, EditorUI.START_EDITOR); 55 | 56 | RemoteServiceHelper.getInstance(EditorService.this, remoteService -> { 57 | try { 58 | if (editor != null) { 59 | remoteService.registerOnKeyEventListener(editor); 60 | remoteService.pauseMouse(); 61 | } 62 | } catch (RemoteException e) { 63 | Log.e("editorActivity", e.getMessage(), e); 64 | } 65 | }); 66 | 67 | editor.open(true); 68 | } else { 69 | Intent newIntent = new Intent(this, EditorActivity.class) 70 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 71 | .addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 72 | newIntent.putExtra(EditorActivity.PROFILE_NAME, selectedProfile); 73 | startActivity(newIntent); 74 | } 75 | 76 | return super.onStartCommand(intent, flags, startId); 77 | } 78 | 79 | @Override 80 | public IBinder onBind(Intent intent) { 81 | return null; 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/editor/ResizeableDpadView.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.editor; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.view.MotionEvent; 5 | import android.view.View; 6 | 7 | class ResizeableDpadView implements View.OnTouchListener { 8 | final View rootView; 9 | float defaultPivotX, defaultPivotY; 10 | 11 | public ResizeableDpadView(View rootView) { 12 | this.rootView = rootView; 13 | } 14 | 15 | private void getDefaultPivotXY() { 16 | defaultPivotX = rootView.getPivotX(); 17 | defaultPivotY = rootView.getPivotY(); 18 | } 19 | 20 | @SuppressLint("ClickableViewAccessibility") 21 | @Override 22 | public boolean onTouch(View v, MotionEvent event) { 23 | if (event.getAction() == MotionEvent.ACTION_MOVE) { 24 | // Resize View in fixed ratio 25 | int newSize = ((int)event.getX() + (int)event.getY()) / 2; 26 | EditorUI.resizeView(rootView, newSize, newSize); 27 | 28 | // Resize View from center point 29 | if (defaultPivotX > 0) { 30 | float newPivotX = rootView.getPivotX() - defaultPivotX; 31 | float newPivotY = rootView.getPivotY() - defaultPivotY; 32 | rootView.setX(rootView.getX() - newPivotX); 33 | rootView.setY(rootView.getY() - newPivotY); 34 | } 35 | getDefaultPivotXY(); 36 | } 37 | return v.onTouchEvent(event); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/editor/ShowKeymapService.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.editor; 2 | 3 | import android.app.Service; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.os.IBinder; 7 | 8 | import androidx.appcompat.view.ContextThemeWrapper; 9 | 10 | import xtr.keymapper.R; 11 | import xtr.keymapper.keymap.KeymapConfig; 12 | 13 | public class ShowKeymapService extends Service { 14 | private EditorUI editorUi; 15 | 16 | public static void start(Context context, String selectedProfile) { 17 | Intent intent = new Intent(context, ShowKeymapService.class); 18 | intent.putExtra(EditorActivity.PROFILE_NAME, selectedProfile); 19 | context.startService(intent); 20 | } 21 | 22 | @Override 23 | public void onDestroy() { 24 | editorUi.hideView(); 25 | super.onDestroy(); 26 | } 27 | 28 | @Override 29 | public int onStartCommand(Intent intent, int flags, int startId) { 30 | String selectedProfile = intent.getStringExtra(EditorActivity.PROFILE_NAME); 31 | 32 | if (selectedProfile == null) { 33 | selectedProfile = "Default"; 34 | } 35 | KeymapConfig keymapConfig = new KeymapConfig(this); 36 | Context context = new ContextThemeWrapper(this, R.style.Theme_XtMapper); 37 | editorUi = new EditorUI(context, editorCallback, selectedProfile, EditorUI.SHOW_KEYMAP_ONLY); 38 | editorUi.loadKeymapAfterView(); 39 | editorUi.showControls(keymapConfig.showControlsOpacity); 40 | return super.onStartCommand(intent, flags, startId); 41 | } 42 | 43 | private final EditorCallback editorCallback = new EditorCallback() { 44 | @Override 45 | public void onHideView() { 46 | editorUi = null; 47 | } 48 | 49 | @Override 50 | public boolean getEvent() { 51 | return false; 52 | } 53 | }; 54 | 55 | 56 | @Override 57 | public IBinder onBind(Intent intent) { 58 | return null; 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/floatingkeys/FloatingActionKey.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.floatingkeys; 2 | 3 | import android.content.Context; 4 | import android.content.res.ColorStateList; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.util.AttributeSet; 10 | 11 | import androidx.appcompat.content.res.AppCompatResources; 12 | 13 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 14 | import com.google.android.material.shape.RelativeCornerSize; 15 | import com.google.android.material.shape.RoundedCornerTreatment; 16 | import com.google.android.material.shape.ShapeAppearanceModel; 17 | import xtr.keymapper.R; 18 | 19 | import java.util.Random; 20 | 21 | public class FloatingActionKey extends FloatingActionButton { 22 | 23 | public String key = "X"; 24 | private ColorStateList colorInactive; 25 | private ColorStateList textColor; 26 | private ColorStateList textColorInactive; 27 | 28 | public FloatingActionKey(Context context) { 29 | super(context); 30 | colorInactive = AppCompatResources.getColorStateList(context, R.color.grey); 31 | textColor = AppCompatResources.getColorStateList(context, R.color.black); 32 | textColorInactive = AppCompatResources.getColorStateList(context, R.color.white); 33 | init(); 34 | } 35 | 36 | public FloatingActionKey(Context context, AttributeSet attrs) { 37 | super(context, attrs); 38 | init(); 39 | } 40 | 41 | public FloatingActionKey(Context context, AttributeSet attrs, int defStyleAttr) { 42 | super(context, attrs, defStyleAttr); 43 | init(); 44 | } 45 | 46 | private void init() { 47 | setShapeAppearanceModel(new ShapeAppearanceModel() 48 | .toBuilder() 49 | .setAllCorners(new RoundedCornerTreatment()).setAllCornerSizes(new RelativeCornerSize(0.5f)) 50 | .build()); 51 | } 52 | 53 | public void setButtonActive(){ 54 | Random rnd = new Random(); 55 | int color = Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256)); 56 | setBackgroundTintList(ColorStateList.valueOf(color)); 57 | setImageTintList(textColor); 58 | } 59 | 60 | public void setButtonInactive(){ 61 | setBackgroundTintList(colorInactive); 62 | setImageTintList(textColorInactive); 63 | } 64 | 65 | public void setText(String text, int size) { 66 | this.key = text; 67 | setButtonActive(); 68 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 69 | paint.setTextSize(size); 70 | paint.setFakeBoldText(true); 71 | paint.setColor(Color.BLACK); 72 | paint.setTextAlign(Paint.Align.LEFT); 73 | float baseline = -paint.ascent(); // ascent() is negative 74 | int width = (int) (paint.measureText(text) + 0.0f); // round 75 | int height = (int) (baseline + paint.descent() + 0.0f); 76 | Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 77 | 78 | Canvas canvas = new Canvas(image); 79 | canvas.drawText(text, 0, baseline, paint); 80 | setImageBitmap(image); 81 | setScaleType(ScaleType.CENTER); 82 | } 83 | 84 | @Override 85 | public void setImageResource(int resId) { 86 | super.setImageResource(resId); 87 | setScaleType(ScaleType.CENTER); 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/floatingkeys/MovableFrameLayout.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.floatingkeys; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.MotionEvent; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.FrameLayout; 9 | 10 | public class MovableFrameLayout extends FrameLayout implements View.OnTouchListener { 11 | private float dX, dY; 12 | 13 | public MovableFrameLayout(Context context) { 14 | super(context); 15 | init(); 16 | } 17 | 18 | public MovableFrameLayout(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | init(); 21 | } 22 | 23 | public MovableFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { 24 | super(context, attrs, defStyleAttr); 25 | init(); 26 | } 27 | 28 | private void init() { 29 | setOnTouchListener(this); 30 | } 31 | 32 | @Override 33 | public boolean onTouch(View view, MotionEvent motionEvent){ 34 | ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)view.getLayoutParams(); 35 | 36 | int action = motionEvent.getAction(); 37 | switch (action) { 38 | case MotionEvent.ACTION_DOWN: { 39 | dX = view.getX() - motionEvent.getRawX(); 40 | dY = view.getY() - motionEvent.getRawY(); 41 | return true; // Consumed 42 | } 43 | case MotionEvent.ACTION_MOVE: { 44 | int viewWidth = view.getWidth(); 45 | int viewHeight = view.getHeight(); 46 | 47 | View viewParent = (View)view.getParent(); 48 | int parentWidth = viewParent.getWidth(); 49 | int parentHeight = viewParent.getHeight(); 50 | 51 | float newX = motionEvent.getRawX() + dX; 52 | newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent 53 | newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent 54 | 55 | float newY = motionEvent.getRawY() + dY; 56 | newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent 57 | newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent 58 | 59 | view.animate() 60 | .x(newX) 61 | .y(newY) 62 | .setDuration(0) 63 | .start(); 64 | return true; // Consumed 65 | } 66 | default: 67 | return view.onTouchEvent(motionEvent); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/keymap/KeymapProfile.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.keymap; 2 | 3 | import static xtr.keymapper.dpad.Dpad.MAX_DPADS; 4 | 5 | import android.content.Context; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import xtr.keymapper.BuildConfig; 14 | import xtr.keymapper.dpad.Dpad; 15 | import xtr.keymapper.macro.Macro; 16 | import xtr.keymapper.macro.MacroSharedPreferences; 17 | import xtr.keymapper.mouse.MouseAimConfig; 18 | import xtr.keymapper.swipekey.SwipeKey; 19 | 20 | public class KeymapProfile implements Parcelable { 21 | public String packageName = BuildConfig.APPLICATION_ID; 22 | public final Dpad[] dpadArray; 23 | public MouseAimConfig mouseAimConfig = null; 24 | public ArrayList keys = new ArrayList<>(); 25 | public ArrayList swipeKeys = new ArrayList<>(); 26 | public KeymapProfileKey rightClick; 27 | public boolean disabled = false; 28 | public Dpad dpadUdlr; 29 | public int xRes, yRes; 30 | public final HashMap macroIdMap = new HashMap<>(); 31 | 32 | public KeymapProfile() { 33 | dpadArray = new Dpad[MAX_DPADS]; 34 | } 35 | 36 | public void scale(float newWidth, float newHeight) { 37 | float scaleX = 1, scaleY = 1; 38 | 39 | if (xRes > 0) 40 | scaleX = newWidth / xRes; 41 | 42 | if (yRes > 0) 43 | scaleY = newHeight / yRes; 44 | 45 | if (xRes > 0 && yRes > 0) { 46 | scaleKeys(scaleX, scaleY); 47 | } 48 | } 49 | 50 | private void scaleKeys(float scaleX, float scaleY) { 51 | if (dpadUdlr != null) dpadUdlr.scale(scaleX, scaleY); 52 | 53 | if (rightClick != null) { 54 | rightClick.x *= scaleX; 55 | rightClick.y *= scaleY; 56 | } 57 | 58 | for (SwipeKey swipeKey : swipeKeys) { 59 | swipeKey.key1.x *= scaleX; 60 | swipeKey.key1.y *= scaleY; 61 | 62 | swipeKey.key2.x *= scaleX; 63 | swipeKey.key2.y *= scaleY; 64 | } 65 | 66 | for (KeymapProfileKey key : keys) { 67 | key.x *= scaleX; 68 | key.y *= scaleY; 69 | } 70 | 71 | for (Dpad dpad : dpadArray) { 72 | if (dpad != null) dpad.scale(scaleX, scaleY); 73 | } 74 | if (mouseAimConfig != null) { 75 | mouseAimConfig.xCenter *= scaleX; 76 | mouseAimConfig.yCenter *= scaleY; 77 | 78 | mouseAimConfig.xleftClick *= scaleX; 79 | mouseAimConfig.yleftClick *= scaleY; 80 | 81 | mouseAimConfig.width *= scaleX; 82 | mouseAimConfig.height *= scaleY; 83 | } 84 | } 85 | 86 | protected KeymapProfile(Parcel in) { 87 | packageName = in.readString(); 88 | dpadArray = in.createTypedArray(Dpad.CREATOR); 89 | mouseAimConfig = in.readParcelable(MouseAimConfig.class.getClassLoader()); 90 | keys = in.createTypedArrayList(KeymapProfileKey.CREATOR); 91 | swipeKeys = in.createTypedArrayList(SwipeKey.CREATOR); 92 | rightClick = in.readParcelable(KeymapProfileKey.class.getClassLoader()); 93 | disabled = in.readByte() != 0; 94 | dpadUdlr = in.readParcelable(Dpad.class.getClassLoader()); 95 | xRes = in.readInt(); 96 | yRes = in.readInt(); 97 | Map hashMap = in.readHashMap(getClass().getClassLoader()); 98 | if(hashMap != null) macroIdMap.putAll(hashMap); 99 | } 100 | 101 | @Override 102 | public void writeToParcel(Parcel dest, int flags) { 103 | dest.writeString(packageName); 104 | dest.writeTypedArray(dpadArray, flags); 105 | dest.writeParcelable(mouseAimConfig, flags); 106 | dest.writeTypedList(keys); 107 | dest.writeTypedList(swipeKeys); 108 | dest.writeParcelable(rightClick, flags); 109 | dest.writeByte((byte) (disabled ? 1 : 0)); 110 | dest.writeParcelable(dpadUdlr, flags); 111 | dest.writeInt(xRes); 112 | dest.writeInt(yRes); 113 | dest.writeMap(macroIdMap); 114 | } 115 | 116 | @Override 117 | public int describeContents() { 118 | return 0; 119 | } 120 | 121 | public static final Creator CREATOR = new Creator<>() { 122 | @Override 123 | public KeymapProfile createFromParcel(Parcel in) { 124 | return new KeymapProfile(in); 125 | } 126 | 127 | @Override 128 | public KeymapProfile[] newArray(int size) { 129 | return new KeymapProfile[size]; 130 | } 131 | }; 132 | 133 | protected void loadMacros(Context context) { 134 | MacroSharedPreferences macroSharedPreferences = new MacroSharedPreferences(context); 135 | macroIdMap.replaceAll((macroId, existingMacro) -> { 136 | Macro loadedMacro = macroSharedPreferences.getMacro(macroId); 137 | loadedMacro.triggerKey = existingMacro.triggerKey; 138 | return loadedMacro; 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/keymap/KeymapProfileKey.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.keymap; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | public final class KeymapProfileKey implements Parcelable { 9 | public String code; 10 | public float x; 11 | public float y; 12 | public float offset; 13 | 14 | public KeymapProfileKey(){ 15 | 16 | } 17 | 18 | protected KeymapProfileKey(Parcel in) { 19 | code = in.readString(); 20 | x = in.readFloat(); 21 | y = in.readFloat(); 22 | offset = in.readFloat(); 23 | } 24 | 25 | public static final Creator CREATOR = new Creator<>() { 26 | @Override 27 | public KeymapProfileKey createFromParcel(Parcel in) { 28 | return new KeymapProfileKey(in); 29 | } 30 | 31 | @Override 32 | public KeymapProfileKey[] newArray(int size) { 33 | return new KeymapProfileKey[size]; 34 | } 35 | }; 36 | 37 | @Override 38 | public int describeContents() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public void writeToParcel(@NonNull Parcel dest, int flags) { 44 | dest.writeString(code); 45 | dest.writeFloat(x); 46 | dest.writeFloat(y); 47 | dest.writeFloat(offset); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/macro/Macro.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.macro; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import xtr.keymapper.server.IInputInterface; 9 | import xtr.keymapper.server.InputService; 10 | 11 | public class Macro implements Parcelable { 12 | 13 | private final Event[] events; 14 | public String triggerKey; 15 | 16 | /** 17 | * @param data format: x1 y1 0; x2 y2 elapsedTimeSinceLastEventMillis; 18 | */ 19 | public Macro(String data) { 20 | String[] eventsData = data.split(";"); 21 | this.events = new Event[eventsData.length]; 22 | 23 | for (int i = 0; i < eventsData.length; i++) { 24 | String[] eventData = eventsData[i].split("\\s+"); 25 | this.events[i] = new Event(); 26 | this.events[i].x = Float.parseFloat(eventData[0]); 27 | this.events[i].y = Float.parseFloat(eventData[1]); 28 | this.events[i].elapsedTimeSinceLastEventMillis = Integer.parseInt(eventData[2]); 29 | } 30 | } 31 | 32 | // For editor 33 | public Macro() { 34 | events = null; 35 | } 36 | 37 | protected Macro(Parcel in) { 38 | events = in.createTypedArray(Event.CREATOR); 39 | triggerKey = in.readString(); 40 | } 41 | 42 | @Override 43 | public void writeToParcel(Parcel dest, int flags) { 44 | dest.writeTypedArray(events, flags); 45 | dest.writeString(triggerKey); 46 | } 47 | 48 | @Override 49 | public int describeContents() { 50 | return 0; 51 | } 52 | 53 | public static final Creator CREATOR = new Creator<>() { 54 | @Override 55 | public Macro createFromParcel(Parcel in) { 56 | return new Macro(in); 57 | } 58 | 59 | @Override 60 | public Macro[] newArray(int size) { 61 | return new Macro[size]; 62 | } 63 | }; 64 | 65 | /** 66 | * Blocking operation 67 | * Run in thread 68 | */ 69 | public void runMacro(IInputInterface mInput, int pointerId) { 70 | // Initial input event 71 | mInput.injectEvent(events[0].x, events[0].y, InputService.DOWN, pointerId); 72 | 73 | // Inject one by one with delay 74 | for (int i = 1; i < events.length - 1; i++) { 75 | try { 76 | Thread.sleep(events[i].elapsedTimeSinceLastEventMillis); 77 | mInput.injectEvent(events[i].x, events[i].y, InputService.MOVE, pointerId); 78 | } catch (InterruptedException ignored) { 79 | } 80 | } 81 | // End input event sequence 82 | mInput.injectEvent(events[events.length - 1].x, events[events.length - 1].y, InputService.UP, pointerId); 83 | 84 | } 85 | 86 | private static class Event implements Parcelable { 87 | float x, y; 88 | int elapsedTimeSinceLastEventMillis; 89 | 90 | protected Event(Parcel in) { 91 | x = in.readFloat(); 92 | y = in.readFloat(); 93 | elapsedTimeSinceLastEventMillis = in.readInt(); 94 | } 95 | 96 | public static final Creator CREATOR = new Creator<>() { 97 | @Override 98 | public Event createFromParcel(Parcel in) { 99 | return new Event(in); 100 | } 101 | 102 | @Override 103 | public Event[] newArray(int size) { 104 | return new Event[size]; 105 | } 106 | }; 107 | 108 | public Event() { 109 | } 110 | 111 | public int describeContents() { 112 | return 0; 113 | } 114 | 115 | public void writeToParcel(@NonNull Parcel dest, int flags) { 116 | dest.writeFloat(x); 117 | dest.writeFloat(y); 118 | dest.writeInt(elapsedTimeSinceLastEventMillis); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/macro/MacroIdUtils.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.macro; 2 | 3 | import java.util.ArrayList; 4 | 5 | import xtr.keymapper.keymap.KeymapProfile; 6 | 7 | public class MacroIdUtils { 8 | public static final String TAG = "MACRO"; 9 | 10 | public static void getLines(ArrayList linesToWrite, KeymapProfile profile) { 11 | profile.macroIdMap.forEach((macroId, macro) -> 12 | linesToWrite.add(TAG + " " + macroId + " " + macro.triggerKey)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/macro/MacroSharedPreferences.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.macro; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import java.util.Set; 9 | 10 | 11 | public class MacroSharedPreferences { 12 | private final SharedPreferences sharedPref; 13 | private final SharedPreferences.Editor editor; 14 | 15 | public MacroSharedPreferences(@NonNull Context context) { 16 | sharedPref = context.getSharedPreferences("macros", Context.MODE_PRIVATE); 17 | editor = sharedPref.edit(); 18 | } 19 | 20 | public void registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) { 21 | sharedPref.registerOnSharedPreferenceChangeListener(listener); 22 | } 23 | 24 | public void unregisterOnSharedPreferenceChangeListener(SharedPreferences. OnSharedPreferenceChangeListener listener) { 25 | sharedPref.unregisterOnSharedPreferenceChangeListener(listener); 26 | } 27 | 28 | /** 29 | * Renames a key of macro in key-value pair of SharedPreferences 30 | * @param oldId Id of macro to be renamed 31 | * @param newId New id 32 | */ 33 | public void renameMacro(String oldId, String newId) { 34 | if (!oldId.equals(newId)) { 35 | editor.putString(newId, sharedPref.getString(oldId, null)); 36 | editor.remove(oldId); 37 | editor.apply(); 38 | } 39 | } 40 | 41 | /** 42 | * Retrieves the value associated with a key 43 | * @param id Macro identifier string 44 | * @return Content of macro 45 | */ 46 | public Macro getMacro(String id) { 47 | String data = sharedPref.getString(id, null); 48 | if (data != null) return new Macro(data); 49 | else return new Macro(); 50 | } 51 | 52 | /** 53 | * Retrieves all keys in SharedPreferences 54 | */ 55 | public Set getMacroIds() { 56 | return sharedPref.getAll().keySet(); 57 | } 58 | 59 | /** 60 | * Removes a key-value pair from SharedPreferences. 61 | * @param id Macro identifier string 62 | */ 63 | public void removeMacro(String id) { 64 | editor.remove(id); 65 | editor.apply(); 66 | } 67 | 68 | /** 69 | * Adds a new macro with the next available identifier string like "macro0", "macro1", etc. 70 | * @param value Content of macro 71 | */ 72 | public void addMacroWithNextAvailableId(String value) { 73 | int index = 0; 74 | String newKey; 75 | 76 | // Find the next available macro key 77 | do { 78 | newKey = "macro" + index; 79 | index++; 80 | } while (sharedPref.contains(newKey)); 81 | 82 | // Store the new key-value pair 83 | editor.putString(newKey, value); 84 | editor.apply(); 85 | } 86 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/macro/MacroStatus.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.macro; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.os.SystemClock; 7 | import android.view.LayoutInflater; 8 | import android.view.ViewGroup; 9 | import android.view.accessibility.AccessibilityManager; 10 | 11 | import androidx.annotation.UiContext; 12 | 13 | import xtr.keymapper.databinding.MacroStatusLayoutBinding; 14 | 15 | 16 | /** 17 | * For displaying elapsed time 18 | */ 19 | public class MacroStatus { 20 | private final MacroStatusLayoutBinding binding; 21 | private long initThreadTimeMillis; 22 | private final AccessibilityManager mAccessibilityManager; 23 | private final Handler mHandler = new Handler(Looper.getMainLooper()); 24 | private boolean isRunning; 25 | private final TimeUpdateRunnable timeUpdateRunnable = new TimeUpdateRunnable(); 26 | private final ViewGroup container; 27 | 28 | public MacroStatus(@UiContext Context context, ViewGroup container) { 29 | this.container = container; 30 | mAccessibilityManager = context.getSystemService(AccessibilityManager.class); 31 | binding = MacroStatusLayoutBinding.inflate(LayoutInflater.from(context), container, true); 32 | } 33 | 34 | public void start() { 35 | initThreadTimeMillis = SystemClock.elapsedRealtime(); 36 | isRunning = true; 37 | timeUpdateRunnable.run(); 38 | } 39 | 40 | public void stop() { 41 | container.removeView(binding.getRoot()); 42 | binding.getRoot().invalidate(); 43 | isRunning = false; 44 | } 45 | 46 | private void updateTime() { 47 | final long totalTimeMillis = SystemClock.elapsedRealtime() - initThreadTimeMillis; 48 | 49 | final int totalTimeSeconds = (int) (totalTimeMillis / 1000); 50 | 51 | final int totalTimeMinutes = totalTimeSeconds / 60; 52 | 53 | // For stopwatch 54 | final long millis = totalTimeMillis - (totalTimeSeconds * 1000L); 55 | final int seconds = totalTimeSeconds - (totalTimeMinutes * 60); 56 | 57 | binding.textClockMinutes.setText(String.valueOf(totalTimeMinutes)); 58 | binding.textClockSeconds.setText(String.valueOf(seconds)); 59 | binding.textClockMilliSeconds.setText(String.valueOf(millis/10)); 60 | } 61 | 62 | private final class TimeUpdateRunnable implements Runnable { 63 | @Override 64 | public void run() { 65 | final long startTime = SystemClock.elapsedRealtime(); 66 | 67 | updateTime(); 68 | 69 | if (isRunning) { 70 | // The stopwatch is still running so execute this runnable again after a delay. 71 | final boolean talkBackOn = mAccessibilityManager.isTouchExplorationEnabled(); 72 | 73 | // Grant longer time between redraws when talk-back is on to let it catch up. 74 | final int period = talkBackOn ? 500 : 25; 75 | 76 | // Try to maintain a consistent period of time between redraws. 77 | final long endTime = SystemClock.elapsedRealtime(); 78 | final long delay = Math.max(0, startTime + period - endTime); 79 | 80 | mHandler.postDelayed(this, delay); 81 | } 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/macro/MacroView.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.macro; 2 | 3 | import android.content.Context; 4 | import android.graphics.BlurMaskFilter; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.view.InputDevice; 10 | import android.view.KeyEvent; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | 14 | import androidx.annotation.NonNull; 15 | 16 | /** 17 | * Custom View for visualization 18 | */ 19 | public class MacroView extends View { 20 | 21 | private final Paint paintOuter; 22 | private final Paint paintInner; 23 | private final Path path; 24 | private final StringBuilder stringBuilder = new StringBuilder(); 25 | private final OnFinishListener onFinishListener; 26 | private long lastEventTime = -1; 27 | 28 | public MacroView(Context context, OnFinishListener onFinishListener) { 29 | super(context); 30 | this.onFinishListener = onFinishListener; 31 | // Set up paint 32 | paintOuter = new Paint(); 33 | paintOuter.setAntiAlias(true); 34 | paintOuter.setColor(Color.GREEN); 35 | paintOuter.setStyle(Paint.Style.STROKE); 36 | 37 | paintOuter.setStrokeWidth(5f); 38 | paintOuter.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL)); 39 | 40 | paintInner = new Paint(paintOuter); 41 | paintInner.setStrokeWidth(2f); 42 | paintInner.setColor(Color.CYAN); 43 | paintInner.setMaskFilter(null); 44 | 45 | // Set up path 46 | path = new Path(); 47 | } 48 | 49 | @Override 50 | protected void onDraw(@NonNull Canvas canvas) { 51 | super.onDraw(canvas); 52 | canvas.drawPath(path, paintOuter); // Draw the path 53 | canvas.drawPath(path, paintInner); // Draw the path 54 | } 55 | 56 | public boolean onKey(KeyEvent event) { 57 | if (event.getSource() == InputDevice.SOURCE_KEYBOARD) { 58 | clearCanvasAndFinish(); 59 | return true; 60 | } 61 | return false; 62 | } 63 | 64 | @Override 65 | public boolean onTouchEvent(MotionEvent event) { 66 | float x = event.getX(); 67 | float y = event.getY(); 68 | 69 | long elapsedTimeSinceLastEventMillis = 0; 70 | 71 | if (lastEventTime > 0) 72 | elapsedTimeSinceLastEventMillis = event.getEventTime() - lastEventTime; 73 | 74 | lastEventTime = event.getEventTime(); 75 | 76 | switch (event.getAction()) { 77 | case MotionEvent.ACTION_DOWN: 78 | path.moveTo(x, y); // Start new path 79 | case MotionEvent.ACTION_MOVE: 80 | path.lineTo(x, y); // Draw line to current touch point 81 | break; 82 | case MotionEvent.ACTION_UP: 83 | path.moveTo(x, y); // Start new path 84 | default: 85 | return false; 86 | } 87 | logEvent(x, y, elapsedTimeSinceLastEventMillis); 88 | 89 | invalidate(); // Request redraw 90 | return true; 91 | } 92 | 93 | private void logEvent(float x, float y, long elapsedTimeSinceLastEventMillis) { 94 | stringBuilder.append(x).append(' ') 95 | .append(y).append(' ') 96 | .append(elapsedTimeSinceLastEventMillis) 97 | .append(";"); 98 | } 99 | 100 | 101 | public void clearCanvasAndFinish() { 102 | path.reset(); 103 | invalidate(); // Request redraw 104 | 105 | new MacroSharedPreferences(getContext()) 106 | .addMacroWithNextAvailableId(stringBuilder.toString()); 107 | 108 | onFinishListener.onFinishMacro(this); 109 | } 110 | 111 | 112 | public interface OnFinishListener { 113 | /** 114 | * @param macroView View with the macro visuals 115 | */ 116 | void onFinishMacro(MacroView macroView); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/mouse/MouseAimConfig.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.mouse; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import xtr.keymapper.floatingkeys.MovableFloatingActionKey; 9 | import xtr.keymapper.floatingkeys.MovableFrameLayout; 10 | 11 | public class MouseAimConfig implements Parcelable { 12 | public float xCenter, yCenter, xleftClick, yleftClick; 13 | public float width, height; 14 | public boolean limitedBounds = true; 15 | private static final int initXY = 300; 16 | public static final String TAG = "MOUSE_AIM"; 17 | public float xSensitivity = 1, ySensitivity = 1; 18 | public boolean applyNonLinearScaling = false; 19 | 20 | public MouseAimConfig() { 21 | xCenter = xleftClick = yleftClick = yCenter = initXY; 22 | } 23 | 24 | protected MouseAimConfig(Parcel in) { 25 | xCenter = in.readFloat(); 26 | yCenter = in.readFloat(); 27 | xleftClick = in.readFloat(); 28 | yleftClick = in.readFloat(); 29 | width = in.readFloat(); 30 | height = in.readFloat(); 31 | limitedBounds = in.readByte() != 0; 32 | xSensitivity = in.readFloat(); 33 | ySensitivity = in.readFloat(); 34 | applyNonLinearScaling = in.readByte() != 0; 35 | } 36 | 37 | public static final Creator CREATOR = new Creator<>() { 38 | @Override 39 | public MouseAimConfig createFromParcel(Parcel in) { 40 | return new MouseAimConfig(in); 41 | } 42 | 43 | @Override 44 | public MouseAimConfig[] newArray(int size) { 45 | return new MouseAimConfig[size]; 46 | } 47 | }; 48 | 49 | public MouseAimConfig parse(String[] data){ 50 | xCenter = Float.parseFloat(data[1]); 51 | yCenter = Float.parseFloat(data[2]); 52 | limitedBounds = Integer.parseInt(data[3]) != 0; 53 | width = Float.parseFloat(data[4]); 54 | height = Float.parseFloat(data[5]); 55 | xleftClick = Float.parseFloat(data[6]); 56 | yleftClick = Float.parseFloat(data[7]); 57 | if (data.length == 11) { 58 | xSensitivity = Float.parseFloat(data[8]); 59 | ySensitivity = Float.parseFloat(data[9]); 60 | applyNonLinearScaling = Integer.parseInt(data[10]) != 0; 61 | } 62 | return this; 63 | } 64 | 65 | public String getData() { 66 | return TAG + " " + xCenter + " " + yCenter + " " 67 | + (limitedBounds ? 1 : 0) + " " 68 | + width + " " + height + " " 69 | + xleftClick + " " + yleftClick + " " 70 | + xSensitivity + " " + ySensitivity + " " 71 | + (applyNonLinearScaling ? 1 : 0); 72 | } 73 | 74 | public void setCenterXY(MovableFrameLayout crosshair){ 75 | this.xCenter = crosshair.getX(); 76 | this.yCenter = crosshair.getY(); 77 | } 78 | 79 | public void setLeftClickXY(MovableFloatingActionKey leftClick) { 80 | this.xleftClick = leftClick.getX(); 81 | this.yleftClick = leftClick.getY(); 82 | } 83 | 84 | @Override 85 | public int describeContents() { 86 | return 0; 87 | } 88 | 89 | @Override 90 | public void writeToParcel(@NonNull Parcel dest, int flags) { 91 | dest.writeFloat(xCenter); 92 | dest.writeFloat(yCenter); 93 | dest.writeFloat(xleftClick); 94 | dest.writeFloat(yleftClick); 95 | dest.writeFloat(width); 96 | dest.writeFloat(height); 97 | dest.writeByte((byte) (limitedBounds ? 1 : 0)); 98 | dest.writeFloat(xSensitivity); 99 | dest.writeFloat(ySensitivity); 100 | dest.writeByte((byte) (applyNonLinearScaling ? 1 : 0)); 101 | } 102 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/mouse/MousePinchZoom.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.mouse; 2 | 3 | import static xtr.keymapper.InputEventCodes.BTN_MOUSE; 4 | import static xtr.keymapper.InputEventCodes.REL_X; 5 | import static xtr.keymapper.InputEventCodes.REL_Y; 6 | import static xtr.keymapper.server.InputService.DOWN; 7 | import static xtr.keymapper.server.InputService.MOVE; 8 | import static xtr.keymapper.server.InputService.UP; 9 | 10 | import xtr.keymapper.server.IInputInterface; 11 | import xtr.keymapper.touchpointer.PointerId; 12 | 13 | public class MousePinchZoom { 14 | private final IInputInterface service; 15 | private float currentX1, currentY1; 16 | private float currentX2, currentY2; 17 | private final float centerX, centerY; 18 | static final int pointerId1 = PointerId.pid1.id; 19 | static final int pointerId2 = PointerId.pid2.id; 20 | private static final int pixels = 50; 21 | 22 | public MousePinchZoom(IInputInterface service, float initX, float initY) { 23 | this.service = service; 24 | centerX = initX; 25 | centerY = initY; 26 | 27 | // Shift initial position of two pointers by 50 pixels 28 | currentX1 = initX + pixels; currentY1 = initY + pixels; 29 | currentX2 = initX - pixels; currentY2 = initY - pixels; 30 | } 31 | 32 | private void initPointers() { 33 | service.injectEvent(currentX1, currentY1, DOWN, pointerId1); 34 | service.injectEvent(currentX2, currentY2, DOWN, pointerId2); 35 | } 36 | 37 | public void releasePointers() { 38 | service.injectEvent(currentX1, currentY1, UP, pointerId1); 39 | service.injectEvent(currentX2, currentY2, UP, pointerId2); 40 | } 41 | 42 | /* 43 | * Move position of pointers away from center 44 | * To make space for performing zoom out gesture 45 | */ 46 | private void moveAwayPointers() { 47 | releasePointers(); 48 | currentX1 += 100; currentX2 -= 100; 49 | currentY1 += 100; currentY2 -= 100; 50 | initPointers(); 51 | } 52 | 53 | public boolean handleEvent(int code, int value) { 54 | switch (code) { 55 | case REL_X: 56 | currentX1 += value; 57 | currentX2 -= value; 58 | // If it passed through the center in opposite direction 59 | if (centerX > currentX1) moveAwayPointers(); 60 | 61 | service.injectEvent(currentX1, currentY1, MOVE, pointerId1); 62 | service.injectEvent(currentX2, currentY2, MOVE, pointerId2); 63 | break; 64 | case REL_Y: 65 | currentY1 += value; 66 | currentY2 -= value; 67 | if (centerY > currentY1) moveAwayPointers(); 68 | 69 | service.injectEvent(currentX1, currentY1, MOVE, pointerId1); 70 | service.injectEvent(currentX2, currentY2, MOVE, pointerId2); 71 | break; 72 | 73 | case BTN_MOUSE: 74 | service.injectEvent(currentX1, currentY1, value, pointerId1); 75 | service.injectEvent(currentX2, currentY2, value, pointerId2); 76 | if (value == UP) return false; 77 | break; 78 | } 79 | return true; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/mouse/MouseWheelZoom.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.mouse; 2 | 3 | import static xtr.keymapper.mouse.MousePinchZoom.pointerId1; 4 | import static xtr.keymapper.mouse.MousePinchZoom.pointerId2; 5 | import static xtr.keymapper.server.InputService.DOWN; 6 | import static xtr.keymapper.server.InputService.MOVE; 7 | import static xtr.keymapper.server.InputService.UP; 8 | 9 | import android.os.Handler; 10 | import android.os.HandlerThread; 11 | import android.os.RemoteException; 12 | import android.util.Log; 13 | 14 | import xtr.keymapper.server.IInputInterface; 15 | import xtr.keymapper.server.RemoteService; 16 | 17 | public class MouseWheelZoom extends HandlerThread { 18 | private final Handler mHandler; 19 | private final IInputInterface service; 20 | private static final int pixels = 50; 21 | int x1, x2; 22 | 23 | public MouseWheelZoom(IInputInterface service) { 24 | super("mouse_wheel"); 25 | start(); 26 | mHandler = new Handler(getLooper()); 27 | this.service = service; 28 | } 29 | 30 | private void initPointers(int x1, int x2, int y) throws RemoteException { 31 | service.injectEvent(x1, y, DOWN, pointerId1); 32 | service.injectEvent(x2, y, DOWN, pointerId2); 33 | } 34 | 35 | public void releasePointers(int x, int y) throws RemoteException { 36 | service.injectEvent(x, y, UP, pointerId1); 37 | service.injectEvent(x, y, UP, pointerId2); 38 | } 39 | 40 | public void onScrollEvent(int value, int x, int y) { mHandler.post(() -> { 41 | // Shift initial position of two pointers by 50 pixels 42 | x1 = x + pixels; 43 | x2 = x - pixels; 44 | try { 45 | initPointers(x1, x2, y); 46 | for (int i = 0; i < 5; i++) { 47 | x1 += pixels / 5 * value; 48 | x2 -= pixels / 5 * value; 49 | service.injectEvent(x1, y, MOVE, pointerId1); 50 | sleep(10); 51 | service.injectEvent(x2, y, MOVE, pointerId2); 52 | sleep(10); 53 | } 54 | releasePointers(x, y); 55 | } catch (RemoteException | InterruptedException e) { 56 | Log.e(RemoteService.TAG, e.getMessage(), e); 57 | } 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/profiles/ProfilesViewFragment.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.profiles; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | import androidx.fragment.app.Fragment; 12 | 13 | import xtr.keymapper.activity.MainActivity; 14 | import xtr.keymapper.databinding.FragmentProfilesViewBinding; 15 | 16 | public class ProfilesViewFragment extends Fragment { 17 | private FragmentProfilesViewBinding binding; 18 | private ProfilesViewAdapter profilesViewAdapter; 19 | 20 | @Override 21 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, 22 | Bundle savedInstanceState) { 23 | // Inflate the layout for this fragment 24 | binding = FragmentProfilesViewBinding.inflate(inflater, container, false); 25 | return binding.getRoot(); 26 | } 27 | 28 | @Override 29 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 30 | super.onViewCreated(view, savedInstanceState); 31 | 32 | Context context = view.getContext(); 33 | setAdapter(); 34 | 35 | if (getActivity() instanceof MainActivity) 36 | ((MainActivity) getActivity()).binding.addButton.setOnClickListener(v -> ProfileSelector.createNewProfile(context, p -> setAdapter())); 37 | } 38 | 39 | private void setAdapter() { 40 | if (binding != null) { 41 | profilesViewAdapter = new ProfilesViewAdapter(getContext(), this::setAdapter, 42 | profileName -> { 43 | if (getActivity() instanceof MainActivity) 44 | ((MainActivity) getActivity()).onProfileSelected(profileName); 45 | }); 46 | binding.profiles.setAdapter(profilesViewAdapter); 47 | } 48 | } 49 | 50 | @Override 51 | public void onDestroyView() { 52 | profilesViewAdapter = null; 53 | binding = null; 54 | super.onDestroyView(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/server/ActivityObserverService.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.server; 2 | 3 | import static android.content.Context.ACTIVITY_SERVICE; 4 | 5 | import android.app.ActivityManager; 6 | import android.app.IActivityManager; 7 | import android.os.Handler; 8 | import android.os.HandlerThread; 9 | import android.os.RemoteException; 10 | import android.os.ServiceManager; 11 | import android.util.Log; 12 | 13 | import java.util.List; 14 | 15 | import xtr.keymapper.ActivityObserver; 16 | 17 | public class ActivityObserverService implements Runnable { 18 | public ActivityObserver mCallback; 19 | private final IActivityManager am = IActivityManager.Stub.asInterface(ServiceManager.getService(ACTIVITY_SERVICE)); 20 | private HandlerThread mHandlerThread; 21 | private Handler mHandler; 22 | 23 | public ActivityObserverService(ActivityObserver observer) { 24 | this.mCallback = observer; 25 | 26 | mHandlerThread = new HandlerThread("activity_observer"); 27 | mHandlerThread.start(); 28 | mHandler = new Handler(mHandlerThread.getLooper()); 29 | // Send activity to client app every 5 seconds 30 | mHandler.post(this); 31 | } 32 | 33 | @Override 34 | public void run() { 35 | try { 36 | List taskInfo = am.getTasks(1); 37 | String packageName = taskInfo.get(0).topActivity.getPackageName(); 38 | if (mCallback != null) { 39 | mCallback.onForegroundActivitiesChanged(packageName); 40 | mHandler.postDelayed(this, 5000); 41 | } else { 42 | stop(); 43 | } 44 | } catch (RemoteException e) { 45 | Log.e(RemoteService.TAG, e.getMessage(), e); 46 | } 47 | } 48 | 49 | public void stop() { 50 | mCallback = null; 51 | mHandler = null; 52 | if (mHandlerThread != null) 53 | mHandlerThread.quit(); 54 | mHandlerThread = null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/server/IInputInterface.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.server; 2 | 3 | import xtr.keymapper.IRemoteServiceCallback; 4 | import xtr.keymapper.keymap.KeymapConfig; 5 | import xtr.keymapper.keymap.KeymapProfile; 6 | import xtr.keymapper.touchpointer.KeyEventHandler; 7 | import xtr.keymapper.touchpointer.MouseEventHandler; 8 | 9 | public interface IInputInterface { 10 | void injectEvent(float x, float y, int action, int pointerId); 11 | void injectHoverEvent(float x, float y, int pointerId); 12 | void injectScroll(float x, float y, int value); 13 | void pauseResumeKeymap(); 14 | KeymapConfig getKeymapConfig(); 15 | KeyEventHandler getKeyEventHandler(); 16 | MouseEventHandler getMouseEventHandler(); 17 | KeymapProfile getKeymapProfile(); 18 | IRemoteServiceCallback getCallback(); 19 | void moveCursorX(int x); 20 | void moveCursorY(int y); 21 | void hideCursor(); 22 | void showCursor(); 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/server/RemoteServiceShell.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.server; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.ContextWrapper; 7 | import android.content.pm.PackageManager; 8 | import android.os.Looper; 9 | import android.os.ServiceManager; 10 | import android.util.Log; 11 | 12 | import java.lang.reflect.Method; 13 | 14 | import xtr.keymapper.BuildConfig; 15 | import xtr.keymapper.activity.MainActivity; 16 | 17 | public class RemoteServiceShell { 18 | public static void main(String[] args) { 19 | try { 20 | System.out.println("Waiting for overlay..."); 21 | new ProcessBuilder("logcat", "-v", "color", "--pid=" + android.os.Process.myPid()).inheritIO().start(); 22 | new ProcessBuilder("setenforce", "0").inheritIO().start(); 23 | RemoteService.loadLibraries(); 24 | Looper.prepareMainLooper(); 25 | RemoteService mService = new RemoteService(getContext()); 26 | mService.startedFromShell = true; 27 | 28 | boolean launchApp = true; 29 | for (String arg: args) { 30 | switch (arg) { 31 | case "--wayland-client": 32 | mService.isWaylandClient = true; 33 | System.out.println("using wayland client"); 34 | break; 35 | case "--no-auto-launch": 36 | launchApp = false; 37 | break; 38 | default: 39 | System.out.println("Invalid argument: " + arg); 40 | break; 41 | } 42 | } 43 | 44 | ServiceManager.addService("xtmapper", mService); 45 | new ProcessBuilder("pm", "grant", BuildConfig.APPLICATION_ID, "android.permission.SYSTEM_ALERT_WINDOW").inheritIO().start(); 46 | new ProcessBuilder("settings put system alert_window_bypass_low_ram 1".split("\\s+")).inheritIO().start(); 47 | 48 | if (launchApp) new ProcessBuilder("am", "start", "-a", "android.intent.action.MAIN", "-n", 49 | new ComponentName(mService.context, MainActivity.class).flattenToString(), 50 | "--es", "data", 51 | MainActivity.SHELL_INIT).inheritIO().start(); 52 | 53 | 54 | } catch (Exception e) { 55 | Log.e(RemoteService.TAG, e.getMessage(), e); 56 | System.exit(1); 57 | } 58 | Looper.loop(); 59 | } 60 | 61 | public static Context getContextImpl(Context context) { 62 | while (context instanceof ContextWrapper) { 63 | context = ((ContextWrapper) context).getBaseContext(); 64 | } 65 | return context; 66 | } 67 | 68 | public static Context getContext() { 69 | Context systemContext = getSystemContext(); 70 | Context context = null; 71 | int flags = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY; 72 | try { 73 | context = systemContext.createPackageContext(BuildConfig.APPLICATION_ID, flags); 74 | } catch (PackageManager.NameNotFoundException e) { 75 | Log.e(RemoteService.TAG, e.getMessage(), e); 76 | } 77 | return getContextImpl(context); 78 | } 79 | 80 | @SuppressLint("PrivateApi") 81 | static Context getSystemContext() { 82 | try { 83 | Class atClazz = Class.forName("android.app.ActivityThread"); 84 | Method systemMain = atClazz.getMethod("systemMain"); 85 | Object activityThread = systemMain.invoke(null); 86 | Method getSystemContext = atClazz.getMethod("getSystemContext"); 87 | return (Context) getSystemContext.invoke(activityThread); 88 | } catch (Exception e) { 89 | throw new RuntimeException(e); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/server/RootRemoteService.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.server; 2 | 3 | import android.content.Intent; 4 | import android.os.IBinder; 5 | import android.os.Process; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.topjohnwu.superuser.ipc.RootService; 10 | 11 | class RootRemoteService extends RootService { 12 | static { 13 | // Only load the library when this class is loaded in a root process. 14 | // The classloader will load this class (and call this static block) in the non-root 15 | // process because we accessed it when constructing the Intent to send. 16 | // Add this check so we don't unnecessarily load native code that'll never be used. 17 | if (Process.myUid() == 0) 18 | RemoteService.loadLibraries(); 19 | } 20 | 21 | private RemoteService mService = null; 22 | 23 | @Override 24 | public IBinder onBind(@NonNull Intent intent) { 25 | if (mService == null) { 26 | mService = new RemoteService(this); 27 | } 28 | return mService; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/service/InputListenerService.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.service; 2 | 3 | import android.inputmethodservice.InputMethodService; 4 | import android.view.inputmethod.EditorInfo; 5 | 6 | import xtr.keymapper.server.RemoteServiceHelper; 7 | 8 | public class InputListenerService extends InputMethodService { 9 | // Input method to detect when user is inputting text 10 | @Override 11 | public void onDestroy() { 12 | super.onDestroy(); 13 | RemoteServiceHelper.resumeKeymap(getApplicationContext()); 14 | } 15 | 16 | @Override 17 | public void onFinishInputView(boolean finishingInput) { 18 | super.onFinishInputView(finishingInput); 19 | RemoteServiceHelper.resumeKeymap(getApplicationContext()); 20 | } 21 | 22 | @Override 23 | public void onStartInputView(EditorInfo info, boolean restarting) { 24 | super.onStartInputView(info, restarting); 25 | RemoteServiceHelper.pauseKeymap(getApplicationContext()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/swipekey/SwipeKey.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.swipekey; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import xtr.keymapper.keymap.KeymapProfileKey; 9 | 10 | public class SwipeKey implements Parcelable { 11 | public KeymapProfileKey key1 = new KeymapProfileKey(); 12 | public KeymapProfileKey key2 = new KeymapProfileKey(); 13 | 14 | public static final String TAG = "SWIPE_KEY"; 15 | 16 | public SwipeKey (String[] data){ 17 | key1.code = data[1]; 18 | key1.x = Float.parseFloat(data[2]); 19 | key1.y = Float.parseFloat(data[3]); 20 | 21 | key2.code = data[4]; 22 | key2.x = Float.parseFloat(data[5]); 23 | key2.y = Float.parseFloat(data[6]); 24 | } 25 | 26 | public SwipeKey(SwipeKeyView swipeKey){ 27 | key1.code = swipeKey.button1.getText(); 28 | key1.x = swipeKey.button1.getX(); 29 | key1.y = swipeKey.button1.getY(); 30 | 31 | key2.code = swipeKey.button2.getText(); 32 | key2.x = swipeKey.button2.getX(); 33 | key2.y = swipeKey.button2.getY(); 34 | } 35 | 36 | protected SwipeKey(Parcel in) { 37 | key1 = in.readParcelable(KeymapProfileKey.class.getClassLoader()); 38 | key2 = in.readParcelable(KeymapProfileKey.class.getClassLoader()); 39 | } 40 | 41 | public static final Creator CREATOR = new Creator<>() { 42 | @Override 43 | public SwipeKey createFromParcel(Parcel in) { 44 | return new SwipeKey(in); 45 | } 46 | 47 | @Override 48 | public SwipeKey[] newArray(int size) { 49 | return new SwipeKey[size]; 50 | } 51 | }; 52 | 53 | public String getData(){ 54 | return TAG + " " + 55 | key1.code + " " + 56 | key1.x + " " + 57 | key1.y + " " + 58 | key2.code + " " + 59 | key2.x + " " + 60 | key2.y; 61 | } 62 | 63 | @Override 64 | public int describeContents() { 65 | return 0; 66 | } 67 | 68 | @Override 69 | public void writeToParcel(@NonNull Parcel dest, int flags) { 70 | dest.writeParcelable(key1, flags); 71 | dest.writeParcelable(key2, flags); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/swipekey/SwipeKeyHandler.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.swipekey; 2 | 3 | import static xtr.keymapper.server.InputService.DOWN; 4 | import static xtr.keymapper.server.InputService.MOVE; 5 | 6 | import android.os.Handler; 7 | 8 | import xtr.keymapper.server.IInputInterface; 9 | import xtr.keymapper.touchpointer.KeyEventHandler.KeyEvent; 10 | import xtr.keymapper.touchpointer.PidProvider; 11 | 12 | public class SwipeKeyHandler { 13 | 14 | private final SwipeEvent swipeEvent1; 15 | private final SwipeEvent swipeEvent2; 16 | private final String keycode1; 17 | private final String keycode2; 18 | 19 | public SwipeKeyHandler(SwipeKey key){ 20 | this.keycode1 = "KEY_" + key.key1.code; 21 | this.keycode2 = "KEY_" + key.key2.code; 22 | float midpointX = (key.key1.x + key.key2.x) / 2; 23 | float midpointY = (key.key1.y + key.key2.y) / 2; 24 | swipeEvent1 = new SwipeEvent(midpointX, midpointY, key.key1.x, key.key1.y); 25 | swipeEvent2 = new SwipeEvent(midpointX, midpointY, key.key2.x, key.key2.y); 26 | } 27 | 28 | private static class SwipeEvent { 29 | float startX, startY; 30 | float stopX, stopY; 31 | 32 | public SwipeEvent(float startX, float startY, float stopX, float stopY) { 33 | this.startX = startX; 34 | this.startY = startY; 35 | this.stopX = stopX; 36 | this.stopY = stopY; 37 | } 38 | } 39 | 40 | public void handleEvent(KeyEvent event, IInputInterface service, PidProvider pidProvider, Handler handler, int swipeDelayMs) { 41 | SwipeEvent swipeEvent; 42 | if (event.code.equals(keycode1)) 43 | swipeEvent = swipeEvent1; 44 | else if (event.code.equals(keycode2)) 45 | swipeEvent = swipeEvent2; 46 | else return; 47 | int pid = pidProvider.getPid(event.code); 48 | 49 | service.injectEvent(swipeEvent.startX, swipeEvent.startY, event.action, pid); 50 | 51 | if (event.action == DOWN) handler.postDelayed(() -> { 52 | service.injectEvent(swipeEvent.stopX, swipeEvent.stopY, MOVE, pid); 53 | }, swipeDelayMs); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/swipekey/SwipeKeyOverlay.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.swipekey; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.DashPathEffect; 7 | import android.graphics.Paint; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | 11 | import androidx.annotation.UiContext; 12 | import androidx.annotation.UiThread; 13 | 14 | import com.google.android.material.button.MaterialButton; 15 | 16 | public class SwipeKeyOverlay extends View { 17 | private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 18 | private float lineStartX, lineStartY; 19 | private float lineStopX, lineStopY; 20 | 21 | public SwipeKeyOverlay(Context context) { 22 | this(context, null); 23 | } 24 | 25 | public SwipeKeyOverlay(Context context, AttributeSet attrs) { 26 | this(context, attrs, 0); 27 | } 28 | 29 | public SwipeKeyOverlay(Context context, AttributeSet attrs, int defStyle) { 30 | super(context, attrs, defStyle); 31 | mPaint.setColor(Color.CYAN); 32 | mPaint.setStrokeWidth(5f); 33 | mPaint.setStyle(Paint.Style.STROKE); 34 | mPaint.setPathEffect(new DashPathEffect(new float[]{5, 10, 15, 20}, 0)); 35 | } 36 | 37 | @Override 38 | protected void onDraw(Canvas canvas) { 39 | super.onDraw(canvas); 40 | canvas.drawLine(lineStartX, lineStartY, lineStopX, lineStopY, mPaint); 41 | } 42 | 43 | @UiThread 44 | public void setLineXyFrom(View view1, View view2) { 45 | this.lineStartX = view1.getX() + view1.getPivotX(); 46 | this.lineStartY = view1.getY() + view1.getPivotY(); 47 | 48 | this.lineStopX = view2.getX() + view2.getPivotX(); 49 | this.lineStopY = view2.getY() + view2.getPivotY(); 50 | invalidate(); 51 | } 52 | 53 | private float getLineMidPointX(){ 54 | return (lineStartX + lineStopX) / 2; 55 | } 56 | private float getLineMidPointY(){ 57 | return (lineStartY + lineStopY) / 2; 58 | } 59 | 60 | public void centerViewOnLine(View view) { 61 | view.setX(getLineMidPointX() - view.getPivotX()); 62 | view.setY(getLineMidPointY() - view.getPivotY()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/swipekey/SwipeKeyView.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.swipekey; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import com.google.android.material.button.MaterialButton; 9 | 10 | import xtr.keymapper.databinding.SwipeKeyBinding; 11 | import xtr.keymapper.floatingkeys.MovableFloatingActionKey; 12 | import xtr.keymapper.keymap.KeymapConfig; 13 | 14 | public class SwipeKeyView { 15 | 16 | public final MovableFloatingActionKey button1, button2; 17 | public final MaterialButton closeButton; 18 | public final SwipeKeyOverlay overlay; 19 | 20 | public interface OnViewRemoved { 21 | void onViewRemoved(SwipeKeyView swipeKeyView); 22 | } 23 | 24 | public SwipeKeyView(ViewGroup mainView, SwipeKey swipeKey, OnViewRemoved callback, View.OnClickListener onClickListener) { 25 | this(mainView, callback, onClickListener); 26 | button1.setText(swipeKey.key1.code); 27 | button1.frameView.animate() 28 | .x(swipeKey.key1.x) 29 | .y(swipeKey.key1.y) 30 | .setDuration(500) 31 | .withEndAction(() -> onXyChange(0, 0)) 32 | .start(); 33 | 34 | button2.setText(swipeKey.key2.code); 35 | button2.frameView.animate() 36 | .x(swipeKey.key2.x) 37 | .y(swipeKey.key2.y) 38 | .setDuration(500) 39 | .withEndAction(() -> onXyChange(0, 0)) 40 | .start(); 41 | 42 | KeymapConfig keymapConfig = new KeymapConfig(mainView.getContext()); 43 | 44 | button1.frameView.setScaleX(keymapConfig.floatingKeysSize); 45 | button1.frameView.setScaleY(keymapConfig.floatingKeysSize); 46 | 47 | button2.frameView.setScaleX(keymapConfig.floatingKeysSize); 48 | button2.frameView.setScaleY(keymapConfig.floatingKeysSize); 49 | } 50 | 51 | public SwipeKeyView(ViewGroup rootView, OnViewRemoved callback, View.OnClickListener onClickListener){ 52 | Context context = rootView.getContext(); 53 | button1 = new MovableFloatingActionKey(context, true, rootView); 54 | button2 = new MovableFloatingActionKey(context, true, rootView); 55 | 56 | closeButton = SwipeKeyBinding.inflate(LayoutInflater.from(context), rootView, true).getRoot(); 57 | 58 | overlay = new SwipeKeyOverlay(context); 59 | rootView.addView(overlay); 60 | 61 | closeButton.setOnClickListener(v -> { 62 | rootView.removeView(button1.frameView); 63 | rootView.removeView(button2.frameView); 64 | rootView.removeView(closeButton); 65 | rootView.removeView(overlay); 66 | callback.onViewRemoved(this); 67 | }); 68 | 69 | button1.setX(rootView.getPivotX() - 100); 70 | button1.setY(rootView.getPivotY() - 100); 71 | 72 | button2.setX(rootView.getPivotX() + 100); 73 | button2.setY(rootView.getPivotY() + 100); 74 | 75 | button1.setXyChangeListener(this::onXyChange); 76 | button2.setXyChangeListener(this::onXyChange); 77 | 78 | button1.setOnClickListener(onClickListener); 79 | button2.setOnClickListener(onClickListener); 80 | } 81 | 82 | private void onXyChange(float x, float y) { 83 | overlay.setLineXyFrom(button1.frameView, button2.frameView); 84 | overlay.centerViewOnLine(closeButton); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/touchpointer/PidProvider.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.touchpointer; 2 | 3 | import androidx.collection.SimpleArrayMap; 4 | 5 | public final class PidProvider { 6 | private final SimpleArrayMap pidList = new SimpleArrayMap<>(); 7 | // returns pointerIds on demand 8 | public Integer getPid(String keycode) { 9 | if (!pidList.containsKey(keycode)) 10 | pidList.put(keycode, pidList.size()); 11 | return pidList.get(keycode); 12 | } 13 | 14 | public void releasePidFor(String keycode) { 15 | pidList.remove(keycode); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/xtr/keymapper/touchpointer/PointerId.java: -------------------------------------------------------------------------------- 1 | package xtr.keymapper.touchpointer; 2 | 3 | public enum PointerId { 4 | // pointer id 0-35 reserved for keyboard events 5 | 6 | pid1(36), // pointer id 36, 37 and 38 reserved for mouse events 7 | pid2(37), 8 | pid3(38), 9 | dpadpid1(39); 10 | 11 | PointerId(int i) { 12 | id = i; 13 | } 14 | 15 | public final int id; 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_360_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/crosshair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/drawable/crosshair.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/dpad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/drawable/dpad.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_add_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_done_36.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_help_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_mouse_36.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_speed_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_touch_app_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_zoom_out_map_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_create_white_36dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pointer_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_start_button.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/drawable/key.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/newkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/drawable/newkey.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/pointer_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/drawable/pointer_arrow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/resize_handle.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 32 | 33 | 47 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/app_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 13 | 14 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 25 | 26 | 33 | 34 | 35 | 41 | 42 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main_controls.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 29 | 30 | 40 | 41 | 51 | 52 | 53 | 54 | 63 | 64 | 74 | 75 | 85 | 86 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 16 | 17 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/crosshair.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 24 | 30 | 31 | 43 | 44 | 56 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/cursor.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dpad.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 29 | 30 | 43 | 44 | 57 | 58 | 71 | 72 | 83 | 84 | 93 | 94 | 95 | 101 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dpad_arrows.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 27 | 28 | 39 | 40 | 51 | 52 | 63 | 64 | 75 | 76 | 85 | 86 | 87 | 93 | -------------------------------------------------------------------------------- /app/src/main/res/layout/floating_key.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 26 | 27 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_profiles_apps.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_profiles_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/keymap_editor_item.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/loading.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/macro_dialog_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/macro_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 24 | 25 | 29 | 30 | 37 | 38 | 45 | 46 | 47 | 48 | 49 | 50 | 53 | 57 | 62 | 63 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/res/layout/macro_status_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 15 | 16 | 24 | 32 | 33 | 40 | 41 | 49 | 50 | 58 | 59 | 60 | 61 | 67 | -------------------------------------------------------------------------------- /app/src/main/res/layout/mouse_aim_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 16 | 17 | 23 | 29 | 30 | 31 | 32 | 36 | 42 | 46 | 52 | 53 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/layout/profile_row_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | 18 | 19 | 23 | 24 | 30 | 31 | 32 | 37 | 38 | 39 | 47 | 48 | 52 | 53 | 59 | 60 | 61 | 62 | 67 | 68 | 69 | 75 | 76 | 83 | 84 | 85 | 86 | 91 | 92 | 99 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /app/src/main/res/layout/profile_row_item2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 19 | 20 | 21 | 29 | 30 | 35 | 36 | 37 | 44 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/resizable.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 20 | 27 | 28 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 22 | 23 | 29 | 30 | 31 | 38 | 39 | 43 | 44 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/swipe_key.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/text_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_navigation_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/menu/keymap_editor_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 15 | 19 | 23 | 27 | 28 | 32 | 33 | 37 | 38 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/combined 5 | @string/overlay 6 | @string/system 7 | 8 | 9 | @string/direct 10 | @string/relative 11 | @string/disabled 12 | 13 | 14 | @string/toggle 15 | @string/hold 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF6200EE 4 | #FF3700B3 5 | #FF03DAC5 6 | #FF000000 7 | #FFFFFFFF 8 | #303030 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/method.xml: -------------------------------------------------------------------------------- 1 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '8.9.1' apply false 4 | id 'com.android.library' version '8.9.1' apply false 5 | id 'org.jetbrains.kotlin.android' version '2.0.21' apply false 6 | id 'org.jetbrains.kotlin.plugin.compose' version '2.0.21' apply false 7 | } 8 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Xtr126/XtMapper/6020d5ed19cfcdf80517fad1184ca31f40b636f8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jan 11 21:11:13 IST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /keystore.properties: -------------------------------------------------------------------------------- 1 | storePassword=android 2 | keyPassword=android 3 | keyAlias=androiddebugkey 4 | storeFile=../../keystore.jks 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url 'https://jitpack.io' } 14 | } 15 | } 16 | rootProject.name = "XtMapper" 17 | include ':app' 18 | include ':app:hidden-api' --------------------------------------------------------------------------------