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