diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 71fafbc9..8e4435dd 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,7 +6,7 @@
version: 2
updates:
- package-ecosystem: "gradle" # See documentation for possible values
- directory: "/" # Location of package manifests
+ directory: "/app" # Location of package manifests
schedule:
interval: "weekly"
registries:
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
deleted file mode 100644
index 1a32a8e4..00000000
--- a/.github/workflows/android.yml
+++ /dev/null
@@ -1,34 +0,0 @@
-name: Build APK
-
-on:
- pull_request:
- push:
- paths-ignore:
- - 'README.md'
- workflow_dispatch:
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
-
- - name: Set up JDK 17
- uses: actions/setup-java@v3
- with:
- java-version: '17'
- distribution: 'temurin'
- cache: gradle
-
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
-
- - name: Build with Gradle
- run: ./gradlew assembleDebug
-
- - uses: actions/upload-artifact@v3
- with:
- name: Debug APK
- path: app/build/outputs/apk/debug/app-debug.apk
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
new file mode 100644
index 00000000..63550058
--- /dev/null
+++ b/.github/workflows/build-docs.yml
@@ -0,0 +1,19 @@
+name: Build Documentation
+
+on:
+ workflow_call:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout your repository using git
+ uses: actions/checkout@v4
+ with:
+ repository: Xtr126/XtMapper-docs
+
+ - name: Install, build, and upload your site
+ uses: withastro/action@v2
+ with:
+ node-version: 20
+ package-manager: pnpm@latest
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..da315cb1
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,64 @@
+name: Build and Release APK
+
+on:
+ workflow_dispatch:
+ push:
+ paths:
+ - '**.yml'
+
+jobs:
+ build-docs:
+ uses: ./.github/workflows/build-docs.yml
+
+ build:
+ needs:
+ - build-docs
+
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: gradle
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+
+ - name: Write key
+ run: |
+ if [ ! -z "${{ secrets.KEYSTORE }}" ]; then
+ {
+ echo storePassword='${{ secrets.KEYSTORE_PASSWORD }}'
+ echo keyPassword='${{ secrets.KEY_PASSWORD }}'
+ echo keyAlias='${{ secrets.KEY_ALIAS }}'
+ echo storeFile='../keystore.jks'
+ } > keystore.properties
+ echo '${{ secrets.KEYSTORE }}' | base64 -d > keystore.jks
+ fi
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+
+ - name: Move artifact to assets
+ run: |
+ cd ./github-pages
+ tar xvf artifact.tar && rm artifact.tar
+ cd ../
+ mkdir ./app/src/main/assets
+ mv ./github-pages ./app/src/main/assets/XtMapper-docs
+
+ - name: Build with Gradle
+ run: ./gradlew assembleRelease
+
+ - name: Release
+ uses: "marvinpinto/action-automatic-releases@latest"
+ with:
+ repo_token: "${{ secrets.GITHUB_TOKEN }}"
+ prerelease: false
+ files: |
+ app/build/outputs/apk/release/*.apk
+
diff --git a/.gitignore b/.gitignore
index 30eafb90..25094a15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@
/build
/app/build
output*.json
+keystore.properties
+keystore.jks
diff --git a/README.md b/README.md
index 718e8a89..83081a09 100644
--- a/README.md
+++ b/README.md
@@ -20,44 +20,8 @@
-## About
-
-Introducing XtMapper, the ultimate keymapper for Android on PC gaming designed to revolutionize your gaming experience with unparalleled features that set it apart from the rest. It offers a comprehensive set of features unmatched by other keymapper available.
-
-Unlike other keymappers that often inject ads or having users to pay, XtMapper is committed to providing a clean and user-friendly experience. With its extensive feature set, user-friendly interface, and commitment to it's userbase, XtMapper stands as the keymapper of choice for Android-x86 gaming enthusiasts.
-
-It is free and open-source software boasting a comprehensive array of functionalities that cater to Android-x86 users diverse needs for mouse/keyboard/touchpad input, making it a standout choice among keymapping applications that usually only target mobile phone hardware.
-
-There is literally just no match for it among other Android keymappers in the context of Android-x86.
-As you might have already experienced, most other keymappers for Android are usually Adware / Paid closed source software that don't even support Android-x86 natively.
-
-XtMapper is included in Bliss OS and featured on [BlissLabs](https://blisslabs.org).
-
-## Key Features
-
-Multi-touch emulation from keyboard/mouse and enhancing control precision in various gaming scenarios: Providing a seamless and immersive touch experience for games that demand intricate touch inputs.
-
-Mouse-to-touch emulation: Utilize your mouse as a touch pointer, enabling control in games that don't support direct mouse input.
-
-Keyboard-to-touch emulation: Configure keyboard keys to trigger touch events, allowing you to play games that don't haven ative support for keyboard.
-
-Aiming assistance: Enhance your aiming accuracy in FPS games with mouse-based aiming assistance while mapping mouse buttons and movement to various in-game functions.
-
-Gesture emulation: Perform pinch-to-zoom gestures using keyboard shortcuts and mouse movements.
-
-Smooth scrolling: Experience smooth and pixel-perfect scrolling emulation with your mouse wheel. Provides a more smooth and responsive scrolling experience than the android system defaults.
-
-Swipe emulation: Execute swipes conveniently using designated keyboard keys, replicating touch-based gestures. Adds more versatility to your gaming controls.
-
-Advanced touchpad support: Choose between direct touch or relative mode for touchpad input, catering to a variety of user preferences.
-
-Low latency and high performance: Xtmapper provides low-latency, high-performance keymapping that ensures your inputs are registered with precision and speed. Enjoy a responsive and lag-free gaming experience.
-
-Automatic profile switching: Automatically switch between keymapper profiles based on the active app, streamlining your gaming setup for each specific game.
-
-Open-source development: Contribute to the ongoing development of XtMapper and benefit from its open-source nature.
-
-XtMapper ensures a stable and reliable experience across most Android-x86 systems as long as it is Android 9 or newer. Older Android versions are not supported.
+## About and features
+https://xtr126.github.io/XtMapper-docs/guides/about
## Development
@@ -73,9 +37,8 @@ You can ask about XtMapper on Bliss OS in
BlissLabs discord server: https://discord.com/invite/F9n5gbdNy2
Telegram: https://t.me/blissx86
-
## Waydroid support
-Due to how XtMapper works by reading input events directly from the kernel, there are certain difficulties in implementing support for android containers.
+Due to how XtMapper works by reading input events directly from the kernel, there are certain limitations in implementing support for Android containers.
An experimental solution was developed: https://github.com/Xtr126/wayland-getevent
It is mostly a "hack" that we have to rely on due to how wayland/waydroid works.
https://youtube.com/watch?v=yvZD2E7kgG0
@@ -84,11 +47,12 @@ https://youtube.com/watch?v=yvZD2E7kgG0
Open source libraries used:
- [Material Design Components](https://github.com/material-components/material-components-android) used for the app user interface.
- [FloatingActionButtonSpeedDial](https://github.com/leinardi/FloatingActionButtonSpeedDial)
+- [libsu](https://github.com/topjohnwu/libsu)
[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.
## Copyright and License
-This project is licensed under the GPL v3.
+The source code is licensed under the GPL v3.
Do not publish unofficial APKs to the play store.
```
XtMapper
diff --git a/app/build.gradle b/app/build.gradle
index 841f7840..b66d3d8a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,6 +2,16 @@ plugins {
id 'com.android.application'
}
+// Create a variable called keystorePropertiesFile, and initialize it to your
+// keystore.properties file, in the rootProject folder.
+def keystorePropertiesFile = rootProject.file("keystore.properties")
+
+// Initialize a new Properties() object called keystoreProperties.
+def keystoreProperties = new Properties()
+
+// Load your keystore.properties file into the keystoreProperties object.
+keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+
android {
namespace 'xtr.keymapper'
compileSdk 34
@@ -9,30 +19,45 @@ android {
defaultConfig {
applicationId "xtr.keymapper"
minSdk 28
- targetSdk 33
- versionCode 10
- versionName '2.1.0'
+ targetSdk 34
+ versionCode 11
+ versionName '2.1.1'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- ndk {
- abiFilter 'x86_64'
- }
+
aaptOptions {
ignoreAssetsPattern ''
}
}
- externalNativeBuild {
- ndkBuild {
- path 'Android.mk'
+
+ signingConfigs {
+ release {
+ keyAlias keystoreProperties['keyAlias']
+ keyPassword keystoreProperties['keyPassword']
+ storeFile file(keystoreProperties['storeFile'])
+ storePassword keystoreProperties['storePassword']
}
}
-
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.release
+ }
+ }
+
+ externalNativeBuild {
+ ndkBuild {
+ path 'Android.mk'
+ }
+ }
+
+ applicationVariants.configureEach {
+ it.outputs.every {
+ it.outputFileName = "XtMapper-${it.name}-v${defaultConfig.versionName}.apk"
}
}
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
@@ -43,19 +68,42 @@ android {
}
packagingOptions.jniLibs.useLegacyPackaging true
+ splits {
+
+ // Configures multiple APKs based on ABI.
+ abi {
+ // Enables building multiple APKs per ABI.
+ enable true
+
+ // Resets the list of ABIs for Gradle to create APKs for to none.
+ reset()
+
+ // Specifies a list of ABIs for Gradle to create APKs for.
+ include "x86_64", "x86", "armeabi-v7a", "arm64-v8a"
+
+ // Specifies that we don't want to also generate a universal APK that includes all ABIs.
+ universalApk false
+ }
+ }
+
ndkVersion '26.1.10909125'
+
+ dependenciesInfo {
+ // Disables dependency metadata when building APKs.
+ includeInApk = false
+ // Disables dependency metadata when building Android App Bundles.
+ includeInBundle = false
+ }
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
- implementation 'com.google.android.material:material:1.10.0'
+ implementation 'com.google.android.material:material:1.11.0'
implementation "com.leinardi.android:speed-dial:3.3.0"
- implementation 'androidx.webkit:webkit:1.8.0'
- // The core module that provides APIs to a shell
- implementation "com.github.topjohnwu.libsu:core:5.2.1"
- // Optional: APIs for creating root services. Depends on ":core"
- implementation "com.github.topjohnwu.libsu:service:5.2.1"
+ implementation 'androidx.webkit:webkit:1.10.0'
+ implementation "com.github.topjohnwu.libsu:core:5.2.2"
+ implementation "com.github.topjohnwu.libsu:service:5.2.2"
compileOnly project(path: ':app:hidden-api')
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eef1d669..9ded9406 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,9 +4,11 @@
+
+
@@ -49,7 +51,8 @@
+ android:enabled="true"
+ android:foregroundServiceType="specialUse"/>
= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ startForeground(2, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
+ } else {
+ startForeground(2, notification);
+ }
if (cursorView == null) showCursor();
- KeymapConfig keymapConfig = new KeymapConfig(this);
-
- if(keymapConfig.disableAutoProfiling) {
- // Select app
- ProfileSelector.select(this, profile -> {
- this.selectedProfile = profile;
- KeymapProfile keymapProfile = new KeymapProfiles(this).getProfile(profile);
- connectRemoteService(keymapProfile);
- Intent launchIntent = getPackageManager().getLaunchIntentForPackage(keymapProfile.packageName);
- if (launchIntent != null) startActivity(launchIntent);
- });
- } else {
- // Launch default profile for current app
- ProfileSelector.select(this, profile -> {
- this.selectedProfile = profile;
- KeymapProfile keymapProfile = new KeymapProfiles(this).getProfile(profile);
- connectRemoteService(keymapProfile);
- }, getPackageName());
- }
+ // Launch default profile
+ this.selectedProfile = "Default";
+ KeymapProfile keymapProfile = new KeymapProfiles(this).getProfile(selectedProfile);
+ connectRemoteService(keymapProfile);
+
return super.onStartCommand(i, flags, startId);
}
+ public void launchApp() {
+ ProfileSelector.select(this, profile -> {
+ this.selectedProfile = profile;
+ KeymapProfile keymapProfile = new KeymapProfiles(this).getProfile(profile);
+ connectRemoteService(keymapProfile);
+ Intent launchIntent = getPackageManager().getLaunchIntentForPackage(keymapProfile.packageName);
+ if (launchIntent != null) startActivity(launchIntent);
+ });
+ }
+
private void connectRemoteService(KeymapProfile profile) {
if (activityCallback != null) activityCallback.updateCmdView1("\n connecting to server..");
- mService = RemoteService.getInstance();
- if (mService == null) {
- if (activityCallback != null) {
- activityCallback.updateCmdView1("\n connection failed\n Please retry activation \n");
- activityCallback.stopPointer();
- } else {
- onDestroy();
- stopSelf();
- }
- return;
- }
- KeymapConfig keymapConfig = new KeymapConfig(this);
- Display display = mWindowManager.getDefaultDisplay();
- Point size = new Point();
- display.getRealSize(size); // TODO: getRealSize() deprecated in API level 31
- try {
- if (keymapConfig.disableAutoProfiling) {
- mService.startServer(profile, keymapConfig, mCallback, size.x, size.y);
- } else {
- if (!activityRemoteCallback) {
- mService.registerActivityObserver(mActivityObserverCallback);
- activityRemoteCallback = true;
+ RemoteServiceHelper.getInstance(this, service -> {
+ mService = service;
+ if (mService == null) {
+ if (activityCallback != null) {
+ activityCallback.updateCmdView1("\n connection failed\n Please retry activation \n");
+ activityCallback.stopPointer();
} else {
- if (!profile.disabled)
- mService.startServer(profile, keymapConfig, mCallback, size.x, size.y);
+ onDestroy();
+ stopSelf();
}
+ return;
}
- } catch (Exception e) {
- if(activityCallback != null) {
- activityCallback.updateCmdView1(e.toString());
- activityCallback.stopPointer();
- } else {
- onDestroy();
- stopSelf();
+ KeymapConfig keymapConfig = new KeymapConfig(this);
+ Display display = mWindowManager.getDefaultDisplay();
+ Point size = new Point();
+ display.getRealSize(size); // TODO: getRealSize() deprecated in API level 31
+ try {
+ if (keymapConfig.disableAutoProfiling) {
+ mService.startServer(profile, keymapConfig, mCallback, size.x, size.y);
+ } else {
+ if (!activityRemoteCallback) {
+ mService.registerActivityObserver(mActivityObserverCallback);
+ activityRemoteCallback = true;
+ } else {
+ if (!profile.disabled)
+ mService.startServer(profile, keymapConfig, mCallback, size.x, size.y);
+ }
+ }
+ } catch (Exception e) {
+ if(activityCallback != null) {
+ activityCallback.updateCmdView1(e.toString());
+ activityCallback.stopPointer();
+ } else {
+ onDestroy();
+ stopSelf();
+ }
+ Log.e("startServer", e.toString(), e);
}
- Log.e("startServer", e.toString(), e);
- }
+
+ });
}
@Override
public void onDestroy() {
if (cursorView != null) {
- mWindowManager.removeView(cursorView);
+ if (cursorView.isAttachedToWindow())
+ mWindowManager.removeView(cursorView);
cursorView.invalidate();
}
if (mService != null) try {
diff --git a/app/src/main/java/xtr/keymapper/Utils.java b/app/src/main/java/xtr/keymapper/Utils.java
index 5867485a..e7ac07cb 100644
--- a/app/src/main/java/xtr/keymapper/Utils.java
+++ b/app/src/main/java/xtr/keymapper/Utils.java
@@ -2,7 +2,6 @@
import java.io.BufferedReader;
import java.io.DataOutputStream;
-import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -27,12 +26,4 @@ public static BufferedReader geteventStream(String nativeLibraryDir) throws IOEx
return new BufferedReader(new InputStreamReader(sh.getInputStream()));
}
- public static Process getRootAccess() throws IOException {
- String[] paths = {"/sbin/su", "/system/sbin/su", "/system/bin/su", "/system/xbin/su", "/su/bin/su", "/magisk/.core/bin/su"};
- for (String path : paths) {
- if (new File(path).canExecute())
- return Runtime.getRuntime().exec(path);
- }
- return Runtime.getRuntime().exec("echo root access not found!");
- }
}
diff --git a/app/src/main/java/xtr/keymapper/activity/MainActivity.java b/app/src/main/java/xtr/keymapper/activity/MainActivity.java
index bc15c85d..9dd3daa8 100644
--- a/app/src/main/java/xtr/keymapper/activity/MainActivity.java
+++ b/app/src/main/java/xtr/keymapper/activity/MainActivity.java
@@ -17,6 +17,7 @@
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.topjohnwu.superuser.Shell;
import xtr.keymapper.R;
import xtr.keymapper.Server;
@@ -24,17 +25,25 @@
import xtr.keymapper.databinding.ActivityMainBinding;
import xtr.keymapper.editor.EditorActivity;
import xtr.keymapper.fragment.SettingsFragment;
-import xtr.keymapper.server.RemoteService;
+import xtr.keymapper.server.RemoteServiceHelper;
public class MainActivity extends AppCompatActivity {
public TouchPointer pointerOverlay;
- private final Server server = new Server();
public ActivityMainBinding binding;
private Intent intent;
private ColorStateList defaultTint;
private boolean stopped = true;
+ static {
+ // Set settings before the main shell can be created
+ Shell.enableVerboseLogging = false;
+ Shell.setDefaultBuilder(Shell.Builder.create()
+ .setFlags(Shell.FLAG_REDIRECT_STDERR)
+ .setTimeout(10)
+ );
+ }
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -42,30 +51,34 @@ protected void onCreate(Bundle savedInstanceState) {
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
- server.mCallback = this.mCallback;
- server.setupServer(this);
-
- setupButtons();
-
Context context = getApplicationContext();
intent = new Intent(context, TouchPointer.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);
+
+ Shell.getShell(shell -> {
+ Boolean rootAccess = Shell.isAppGrantedRoot();
+ if (rootAccess == null || !rootAccess) {
+ Server.setupServer(this, mCallback);
+ alertRootAccessNotFound();
+ }
+ setupButtons();
+ });
}
private void setupButtons() {
- defaultTint = binding.controls.startServer.getBackgroundTintList();
- binding.controls.startServer.setOnClickListener(v -> startServer(true));
- binding.controls.startInTerminal.setOnClickListener(v -> startServer(false));
+ defaultTint = binding.controls.launchApp.getBackgroundTintList();
+ binding.controls.launchApp.setOnClickListener(v -> launchApp());
binding.controls.startPointer.setOnClickListener(v -> startPointer());
binding.controls.startEditor.setOnClickListener(v -> startEditor());
binding.controls.configButton.setOnClickListener
(v -> new SettingsFragment(this).show(getSupportFragmentManager(), "dialog"));
binding.controls.aboutButton.setOnClickListener
(v -> startActivity(new Intent(this, InfoActivity.class)));
- if (RemoteService.getInstance() != null) {
- mCallback.alertActivation();
- binding.controls.startServer.setEnabled(false);
- }
+ }
+
+ private void launchApp() {
+ if (!stopped) pointerOverlay.launchApp();
+ else startPointer();
}
public void startPointer(){
@@ -77,6 +90,8 @@ public void startPointer(){
setButtonState(false);
requestNotificationPermission();
}
+ if (!RemoteServiceHelper.isRootService)
+ alertRootAccessAndExit();
}
private void setButtonState(boolean start) {
@@ -93,10 +108,10 @@ private void setButtonState(boolean start) {
}
public void stopPointer(){
- stopped = false;
unbindTouchPointer();
stopService(intent);
setButtonState(true);
+ stopped = true;
}
private void unbindTouchPointer() {
@@ -113,14 +128,6 @@ private void startEditor(){
startActivity(new Intent(this, EditorActivity.class));
}
- private void startServer(boolean autorun){
- checkOverlayPermission();
- if(Settings.canDrawOverlays(this)) {
- if (autorun) new Thread(server::startServer).start();
- else mCallback.updateCmdView1("run in adb shell:\n sh " + server.script.getPath());
- }
- }
-
private void checkOverlayPermission(){
if (!Settings.canDrawOverlays(this)) {
// send user to the device settings
@@ -136,6 +143,30 @@ private void requestNotificationPermission(){
}
}
+ public void alertRootAccessNotFound() {
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
+ builder.setTitle(R.string.root_not_found_title)
+ .setMessage(R.string.root_not_found_message)
+ .setPositiveButton(R.string.ok, (dialog, which) -> {
+ Intent launchIntent = MainActivity.this.getPackageManager().getLaunchIntentForPackage("me.weishu.kernelsu");
+ if (launchIntent != null) startActivity(launchIntent);
+ });
+ runOnUiThread(() -> builder.create().show());
+ }
+
+ public void alertRootAccessAndExit() {
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(MainActivity.this);
+ builder.setTitle(R.string.root_no_privileges_title)
+ .setMessage(R.string.root_no_privileges_message)
+ .setPositiveButton(R.string.ok, (dialog, which) -> {
+ finishAffinity();
+ System.exit(0);
+ })
+ .setNegativeButton(R.string.cancel, (dialog, which) -> {});
+ runOnUiThread(() -> builder.create().show());
+ }
+
+
@Override
protected void onDestroy() {
super.onDestroy();
@@ -145,8 +176,6 @@ protected void onDestroy() {
public interface Callback {
void updateCmdView1(String line);
void stopPointer();
- void alertRootAccessNotFound();
- void alertActivation();
}
private final Callback mCallback = new Callback() {
@@ -166,26 +195,6 @@ public void updateCmdView1(String line) {
public void stopPointer() {
MainActivity.this.stopPointer();
}
-
- @Override
- public void alertRootAccessNotFound() {
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(MainActivity.this);
- builder.setTitle(R.string.root_not_found_title)
- .setMessage(R.string.root_not_found_message)
- .setPositiveButton("OK", (dialog, which) -> {
- Intent launchIntent = MainActivity.this.getPackageManager().getLaunchIntentForPackage("me.weishu.kernelsu");
- if (launchIntent != null) startActivity(launchIntent);
- });
- runOnUiThread(() -> builder.create().show());
- }
-
- @Override
- public void alertActivation() {
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(MainActivity.this);
- builder.setTitle(R.string.activated)
- .setMessage(R.string.activation_successful);
- runOnUiThread(() -> builder.create().show());
- }
};
/** Defines callbacks for service binding, passed to bindService() */
diff --git a/app/src/main/java/xtr/keymapper/editor/EditorActivity.java b/app/src/main/java/xtr/keymapper/editor/EditorActivity.java
index ba42412b..e2198440 100644
--- a/app/src/main/java/xtr/keymapper/editor/EditorActivity.java
+++ b/app/src/main/java/xtr/keymapper/editor/EditorActivity.java
@@ -22,7 +22,6 @@
import xtr.keymapper.R;
import xtr.keymapper.TouchPointer;
import xtr.keymapper.profiles.ProfileSelector;
-import xtr.keymapper.server.RemoteService;
import xtr.keymapper.server.RemoteServiceHelper;
public class EditorActivity extends Activity implements EditorUI.OnHideListener {
@@ -35,8 +34,8 @@ public void onCreate(Bundle savedInstanceState) {
WindowInsetsControllerCompat windowInsetsController = WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
windowInsetsController.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());
- mService = RemoteService.getInstance();
- RemoteServiceHelper.pauseKeymap();
+// RemoteServiceHelper.pauseKeymap();
+ RemoteServiceHelper.getInstance(this, service -> mService = service);
if (editor != null) editor.hideView();
bindService(new Intent(this, TouchPointer.class), connection, Context.BIND_AUTO_CREATE);
@@ -101,7 +100,7 @@ protected void onDestroy() {
} catch (RemoteException ignored) {
}
editor = null;
- RemoteServiceHelper.resumeKeymap();
+// RemoteServiceHelper.resumeKeymap();
}
@Override
diff --git a/app/src/main/java/xtr/keymapper/editor/EditorUI.java b/app/src/main/java/xtr/keymapper/editor/EditorUI.java
index 1dfadcc0..a42da634 100644
--- a/app/src/main/java/xtr/keymapper/editor/EditorUI.java
+++ b/app/src/main/java/xtr/keymapper/editor/EditorUI.java
@@ -3,10 +3,8 @@
import static xtr.keymapper.keymap.KeymapProfiles.MOUSE_RIGHT;
import android.annotation.SuppressLint;
-import android.app.AlertDialog;
import android.content.Context;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -15,6 +13,10 @@
import android.view.ViewGroup;
import android.view.WindowManager;
+import androidx.appcompat.app.AlertDialog;
+
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
import java.util.ArrayList;
import java.util.List;
@@ -111,11 +113,6 @@ public void onKeyEvent(String event) {
});
}
- @Override
- public IBinder asBinder() {
- return this;
- }
-
public interface OnHideListener {
void onHideView();
@@ -181,7 +178,7 @@ private void saveKeymap() {
profiles.saveProfile(profileName, linesToWrite, profile.packageName, !profile.disabled);
// Reload keymap if service running
- RemoteServiceHelper.reloadKeymap();
+ RemoteServiceHelper.reloadKeymap(context);
}
public void setupButtons() {
@@ -200,7 +197,7 @@ else if (id == R.id.save) {
}
else if (id == R.id.dpad) {
final CharSequence[] items = { "Arrow Keys", "WASD Keys"};
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle("Select Dpad").setItems(items, (dialog, i) -> {
if (i == 0) addArrowKeysDpad(defaultX, defaultY);
else addWasdDpad(defaultX, defaultY);
diff --git a/app/src/main/java/xtr/keymapper/fragment/SettingsFragment.java b/app/src/main/java/xtr/keymapper/fragment/SettingsFragment.java
index 441889b3..e7deadc0 100644
--- a/app/src/main/java/xtr/keymapper/fragment/SettingsFragment.java
+++ b/app/src/main/java/xtr/keymapper/fragment/SettingsFragment.java
@@ -201,7 +201,7 @@ public void onDestroyView() {
keymapConfig.dpadRadiusMultiplier = binding.sliderDpad.getValue();
keymapConfig.applySharedPrefs();
- RemoteServiceHelper.reloadKeymap();
+ RemoteServiceHelper.reloadKeymap(getContext());
binding = null;
super.onDestroyView();
}
diff --git a/app/src/main/java/xtr/keymapper/keymap/KeymapConfig.java b/app/src/main/java/xtr/keymapper/keymap/KeymapConfig.java
index a1812028..3f9c4df1 100644
--- a/app/src/main/java/xtr/keymapper/keymap/KeymapConfig.java
+++ b/app/src/main/java/xtr/keymapper/keymap/KeymapConfig.java
@@ -102,7 +102,7 @@ private void loadSharedPrefs() {
swipeDelayMs = sharedPref.getInt("swipe_delay_ms", 0);
dpadRadiusMultiplier = sharedPref.getFloat("dpad_radius", 1f);
- touchpadInputMode = sharedPref.getString("touchpad_input_mode", TOUCHPAD_RELATIVE);
+ touchpadInputMode = sharedPref.getString("touchpad_input_mode", TOUCHPAD_DISABLED);
}
public void applySharedPrefs() {
diff --git a/app/src/main/java/xtr/keymapper/profiles/ProfileSelector.java b/app/src/main/java/xtr/keymapper/profiles/ProfileSelector.java
index f25fae59..8e8d3b6a 100644
--- a/app/src/main/java/xtr/keymapper/profiles/ProfileSelector.java
+++ b/app/src/main/java/xtr/keymapper/profiles/ProfileSelector.java
@@ -74,6 +74,7 @@ public static void createNewProfile(@UiContext Context context, OnProfileSelecte
}
public static void showEnableProfileDialog(@UiContext Context context, String packageName, OnProfileEnabledListener listener){
+ context.setTheme(R.style.Theme_XtMapper);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
AppViewBinding binding = AppViewBinding.inflate(LayoutInflater.from(context));
PackageManager pm = context.getPackageManager();
diff --git a/app/src/main/java/xtr/keymapper/server/Input.java b/app/src/main/java/xtr/keymapper/server/Input.java
index 87ee176f..5075a0a5 100644
--- a/app/src/main/java/xtr/keymapper/server/Input.java
+++ b/app/src/main/java/xtr/keymapper/server/Input.java
@@ -1,6 +1,6 @@
package xtr.keymapper.server;
-import android.hardware.input.InputManager;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -17,7 +17,7 @@
public class Input {
static Method injectInputEventMethod;
- static InputManager im;
+ static Object inputManager;
static int inputSource = InputDevice.SOURCE_TOUCHSCREEN;
private final PointersState pointersState = new PointersState();
@@ -80,7 +80,7 @@ public void injectTouch(int action, int pointerId, float pressure, float x, floa
0, 0, 1f, 1f,
0, 0, inputSource, 0);
try {
- injectInputEventMethod.invoke(im, motionEvent, 0);
+ injectInputEventMethod.invoke(inputManager, motionEvent, 0);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace(System.out);
}
@@ -108,7 +108,7 @@ private void injectScroll(ScrollEvent event, float value) {
0, 0, 1f, 1f, 0, 0,
InputDevice.SOURCE_MOUSE, 0);
try {
- injectInputEventMethod.invoke(im, motionEvent, 0);
+ injectInputEventMethod.invoke(inputManager, motionEvent, 0);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace(System.out);
}
@@ -189,7 +189,14 @@ private void next() {
String methodName = "getInstance";
Object[] objArr = new Object[0];
try {
- im = (InputManager) InputManager.class.getDeclaredMethod(methodName)
+ Class> inputManagerClass;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ inputManagerClass = Class.forName("android.hardware.input.InputManagerGlobal");
+ } else {
+ inputManagerClass = android.hardware.input.InputManager.class;
+ }
+
+ inputManager = inputManagerClass.getDeclaredMethod(methodName)
.invoke(null, objArr);
//Make MotionEvent.obtain() method accessible
methodName = "obtain";
@@ -199,7 +206,7 @@ private void next() {
//Get the reference to injectInputEvent method
methodName = "injectInputEvent";
- injectInputEventMethod = InputManager.class.getMethod(methodName, android.view.InputEvent.class, Integer.TYPE);
+ injectInputEventMethod = inputManagerClass.getMethod(methodName, android.view.InputEvent.class, Integer.TYPE);
} catch (Exception e) {
e.printStackTrace(System.out);
diff --git a/app/src/main/java/xtr/keymapper/server/InputService.java b/app/src/main/java/xtr/keymapper/server/InputService.java
index c2f84781..e902763c 100644
--- a/app/src/main/java/xtr/keymapper/server/InputService.java
+++ b/app/src/main/java/xtr/keymapper/server/InputService.java
@@ -1,6 +1,10 @@
package xtr.keymapper.server;
-import static xtr.keymapper.InputEventCodes.*;
+import static xtr.keymapper.InputEventCodes.BTN_MOUSE;
+import static xtr.keymapper.InputEventCodes.BTN_RIGHT;
+import static xtr.keymapper.InputEventCodes.REL_WHEEL;
+import static xtr.keymapper.InputEventCodes.REL_X;
+import static xtr.keymapper.InputEventCodes.REL_Y;
import android.os.RemoteException;
import android.view.MotionEvent;
@@ -16,7 +20,7 @@ public class InputService implements IInputInterface {
private final KeyEventHandler keyEventHandler;
private KeymapConfig keymapConfig;
private KeymapProfile keymapProfile;
- private static final Input input = new Input();
+ private final Input input = new Input();
public static final int UP = 0, DOWN = 1, MOVE = 2;
private final IRemoteServiceCallback mCallback;
final int supportsUinput;
diff --git a/app/src/main/java/xtr/keymapper/server/RemoteService.java b/app/src/main/java/xtr/keymapper/server/RemoteService.java
index e142392a..79281c38 100644
--- a/app/src/main/java/xtr/keymapper/server/RemoteService.java
+++ b/app/src/main/java/xtr/keymapper/server/RemoteService.java
@@ -1,9 +1,6 @@
package xtr.keymapper.server;
-import android.os.Looper;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
import java.io.BufferedReader;
import java.io.InputStreamReader;
@@ -20,47 +17,25 @@ public class RemoteService extends IRemoteService.Stub {
private String currentDevice = "";
private InputService inputService;
private OnKeyEventListener mOnKeyEventListener;
- private boolean isWaylandClient = false;
+ boolean isWaylandClient = false;
private ActivityObserverService activityObserverService;
+ String nativeLibraryDir = System.getProperty("java.library.path");
- public RemoteService() {
-
- }
-
- public static void main(String[] args) {
- loadLibraries();
- Looper.prepare();
- new RemoteService(args);
- Looper.loop();
- }
-
- public RemoteService(String[] args) {
- super();
- Log.i("XtMapper", "starting server...");
- try {
- ServiceManager.addService("xtmapper", this);
- System.out.println("Waiting for overlay...");
- for (String arg: args) {
- if (arg.equals("--wayland-client")) {
- isWaylandClient = true;
- System.out.println("using wayland client");
- }
- }
- start_getevent();
- } catch (Exception e) {
- e.printStackTrace(System.out);
- System.exit(1);
- }
+ public static void loadLibraries() {
+ System.loadLibrary("mouse_read");
+ System.loadLibrary("mouse_cursor");
+ System.loadLibrary("touchpad_direct");
+ System.loadLibrary("touchpad_relative");
}
- private void start_getevent() {
+ void start_getevent() {
new Thread(() -> {
try {
final BufferedReader getevent;
if (isWaylandClient) {
getevent = new BufferedReader(new InputStreamReader(System.in));
} else {
- getevent = Utils.geteventStream(System.getProperty("java.library.path"));
+ getevent = Utils.geteventStream(nativeLibraryDir);
}
String line;
while ((line = getevent.readLine()) != null) {
@@ -107,6 +82,7 @@ public boolean isRoot() {
@Override
public void startServer(KeymapProfile profile, KeymapConfig keymapConfig, IRemoteServiceCallback cb, int screenWidth, int screenHeight) throws RemoteException {
if (inputService != null) stopServer();
+ cb.asBinder().linkToDeath(this::stopServer, 0);
inputService = new InputService(profile, keymapConfig, cb, screenWidth, screenHeight, isWaylandClient);
if (!isWaylandClient) {
inputService.setMouseLock(true);
@@ -154,11 +130,13 @@ public void unregisterActivityObserver(ActivityObserver callback) {
activityObserverService = null;
}
+ @Override
public void pauseMouse(){
if (inputService != null)
if (!inputService.stopEvents) inputService.pauseResumeKeymap();
}
+ @Override
public void resumeMouse(){
if (inputService != null)
if (inputService.stopEvents) inputService.pauseResumeKeymap();
@@ -169,15 +147,4 @@ public void reloadKeymap() {
if (inputService != null) inputService.reloadKeymap();
}
- public static void loadLibraries() {
- System.loadLibrary("mouse_read");
- System.loadLibrary("mouse_cursor");
- System.loadLibrary("touchpad_direct");
- System.loadLibrary("touchpad_relative");
- }
-
- public static IRemoteService getInstance(){
- return IRemoteService.Stub.asInterface(ServiceManager.getService("xtmapper"));
- }
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/xtr/keymapper/server/RemoteServiceHelper.java b/app/src/main/java/xtr/keymapper/server/RemoteServiceHelper.java
index a2efd300..e500cf83 100644
--- a/app/src/main/java/xtr/keymapper/server/RemoteServiceHelper.java
+++ b/app/src/main/java/xtr/keymapper/server/RemoteServiceHelper.java
@@ -1,36 +1,103 @@
package xtr.keymapper.server;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
+import com.topjohnwu.superuser.Shell;
+import com.topjohnwu.superuser.ipc.RootService;
+
+import java.io.IOException;
+
import xtr.keymapper.IRemoteService;
public class RemoteServiceHelper {
- public static void pauseKeymap(){
- IRemoteService mService = RemoteService.getInstance();
- if (mService != null) try {
- mService.pauseMouse();
- } catch (RemoteException e) {
- Log.i("RemoteService", e.toString());
+ private static IRemoteService service = null;
+ public static boolean isRootService = true;
+
+ public static void pauseKeymap(Context context){
+ RemoteServiceHelper.getInstance(context, service -> {
+ try {
+ service.pauseMouse();
+ } catch (RemoteException e) {
+ Log.i("RemoteService", e.toString());
+ }
+ });
+ }
+
+ public static void resumeKeymap(Context context){
+ RemoteServiceHelper.getInstance(context, service -> {
+ try {
+ service.resumeMouse();
+ } catch (RemoteException e) {
+ Log.i("RemoteService", e.toString());
+ }
+ });
+ }
+
+ public static void reloadKeymap(Context context) {
+ RemoteServiceHelper.getInstance(context, service -> {
+ try {
+ service.reloadKeymap();
+ } catch (RemoteException e) {
+ Log.i("RemoteService", e.toString());
+ }
+ });
+ }
+
+ public interface RootRemoteServiceCallback {
+ void onConnection(IRemoteService service);
+ }
+ public static class RemoteServiceConnection implements ServiceConnection {
+ RootRemoteServiceCallback cb;
+ public RemoteServiceConnection(RootRemoteServiceCallback cb){
+ this.cb = cb;
+ }
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ cb.onConnection(IRemoteService.Stub.asInterface(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+
}
}
- public static void resumeKeymap(){
- IRemoteService mService = RemoteService.getInstance();
- if (mService != null) try {
- mService.resumeMouse();
- } catch (RemoteException e) {
- Log.i("RemoteService", e.toString());
+ public static void getInstance(){
+ if (service == null) {
+ // Try tcpip connection first
+ try {
+ service = new RemoteServiceSocketClient();
+ } catch (IOException e) {
+ Log.e(e.toString(), e.getMessage(), e);
+ RemoteServiceSocketClient.socket = null;
+ }
+ if (RemoteServiceSocketClient.socket == null) {
+ service = IRemoteService.Stub.asInterface(ServiceManager.getService("xtmapper"));
+ if (service != null) try {
+ service.asBinder().linkToDeath(() -> service = null, 0);
+ } catch (RemoteException ignored) {
+ }
+ }
}
}
- public static void reloadKeymap() {
- IRemoteService mService = RemoteService.getInstance();
- if (mService != null) try {
- mService.reloadKeymap();
- } catch (RemoteException e) {
- Log.i("RemoteService", e.toString());
+ public static void getInstance(Context context, RootRemoteServiceCallback cb){
+ getInstance();
+ if (service != null) cb.onConnection(service);
+ else {
+ Boolean hasRootAccess = Shell.isAppGrantedRoot();
+ if (hasRootAccess != null) isRootService = hasRootAccess;
+ RemoteServiceConnection connection = new RemoteServiceConnection(cb);
+ Intent intent = new Intent(context, RootRemoteService.class);
+ RootService.bind(intent, connection);
}
}
}
diff --git a/app/src/main/java/xtr/keymapper/server/RemoteServiceShell.java b/app/src/main/java/xtr/keymapper/server/RemoteServiceShell.java
new file mode 100644
index 00000000..cc021933
--- /dev/null
+++ b/app/src/main/java/xtr/keymapper/server/RemoteServiceShell.java
@@ -0,0 +1,33 @@
+package xtr.keymapper.server;
+
+
+import android.os.Looper;
+import android.os.ServiceManager;
+
+public class RemoteServiceShell {
+ public static void main(String[] args) {
+ RemoteService.loadLibraries();
+ RemoteService mService = new RemoteService();
+ Looper.prepareMainLooper();
+ try {
+ System.out.println("Waiting for overlay...");
+ for (String arg: args) {
+ if (arg.equals("--wayland-client")) {
+ mService.isWaylandClient = true;
+ System.out.println("using wayland client");
+ }
+ if (arg.equals("--tcpip")) {
+ mService.start_getevent();
+ System.out.println("using tcpip");
+ new RemoteServiceSocketServer(mService);
+ }
+ }
+ ServiceManager.addService("xtmapper", mService);
+ mService.start_getevent();
+ } catch (Exception e) {
+ e.printStackTrace(System.out);
+ System.exit(1);
+ }
+ Looper.loop();
+ }
+}
diff --git a/app/src/main/java/xtr/keymapper/server/RemoteServiceSocketClient.java b/app/src/main/java/xtr/keymapper/server/RemoteServiceSocketClient.java
index 685f0127..32a83631 100644
--- a/app/src/main/java/xtr/keymapper/server/RemoteServiceSocketClient.java
+++ b/app/src/main/java/xtr/keymapper/server/RemoteServiceSocketClient.java
@@ -6,6 +6,9 @@
import android.os.Parcel;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.function.Consumer;
import xtr.keymapper.IRemoteService;
import xtr.keymapper.IRemoteServiceCallback;
@@ -14,176 +17,133 @@
public class RemoteServiceSocketClient implements IRemoteService {
- private LocalSocket socket;
+ // Socket should stay alive
+ public static LocalSocket socket = null;
- static private void writeTypedObject(
- android.os.Parcel parcel, T value, int parcelableFlags) {
- if (value != null) {
- parcel.writeInt(1);
- value.writeToParcel(parcel, parcelableFlags);
- } else {
- parcel.writeInt(0);
+ public RemoteServiceSocketClient() throws IOException {
+ if (socket == null) {
+ socket = new LocalSocket();
+ socket.connect(new LocalSocketAddress("xtmapper-a3e11694"));
}
}
+ public static class ParcelableByteArray {
+ final byte[][] data;
+ int i = 0;
- private boolean transactRemote(int code, Parcel data, Parcel reply, int flags) {
+ public ParcelableByteArray(int size) {
+ data = new byte[size][];
+ }
+
+ public void foreach(Consumer action) {
+ for (byte[] bytes : data) action.accept(bytes);
+ }
+
+ private void writeTypedObject(T value) {
+ Parcel parcel = Parcel.obtain();
+ value.writeToParcel(parcel, 0);
+ data[i] = parcel.marshall();
+ parcel.recycle();
+ i++;
+ }
+
+ public void writeStrongInterface(Object o) {
+ data[i] = new byte[0];
+ i++;
+ }
+
+ public void writeInt(int x) {
+ data[i] = ByteBuffer.allocate(4).putInt(x).array();
+ i++;
+ }
+ }
+
+ public int readInt() {
+ int i;
try {
- byte[] b = data.marshall();
- socket.getOutputStream().write(b.length);
- socket.getOutputStream().write(b);
+ InputStream inputStream = socket.getInputStream();
+ int length = inputStream.read();
+ byte[] bytes = new byte[length];
+ inputStream.read(bytes);
+ i = ByteBuffer.wrap(bytes).getInt();
} catch (IOException e) {
throw new RuntimeException(e);
}
- return true;
+ return i;
}
- public void init() throws IOException {
- socket = new LocalSocket();
- socket.connect(new LocalSocketAddress("xtmapper-a3e11694"));
+
+ private boolean transactRemote(int code, ParcelableByteArray data) {
+ try {
+ socket.getOutputStream().write(code);
+
+ data.foreach(bytes -> {
+ try {
+ socket.getOutputStream().write(bytes.length);
+ socket.getOutputStream().write(bytes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return true;
}
@Override public boolean isRoot()
{
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- boolean _result;
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- boolean _status = transactRemote(TRANSACTION_isRoot, _data, _reply, 0);
- _reply.readException();
- _result = (0!=_reply.readInt());
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ boolean _status = transactRemote(TRANSACTION_isRoot, _data);
+
+ boolean _result = (0!=readInt());
+
return _result;
}
@Override public void startServer(KeymapProfile profile, KeymapConfig keymapConfig, IRemoteServiceCallback cb, int screenWidth, int screenHeight) {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- writeTypedObject(_data, profile, 0);
- writeTypedObject(_data, keymapConfig, 0);
- _data.writeStrongInterface(cb);
- _data.writeInt(screenWidth);
- _data.writeInt(screenHeight);
- boolean _status = transactRemote(TRANSACTION_startServer, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ _data.writeTypedObject(profile);
+ _data.writeTypedObject(keymapConfig);
+ _data.writeStrongInterface(null);
+ _data.writeInt(screenWidth);
+ _data.writeInt(screenHeight);
+ boolean _status = transactRemote(TRANSACTION_startServer, _data);
}
@Override public void stopServer() {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- boolean _status = transactRemote(TRANSACTION_stopServer, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ boolean _status = transactRemote(TRANSACTION_stopServer, _data);
}
@Override public void registerOnKeyEventListener(xtr.keymapper.OnKeyEventListener l) {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongInterface(l);
- boolean _status = transactRemote(TRANSACTION_registerOnKeyEventListener, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ _data.writeStrongInterface(null);
+ boolean _status = transactRemote(TRANSACTION_registerOnKeyEventListener, _data);
+
}
@Override public void unregisterOnKeyEventListener(xtr.keymapper.OnKeyEventListener l) {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongInterface(l);
- boolean _status = transactRemote(TRANSACTION_unregisterOnKeyEventListener, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ _data.writeStrongInterface(null);
+ boolean _status = transactRemote(TRANSACTION_unregisterOnKeyEventListener, _data);
}
@Override public void registerActivityObserver(xtr.keymapper.ActivityObserver callback) {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongInterface(callback);
- boolean _status = transactRemote(TRANSACTION_registerActivityObserver, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ _data.writeStrongInterface(null);
+ boolean _status = transactRemote(TRANSACTION_registerActivityObserver, _data);
}
@Override public void unregisterActivityObserver(xtr.keymapper.ActivityObserver callback) {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- _data.writeStrongInterface(callback);
- boolean _status = transactRemote(TRANSACTION_unregisterActivityObserver, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ _data.writeStrongInterface(null);
+ boolean _status = transactRemote(TRANSACTION_unregisterActivityObserver, _data);
}
@Override public void resumeMouse() {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- boolean _status = transactRemote(TRANSACTION_resumeMouse, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ boolean _status = transactRemote(TRANSACTION_resumeMouse, _data);
}
@Override public void pauseMouse() {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- boolean _status = transactRemote(TRANSACTION_pauseMouse, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ boolean _status = transactRemote(TRANSACTION_pauseMouse, _data);
}
@Override public void reloadKeymap() {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- boolean _status = transactRemote(TRANSACTION_reloadKeymap, _data, _reply, 0);
- _reply.readException();
- }
- finally {
- _reply.recycle();
- _data.recycle();
- }
+ ParcelableByteArray _data = new ParcelableByteArray(5);
+ boolean _status = transactRemote(TRANSACTION_reloadKeymap, _data);
}
static final int TRANSACTION_isRoot = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
diff --git a/app/src/main/java/xtr/keymapper/server/RemoteServiceSocketServer.java b/app/src/main/java/xtr/keymapper/server/RemoteServiceSocketServer.java
index 71ede3aa..281ed7dc 100644
--- a/app/src/main/java/xtr/keymapper/server/RemoteServiceSocketServer.java
+++ b/app/src/main/java/xtr/keymapper/server/RemoteServiceSocketServer.java
@@ -1,23 +1,174 @@
package xtr.keymapper.server;
+import static xtr.keymapper.server.RemoteServiceSocketClient.*;
+
import android.net.LocalServerSocket;
import android.net.LocalSocket;
+import android.os.Parcel;
+import android.os.RemoteException;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
public class RemoteServiceSocketServer {
- private LocalSocket socket;
+// private final LocalSocket socket;
+ private final RemoteService mService;
+ private final InputStream inputStream;
+ private final OutputStream outputStream;
+
+ private T readTypedObject(android.os.Parcelable.Creator c) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ int length = inputStream.read();
+ byte[] bytes = new byte[length];
+ System.out.println(length);
+ inputStream.read(bytes);
+ System.out.println(bytes.toString());
+ parcel.unmarshall(bytes, 0, length);
+ parcel.setDataPosition(0);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ T obj = c.createFromParcel(parcel);
+ parcel.recycle();
+ return obj;
+ }
- public void init() {
+ // blocking
+ public RemoteServiceSocketServer(RemoteService mService) {
+ this.mService = mService;
try {
LocalServerSocket serverSocket = new LocalServerSocket("xtmapper-a3e11694");
- socket = serverSocket.accept();
- int length = socket.getInputStream().read();
+ LocalSocket socket = serverSocket.accept();
+ inputStream = socket.getInputStream();
+ outputStream = socket.getOutputStream();
+ while (true) {
+ int code = inputStream.read();
+ if (code == -1) continue;
+
+ int length = inputStream.read();
+ if (length == -1) continue;
+ byte[] b = new byte[length];
+ inputStream.read(b);
+
+ onTransact(code);
+ }
+ } catch (IOException | RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void onTransact(int code) throws RemoteException {
+ switch (code)
+ {
+ case TRANSACTION_isRoot:
+ {
+ boolean _result = mService.isRoot();
+ writeNoException();
+ writeInt(((_result)?(1):(0)));
+ break;
+ }
+ case TRANSACTION_startServer:
+ {
+ xtr.keymapper.keymap.KeymapProfile _arg0;
+ _arg0 = readTypedObject(xtr.keymapper.keymap.KeymapProfile.CREATOR);
+ xtr.keymapper.keymap.KeymapConfig _arg1;
+ _arg1 = readTypedObject(xtr.keymapper.keymap.KeymapConfig.CREATOR);
+ xtr.keymapper.IRemoteServiceCallback _arg2;
+ _arg2 = null;
+ int _arg3;
+ _arg3 = readInt();
+ int _arg4;
+ _arg4 = readInt();
+ mService.startServer(_arg0, _arg1, _arg2, _arg3, _arg4);
+ writeNoException();
+ break;
+ }
+ case TRANSACTION_stopServer:
+ {
+ mService.stopServer();
+ writeNoException();
+ break;
+ }
+ case TRANSACTION_registerOnKeyEventListener:
+ {
+ xtr.keymapper.OnKeyEventListener _arg0;
+ _arg0 = null;
+ mService.registerOnKeyEventListener(_arg0);
+ writeNoException();
+ break;
+ }
+ case TRANSACTION_unregisterOnKeyEventListener:
+ {
+ xtr.keymapper.OnKeyEventListener _arg0;
+ _arg0 = null;
+ mService.unregisterOnKeyEventListener(_arg0);
+ writeNoException();
+ break;
+ }
+ case TRANSACTION_registerActivityObserver:
+ {
+ xtr.keymapper.ActivityObserver _arg0;
+ _arg0 = null;
+ mService.registerActivityObserver(_arg0);
+ writeNoException();
+ break;
+ }
+ case TRANSACTION_unregisterActivityObserver:
+ {
+ xtr.keymapper.ActivityObserver _arg0;
+ _arg0 = null;
+ mService.unregisterActivityObserver(_arg0);
+ writeNoException();
+ break;
+ }
+ case TRANSACTION_resumeMouse:
+ {
+ mService.resumeMouse();
+ writeNoException();
+ break;
+ }
+ case TRANSACTION_pauseMouse:
+ {
+ mService.pauseMouse();
+ writeNoException();
+ break;
+ }
+ case TRANSACTION_reloadKeymap:
+ {
+ mService.reloadKeymap();
+ writeNoException();
+ break;
+ }
+ }
+ }
+
+ public int readInt() {
+ int i;
+ try {
+ int length = inputStream.read();
byte[] b = new byte[length];
- socket.getInputStream().read(b);
+ inputStream.read(b);
+ i = ByteBuffer.wrap(b).getInt();
} catch (IOException e) {
throw new RuntimeException(e);
}
+ return i;
+ }
+
+ public void writeNoException() {
}
+
+ public void writeInt(int x) {
+ byte[] bytes = ByteBuffer.allocate(4).putInt(x).array();
+ try {
+ outputStream.write(bytes.length);
+ outputStream.write(bytes);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/app/src/main/java/xtr/keymapper/server/RootRemoteService.java b/app/src/main/java/xtr/keymapper/server/RootRemoteService.java
index b9786541..cd47210a 100644
--- a/app/src/main/java/xtr/keymapper/server/RootRemoteService.java
+++ b/app/src/main/java/xtr/keymapper/server/RootRemoteService.java
@@ -1,6 +1,8 @@
package xtr.keymapper.server;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.Process;
@@ -20,8 +22,18 @@ class RootRemoteService extends RootService {
public final RemoteService mService = new RemoteService();
+
@Override
public IBinder onBind(@NonNull Intent intent) {
+ PackageManager pm = this.getPackageManager();
+ String packageName = this.getPackageName();
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(packageName, 0);
+ mService.nativeLibraryDir = ai.nativeLibraryDir;
+ mService.start_getevent();
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
return mService;
}
diff --git a/app/src/main/java/xtr/keymapper/service/InputListenerService.java b/app/src/main/java/xtr/keymapper/service/InputListenerService.java
index 1d56718a..f29d0824 100644
--- a/app/src/main/java/xtr/keymapper/service/InputListenerService.java
+++ b/app/src/main/java/xtr/keymapper/service/InputListenerService.java
@@ -6,22 +6,23 @@
import xtr.keymapper.server.RemoteServiceHelper;
public class InputListenerService extends InputMethodService {
+ // Input method to detect when user is inputting text
@Override
public void onDestroy() {
super.onDestroy();
- RemoteServiceHelper.resumeKeymap();
+ RemoteServiceHelper.resumeKeymap(getApplicationContext());
}
@Override
public void onFinishInputView(boolean finishingInput) {
super.onFinishInputView(finishingInput);
- RemoteServiceHelper.resumeKeymap();
+ RemoteServiceHelper.resumeKeymap(getApplicationContext());
}
@Override
public void onStartInputView(EditorInfo info, boolean restarting) {
super.onStartInputView(info, restarting);
- RemoteServiceHelper.pauseKeymap();
+ RemoteServiceHelper.pauseKeymap(getApplicationContext());
}
}
diff --git a/app/src/main/java/xtr/keymapper/touchpointer/PidProvider.java b/app/src/main/java/xtr/keymapper/touchpointer/PidProvider.java
index 3973e628..d8632683 100644
--- a/app/src/main/java/xtr/keymapper/touchpointer/PidProvider.java
+++ b/app/src/main/java/xtr/keymapper/touchpointer/PidProvider.java
@@ -4,8 +4,8 @@
public final class PidProvider {
private final SimpleArrayMap pidList = new SimpleArrayMap<>();
-
- public Integer getPid(String keycode) {
+ // returns pointerIds on demand
+ public Integer getPid(String keycode) {
if (!pidList.containsKey(keycode))
pidList.put(keycode, pidList.size());
return pidList.get(keycode);
diff --git a/app/src/main/res/drawable/ic_terminal.xml b/app/src/main/res/drawable/ic_terminal.xml
deleted file mode 100644
index 3ac33d71..00000000
--- a/app/src/main/res/drawable/ic_terminal.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml
index fe118b2d..07956505 100644
--- a/app/src/main/res/layout-land/activity_main.xml
+++ b/app/src/main/res/layout-land/activity_main.xml
@@ -2,7 +2,6 @@
-
-
diff --git a/app/src/main/res/layout/keymap_editor.xml b/app/src/main/res/layout/keymap_editor.xml
index 524c66c1..e6d6a8a7 100644
--- a/app/src/main/res/layout/keymap_editor.xml
+++ b/app/src/main/res/layout/keymap_editor.xml
@@ -1,6 +1,5 @@
-XtMapper
Start
Stop
- Auto-Activate
- Activation
- Activated
- Keymapper is activated. Tap on Start button to start keymapper.
Settings
Edit keymap
Help
package name:
Mouse Sensitivity
Dpad Radius
-
- \n XtMapper: Free and open source keymapper. \n\n
-
- 1. Activation \n\n
- 2. Tap on button to start/stop service \n\n
- 3. When service running, drag down notification panel and click on notification to launch editor \n\n
- 4. Setup keymap according to touch controls in your game and save \n\n
- Touchpads are not supported, only mouse and keyboard. \n\n
- Mouse right click or ~ key to aim with mouse for FPS games \n\n
- Left-click drag mouse while holding ctrl key to simulate pinch-to-zoom gesture with two fingers. \n\n
-
- \n License: GPL v3.0
- \n GitHub: https://github.com/Xtr126/XtMapper \n\n
-
Shooting mode activated Right click or ~ key to exit
@@ -67,8 +49,14 @@
Swipe delay %d milliseconds
Profile Name
Root access not found
- Root access is required for the keymapper to function. \n Please grant root access to XtMapper from KernelSU manager app:
+ Root access is required for the keymapper to function. \n Please grant superuser to XtMapper from KernelSU manager app:
+ Service failed to start as root
+
+ Unable to obtain privileges. Do you want to exit the application?\n
+ If you already granted superuser privileges, it might work after exiting and opening the app.
+
Disable auto profiling
Touchpad Input
+ Launch App
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index cfe72ac4..ee55c94b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '8.2.1' apply false
+ id 'com.android.application' version '8.2.2' apply false
id 'com.android.library' version '8.2.2' apply false
}
\ No newline at end of file