From e9ee251f8f543b7851f96cc7d7690c46251f5155 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:53:33 +0200 Subject: [PATCH 01/12] migrate MotionEventMod to Kotlin --- MotionEventMod/build.gradle.kts | 1 + .../MotionEventMod/XposedHook.java | 55 ------------------- .../MotionEventMod/XposedHook.kt | 51 +++++++++++++++++ 3 files changed, 52 insertions(+), 55 deletions(-) delete mode 100644 MotionEventMod/src/main/java/com/programminghoch10/MotionEventMod/XposedHook.java create mode 100644 MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt diff --git a/MotionEventMod/build.gradle.kts b/MotionEventMod/build.gradle.kts index f795483..048f240 100644 --- a/MotionEventMod/build.gradle.kts +++ b/MotionEventMod/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.buildlogic.android.application) + alias(libs.plugins.buildlogic.kotlin.android) } android { diff --git a/MotionEventMod/src/main/java/com/programminghoch10/MotionEventMod/XposedHook.java b/MotionEventMod/src/main/java/com/programminghoch10/MotionEventMod/XposedHook.java deleted file mode 100644 index b9f048a..0000000 --- a/MotionEventMod/src/main/java/com/programminghoch10/MotionEventMod/XposedHook.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.programminghoch10.MotionEventMod; - -import android.view.MotionEvent; -import android.view.View; - -import de.robv.android.xposed.IXposedHookLoadPackage; -import de.robv.android.xposed.XC_MethodHook; -import de.robv.android.xposed.XposedHelpers; -import de.robv.android.xposed.callbacks.XC_LoadPackage; - -public class XposedHook implements IXposedHookLoadPackage { - private static final String TAG = BuildConfig.APPLICATION_ID.split("[.]")[2]; - private static final long hover_timeout = 1000L; - private long hover_exit_timestamp = 0; - - @Override - public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) { -// Log.d(TAG, "handleLoadPackage: hooking package " + lpparam.packageName); - XposedHelpers.findAndHookMethod( - View.class, "dispatchTouchEvent", MotionEvent.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - MotionEvent event = (MotionEvent) param.args[0]; - //Log.d(TAG, "dispatchTouchEvent: event=" + event); - if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) return; - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_MOVE: - if (hover_exit_timestamp + hover_timeout > System.currentTimeMillis()) param.setResult(true); - break; - } - } - } - ); - XposedHelpers.findAndHookMethod( - View.class, "dispatchHoverEvent", MotionEvent.class, new XC_MethodHook() { - @Override - protected void beforeHookedMethod(MethodHookParam param) { - MotionEvent event = (MotionEvent) param.args[0]; - //Log.d(TAG, "dispatchHoverEvent: event=" + event); - switch (event.getAction()) { - case MotionEvent.ACTION_HOVER_ENTER: - //Log.d(TAG, "dispatchHoverEvent: Hover enter"); - break; - case MotionEvent.ACTION_HOVER_EXIT: - hover_exit_timestamp = System.currentTimeMillis(); - //Log.d(TAG, "dispatchHoverEvent: Hover exit"); - break; - } - } - } - ); - } -} diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt new file mode 100644 index 0000000..78c1fea --- /dev/null +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -0,0 +1,51 @@ +package com.programminghoch10.MotionEventMod + +import android.view.MotionEvent +import android.view.View +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XposedHelpers +import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam + +private val TAG: String = BuildConfig.APPLICATION_ID.split("[.]".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[2] +private const val hover_timeout = 1000L + +class XposedHook : IXposedHookLoadPackage { + private var hover_exit_timestamp: Long = 0 + + override fun handleLoadPackage(lpparam: LoadPackageParam) { + XposedHelpers.findAndHookMethod( + View::class.java, + "dispatchTouchEvent", + MotionEvent::class.java, + object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + val event = param.args[0] as MotionEvent + if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) return + when (event.action) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE -> { + if (hover_exit_timestamp + hover_timeout > System.currentTimeMillis()) { + param.setResult(true) + } + } + } + } + }, + ) + + XposedHelpers.findAndHookMethod( + View::class.java, + "dispatchHoverEvent", + MotionEvent::class.java, + object : XC_MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + val event = param.args[0] as MotionEvent + when (event.action) { + MotionEvent.ACTION_HOVER_ENTER -> {} + MotionEvent.ACTION_HOVER_EXIT -> hover_exit_timestamp = System.currentTimeMillis() + } + } + }, + ) + } +} From 91c52b5464c754e3f543e49d22a63a7f7641144c Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:55:56 +0200 Subject: [PATCH 02/12] add empty configuration activity stubs to MotionEventMod --- MotionEventMod/build.gradle.kts | 11 ++++-- MotionEventMod/src/main/AndroidManifest.xml | 24 ++++++++++-- .../MotionEventMod/InputTestActivity.kt | 17 ++++++++ .../MotionEventMod/SettingsActivity.kt | 30 ++++++++++++++ .../src/main/res/layout/settings_activity.xml | 13 +++++++ .../src/main/res/values-v21/themes.xml | 5 +++ .../src/main/res/values/strings.xml | 6 +++ MotionEventMod/src/main/res/values/themes.xml | 9 +++++ .../src/main/res/xml/root_preferences.xml | 39 +++++++++++++++++++ 9 files changed, 147 insertions(+), 7 deletions(-) create mode 100644 MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/InputTestActivity.kt create mode 100644 MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt create mode 100644 MotionEventMod/src/main/res/layout/settings_activity.xml create mode 100644 MotionEventMod/src/main/res/values-v21/themes.xml create mode 100644 MotionEventMod/src/main/res/values/strings.xml create mode 100644 MotionEventMod/src/main/res/values/themes.xml create mode 100644 MotionEventMod/src/main/res/xml/root_preferences.xml diff --git a/MotionEventMod/build.gradle.kts b/MotionEventMod/build.gradle.kts index 048f240..ae1139c 100644 --- a/MotionEventMod/build.gradle.kts +++ b/MotionEventMod/build.gradle.kts @@ -4,15 +4,18 @@ plugins { } android { - val packageName = "com.programminghoch10.MotionEventMod" - namespace = packageName + namespace = "com.programminghoch10.MotionEventMod" defaultConfig { - applicationId = packageName minSdk = 14 - targetSdk = 33 + targetSdk = 37 + buildConfigField("String", "SHARED_PREFERENCES_NAME", "\"MotionEventMod\"") } buildFeatures { buildConfig = true } } + +dependencies { + implementation(libs.androidx.preference.ktx) +} diff --git a/MotionEventMod/src/main/AndroidManifest.xml b/MotionEventMod/src/main/AndroidManifest.xml index e64c5c4..6da92a5 100644 --- a/MotionEventMod/src/main/AndroidManifest.xml +++ b/MotionEventMod/src/main/AndroidManifest.xml @@ -4,8 +4,26 @@ + + + + + + + + + + diff --git a/MotionEventMod/src/main/res/values-v21/themes.xml b/MotionEventMod/src/main/res/values-v21/themes.xml new file mode 100644 index 0000000..ee02adb --- /dev/null +++ b/MotionEventMod/src/main/res/values-v21/themes.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml new file mode 100644 index 0000000..1218d32 --- /dev/null +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -0,0 +1,39 @@ + + + + + + + + + + From 0716eb5c6544c2708d20c83356d887ed8d1b2160 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 21 Sep 2026 16:02:53 +0200 Subject: [PATCH 03/12] MotionEventMod: replace TAG constant with buildConfig entry --- MotionEventMod/build.gradle.kts | 1 + .../kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/MotionEventMod/build.gradle.kts b/MotionEventMod/build.gradle.kts index ae1139c..eb3db92 100644 --- a/MotionEventMod/build.gradle.kts +++ b/MotionEventMod/build.gradle.kts @@ -10,6 +10,7 @@ android { minSdk = 14 targetSdk = 37 buildConfigField("String", "SHARED_PREFERENCES_NAME", "\"MotionEventMod\"") + buildConfigField("String", "TAG", "\"${namespace!!.split(".").last()}\"") } buildFeatures { buildConfig = true diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt index 78c1fea..4d10390 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -7,7 +7,6 @@ import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam -private val TAG: String = BuildConfig.APPLICATION_ID.split("[.]".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[2] private const val hover_timeout = 1000L class XposedHook : IXposedHookLoadPackage { From 00fddc81bc9786fbac533f36e690691b3bcca856 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:33:09 +0200 Subject: [PATCH 04/12] add MotionEventMod SettingsActivity preference titles and summaries --- MotionEventMod/src/main/AndroidManifest.xml | 4 +++- MotionEventMod/src/main/res/values/strings.xml | 14 +++++++++++++- .../src/main/res/xml/root_preferences.xml | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/MotionEventMod/src/main/AndroidManifest.xml b/MotionEventMod/src/main/AndroidManifest.xml index 6da92a5..c547eb1 100644 --- a/MotionEventMod/src/main/AndroidManifest.xml +++ b/MotionEventMod/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ android:name=".SettingsActivity" android:excludeFromRecents="true" android:exported="true" - android:label="@string/title_activity_settings" + android:label="@string/activity_settings_title" android:theme="@style/AppTheme" > @@ -20,7 +20,9 @@ diff --git a/MotionEventMod/src/main/res/values/strings.xml b/MotionEventMod/src/main/res/values/strings.xml index 937b828..e3420a1 100644 --- a/MotionEventMod/src/main/res/values/strings.xml +++ b/MotionEventMod/src/main/res/values/strings.xml @@ -1,6 +1,18 @@ - MotionEventMod Configuration + MotionEventMod Configuration MotionEventMod Customize MotionEvent functionality + MotionEvents Test + Disable Touch while Pen is in use + Disable Touch while Pen is hovering + Disable Touch timeout + How long to wait after last pen interaction before the pen is re-enabled. + Replay Events after Timeout + Replay MotionEvents which occurred during the disabled phase, if they continue after the timeout ended. + Mark Disabled MotionEvents as Handled + Tell Android the MotionEvents have been handled. + Tell Android the MotionEvents have not been handled. + Disable MotionEvents by Type + Test the module configuration diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml index 1218d32..0416a09 100644 --- a/MotionEventMod/src/main/res/xml/root_preferences.xml +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -5,35 +5,49 @@ app:enabled="false" app:iconSpaceReserved="false" app:key="disableTouchDuringPen" + app:title="@string/disableTouchDuringPen_title" /> From df2f780bd42646b060e66d4f9f0281911bf09fee Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Mon, 21 Sep 2026 19:33:51 +0200 Subject: [PATCH 05/12] implement MotionEventMod disable on Pen and Hover --- .../MotionEventMod/SettingsActivity.kt | 47 +++++++++++++++ .../MotionEventMod/XposedHook.kt | 60 ++++++++++++++----- .../src/main/res/xml/root_preferences.xml | 4 +- 3 files changed, 94 insertions(+), 17 deletions(-) diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt index aa153a4..8fa42a0 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt @@ -1,8 +1,18 @@ package com.programminghoch10.MotionEventMod +import kotlin.sequences.forEach +import android.content.Intent import android.os.Bundle +import android.text.InputType import androidx.fragment.app.FragmentActivity +import androidx.preference.EditTextPreference +import androidx.preference.MultiSelectListPreferenceDialogFragmentCompat +import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceGroup +import androidx.preference.SwitchPreference +import androidx.preference.TwoStatePreference +import androidx.preference.children import com.programminghoch10.MotionEventMod.BuildConfig.SHARED_PREFERENCES_NAME class SettingsActivity : FragmentActivity() { @@ -25,6 +35,43 @@ class SettingsActivity : FragmentActivity() { preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE setPreferencesFromResource(R.xml.root_preferences, rootKey) + + val disableTouchTimeoutPreference = findPreference("disableTouchTimeout")!! + val disableTouchDuringPenPreference = findPreference("disableTouchDuringPen")!! + val disableTouchDuringHoverPreference = findPreference("disableTouchDuringHover")!! + val disableTypesPreference = findPreference("disableTypes")!! + val testPreference = findPreference("test")!! + + fun recalculateDependencies() { + disableTouchTimeoutPreference.isEnabled = disableTouchDuringPenPreference.isChecked || disableTouchDuringHoverPreference.isChecked + } + recalculateDependencies() + listOf(disableTouchDuringPenPreference, disableTouchDuringHoverPreference).forEach { + it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> + recalculateDependencies() + true + } + } + disableTouchTimeoutPreference.setOnBindEditTextListener { + it.hint = "Time in seconds" + it.inputType = InputType.TYPE_CLASS_NUMBER + } + disableTypesPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + parentFragmentManager.beginTransaction().add(R.id.settings, TypeSelectorFragment()).commit() > 0 + } + testPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + val intent = Intent(context, InputTestActivity::class.java) + requireActivity().startActivity(intent) + true + } + } + } + + class TypeSelectorFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + TODO("Not yet implemented") } } } + +private val TwoStatePreference.isEnabledAndChecked get() = isEnabled && isChecked diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt index 4d10390..d0f60f0 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -2,17 +2,51 @@ package com.programminghoch10.MotionEventMod import android.view.MotionEvent import android.view.View +import com.programminghoch10.MotionEventMod.BuildConfig.APPLICATION_ID +import com.programminghoch10.MotionEventMod.BuildConfig.SHARED_PREFERENCES_NAME import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XC_MethodHook +import de.robv.android.xposed.XC_MethodHook.MethodHookParam +import de.robv.android.xposed.XSharedPreferences import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam -private const val hover_timeout = 1000L - class XposedHook : IXposedHookLoadPackage { - private var hover_exit_timestamp: Long = 0 + val sharedPreferences = XSharedPreferences(APPLICATION_ID, SHARED_PREFERENCES_NAME) + val disableTouchDuringPen get() = sharedPreferences.getBoolean("disableTouchDuringPen", false) + val disableTouchDuringHover get() = sharedPreferences.getBoolean("disableTouchDuringHover", false) + val markAsHandled get() = sharedPreferences.getBoolean("markAsHandled", true) + fun preventMotionEvent(param: MethodHookParam) = run { param.result = markAsHandled } + + private var isPenDown: Boolean = false + private var isPenHovering: Boolean = false + private var lastPenEventTimestamp: Long = 0L + private var lastHoverEventTimestamp: Long = 0L + + fun handleTouchEvent(event: MotionEvent, param: MethodHookParam) { + if (disableTouchDuringPen && isPenDown) preventMotionEvent(param) + if (disableTouchDuringHover && isPenHovering) preventMotionEvent(param) + } + + fun handleStylusEvent(event: MotionEvent, param: MethodHookParam) { + when (event.action) { + MotionEvent.ACTION_DOWN -> isPenDown = true + MotionEvent.ACTION_UP -> isPenDown = false + } + lastPenEventTimestamp = System.currentTimeMillis() + } + + fun handleHoverEvent(event: MotionEvent, param: MethodHookParam) { + when (event.action) { + MotionEvent.ACTION_HOVER_ENTER -> isPenHovering = true + MotionEvent.ACTION_HOVER_EXIT -> isPenHovering = false + } + lastHoverEventTimestamp = System.currentTimeMillis() + } override fun handleLoadPackage(lpparam: LoadPackageParam) { + if (lpparam.packageName == "android") return + XposedHelpers.findAndHookMethod( View::class.java, "dispatchTouchEvent", @@ -20,13 +54,14 @@ class XposedHook : IXposedHookLoadPackage { object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as MotionEvent - if (event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS) return - when (event.action) { - MotionEvent.ACTION_DOWN, MotionEvent.ACTION_UP, MotionEvent.ACTION_MOVE -> { - if (hover_exit_timestamp + hover_timeout > System.currentTimeMillis()) { - param.setResult(true) - } - } + val actionIndex = event.actionIndex + val pointerId = event.getPointerId(actionIndex) + val toolType = event.getToolType(pointerId) + when (toolType) { + MotionEvent.TOOL_TYPE_UNKNOWN -> return + MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER -> handleStylusEvent(event, param) + MotionEvent.TOOL_TYPE_MOUSE -> TODO("implement mouse") + MotionEvent.TOOL_TYPE_FINGER -> handleTouchEvent(event, param) } } }, @@ -39,10 +74,7 @@ class XposedHook : IXposedHookLoadPackage { object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as MotionEvent - when (event.action) { - MotionEvent.ACTION_HOVER_ENTER -> {} - MotionEvent.ACTION_HOVER_EXIT -> hover_exit_timestamp = System.currentTimeMillis() - } + handleHoverEvent(event, param) } }, ) diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml index 0416a09..2147b41 100644 --- a/MotionEventMod/src/main/res/xml/root_preferences.xml +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -2,13 +2,11 @@ Date: Wed, 23 Sep 2026 16:37:16 +0200 Subject: [PATCH 06/12] MotionEventMod: re-implement Touch Timeout --- .../MotionEventMod/SettingsActivity.kt | 11 +---- .../MotionEventMod/TimeoutPreference.kt | 46 +++++++++++++++++++ .../MotionEventMod/XposedHook.kt | 20 ++++++-- .../res/layout/timeoutpreference_dialog.xml | 24 ++++++++++ .../src/main/res/values/strings.xml | 5 +- .../src/main/res/xml/root_preferences.xml | 3 +- 6 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/TimeoutPreference.kt create mode 100644 MotionEventMod/src/main/res/layout/timeoutpreference_dialog.xml diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt index 8fa42a0..9d9a9f3 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt @@ -1,12 +1,7 @@ package com.programminghoch10.MotionEventMod -import kotlin.sequences.forEach -import android.content.Intent import android.os.Bundle -import android.text.InputType import androidx.fragment.app.FragmentActivity -import androidx.preference.EditTextPreference -import androidx.preference.MultiSelectListPreferenceDialogFragmentCompat import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceGroup @@ -36,7 +31,7 @@ class SettingsActivity : FragmentActivity() { preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE setPreferencesFromResource(R.xml.root_preferences, rootKey) - val disableTouchTimeoutPreference = findPreference("disableTouchTimeout")!! + val disableTouchTimeoutPreference = findPreference("disableTouchTimeout")!! val disableTouchDuringPenPreference = findPreference("disableTouchDuringPen")!! val disableTouchDuringHoverPreference = findPreference("disableTouchDuringHover")!! val disableTypesPreference = findPreference("disableTypes")!! @@ -52,10 +47,6 @@ class SettingsActivity : FragmentActivity() { true } } - disableTouchTimeoutPreference.setOnBindEditTextListener { - it.hint = "Time in seconds" - it.inputType = InputType.TYPE_CLASS_NUMBER - } disableTypesPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { parentFragmentManager.beginTransaction().add(R.id.settings, TypeSelectorFragment()).commit() > 0 } diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/TimeoutPreference.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/TimeoutPreference.kt new file mode 100644 index 0000000..b019681 --- /dev/null +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/TimeoutPreference.kt @@ -0,0 +1,46 @@ +package com.programminghoch10.MotionEventMod + +import android.app.AlertDialog +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.EditText +import android.widget.TextView +import androidx.preference.Preference + +class TimeoutPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs) { + val TAG = TimeoutPreference::class.simpleName + var defaultValue = 0f + + var savedValue: Float + get() = getPersistedFloat(defaultValue) + set(value) { + persistFloat(value) + } + + override fun setDefaultValue(defaultValue: Any?) { + require(defaultValue is Float) + this.defaultValue = defaultValue + super.setDefaultValue(defaultValue) + } + + override fun getSummary(): CharSequence { + return String.format(super.summary.toString(), savedValue) + } + + override fun onClick() { + require(sharedPreferences != null) + val inflater = LayoutInflater.from(context) + val dialogView = inflater.inflate(R.layout.timeoutpreference_dialog, null) + dialogView.findViewById(android.R.id.title).text = title + val editText = dialogView.findViewById(R.id.editText) + editText.setText(savedValue.toString()) + AlertDialog.Builder(context).apply { + setView(dialogView) + }.setPositiveButton(android.R.string.ok) { _, _ -> + val result = editText.text.toString().toFloatOrNull() ?: 0f + if (callChangeListener(result)) savedValue = result + notifyChanged() + }.setNegativeButton(android.R.string.cancel, null).show() + } +} diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt index d0f60f0..51a9b9c 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -1,5 +1,6 @@ package com.programminghoch10.MotionEventMod +import kotlin.math.roundToLong import android.view.MotionEvent import android.view.View import com.programminghoch10.MotionEventMod.BuildConfig.APPLICATION_ID @@ -16,16 +17,20 @@ class XposedHook : IXposedHookLoadPackage { val disableTouchDuringPen get() = sharedPreferences.getBoolean("disableTouchDuringPen", false) val disableTouchDuringHover get() = sharedPreferences.getBoolean("disableTouchDuringHover", false) val markAsHandled get() = sharedPreferences.getBoolean("markAsHandled", true) + val disableTouchTimeout get() = (sharedPreferences.getFloat("disableTouchTimeout", 0f) * 1000L).roundToLong() fun preventMotionEvent(param: MethodHookParam) = run { param.result = markAsHandled } private var isPenDown: Boolean = false private var isPenHovering: Boolean = false private var lastPenEventTimestamp: Long = 0L private var lastHoverEventTimestamp: Long = 0L + fun isInTimeout(m: Long): Boolean = System.currentTimeMillis() < m + disableTouchTimeout fun handleTouchEvent(event: MotionEvent, param: MethodHookParam) { if (disableTouchDuringPen && isPenDown) preventMotionEvent(param) if (disableTouchDuringHover && isPenHovering) preventMotionEvent(param) + if (disableTouchDuringPen && isInTimeout(lastPenEventTimestamp)) preventMotionEvent(param) + if (disableTouchDuringHover && isInTimeout(lastHoverEventTimestamp)) preventMotionEvent(param) } fun handleStylusEvent(event: MotionEvent, param: MethodHookParam) { @@ -36,7 +41,7 @@ class XposedHook : IXposedHookLoadPackage { lastPenEventTimestamp = System.currentTimeMillis() } - fun handleHoverEvent(event: MotionEvent, param: MethodHookParam) { + fun handleStylusHoverEvent(event: MotionEvent, param: MethodHookParam) { when (event.action) { MotionEvent.ACTION_HOVER_ENTER -> isPenHovering = true MotionEvent.ACTION_HOVER_EXIT -> isPenHovering = false @@ -46,6 +51,7 @@ class XposedHook : IXposedHookLoadPackage { override fun handleLoadPackage(lpparam: LoadPackageParam) { if (lpparam.packageName == "android") return + if (lpparam.packageName == APPLICATION_ID) return XposedHelpers.findAndHookMethod( View::class.java, @@ -54,9 +60,7 @@ class XposedHook : IXposedHookLoadPackage { object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as MotionEvent - val actionIndex = event.actionIndex - val pointerId = event.getPointerId(actionIndex) - val toolType = event.getToolType(pointerId) + val toolType = event.getToolType() when (toolType) { MotionEvent.TOOL_TYPE_UNKNOWN -> return MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER -> handleStylusEvent(event, param) @@ -74,9 +78,15 @@ class XposedHook : IXposedHookLoadPackage { object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as MotionEvent - handleHoverEvent(event, param) + when (event.getToolType()) { + MotionEvent.TOOL_TYPE_UNKNOWN -> return + MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER -> handleStylusHoverEvent(event, param) + } } }, ) } } + +fun MotionEvent.getPointerId(): Int = getPointerId(actionIndex) +fun MotionEvent.getToolType(): Int = getToolType(getPointerId()) diff --git a/MotionEventMod/src/main/res/layout/timeoutpreference_dialog.xml b/MotionEventMod/src/main/res/layout/timeoutpreference_dialog.xml new file mode 100644 index 0000000..edc6429 --- /dev/null +++ b/MotionEventMod/src/main/res/layout/timeoutpreference_dialog.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/MotionEventMod/src/main/res/values/strings.xml b/MotionEventMod/src/main/res/values/strings.xml index e3420a1..7021391 100644 --- a/MotionEventMod/src/main/res/values/strings.xml +++ b/MotionEventMod/src/main/res/values/strings.xml @@ -6,8 +6,8 @@ MotionEvents Test Disable Touch while Pen is in use Disable Touch while Pen is hovering - Disable Touch timeout - How long to wait after last pen interaction before the pen is re-enabled. + Disable Touch Timeout + How long to wait after last pen interaction before the pen is re-enabled.\nCurrently: %1$.1f seconds Replay Events after Timeout Replay MotionEvents which occurred during the disabled phase, if they continue after the timeout ended. Mark Disabled MotionEvents as Handled @@ -15,4 +15,5 @@ Tell Android the MotionEvents have not been handled. Disable MotionEvents by Type Test the module configuration + seconds diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml index 2147b41..edb0394 100644 --- a/MotionEventMod/src/main/res/xml/root_preferences.xml +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -11,8 +11,7 @@ app:key="disableTouchDuringHover" app:title="@string/disableTouchDuringHover_title" /> - Date: Wed, 23 Sep 2026 16:42:28 +0200 Subject: [PATCH 07/12] MotionEventMod: implement disable hover --- .../com/programminghoch10/MotionEventMod/XposedHook.kt | 2 ++ MotionEventMod/src/main/res/values/strings.xml | 2 ++ MotionEventMod/src/main/res/xml/root_preferences.xml | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt index 51a9b9c..a0a7408 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -18,6 +18,7 @@ class XposedHook : IXposedHookLoadPackage { val disableTouchDuringHover get() = sharedPreferences.getBoolean("disableTouchDuringHover", false) val markAsHandled get() = sharedPreferences.getBoolean("markAsHandled", true) val disableTouchTimeout get() = (sharedPreferences.getFloat("disableTouchTimeout", 0f) * 1000L).roundToLong() + val disableHover get() = sharedPreferences.getBoolean("disableHover", false) fun preventMotionEvent(param: MethodHookParam) = run { param.result = markAsHandled } private var isPenDown: Boolean = false @@ -78,6 +79,7 @@ class XposedHook : IXposedHookLoadPackage { object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as MotionEvent + if (disableHover) return preventMotionEvent(param) when (event.getToolType()) { MotionEvent.TOOL_TYPE_UNKNOWN -> return MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER -> handleStylusHoverEvent(event, param) diff --git a/MotionEventMod/src/main/res/values/strings.xml b/MotionEventMod/src/main/res/values/strings.xml index 7021391..2c755af 100644 --- a/MotionEventMod/src/main/res/values/strings.xml +++ b/MotionEventMod/src/main/res/values/strings.xml @@ -16,4 +16,6 @@ Disable MotionEvents by Type Test the module configuration seconds + Disable Hover + Disable Hover events completely. diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml index edb0394..b1ac56d 100644 --- a/MotionEventMod/src/main/res/xml/root_preferences.xml +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -32,6 +32,12 @@ app:summaryOff="@string/markAsHandled_summaryOff" app:title="@string/markAsHandled_title" /> + Date: Wed, 23 Sep 2026 18:42:59 +0200 Subject: [PATCH 08/12] MotionEventMod: implement disable by type --- MotionEventMod/src/main/AndroidManifest.xml | 1 + .../MotionEventMod/SettingsActivity.kt | 18 ++++++++++++------ .../MotionEventMod/XposedHook.kt | 13 +++++++++++++ .../src/main/res/xml/root_preferences.xml | 3 +-- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/MotionEventMod/src/main/AndroidManifest.xml b/MotionEventMod/src/main/AndroidManifest.xml index c547eb1..4007019 100644 --- a/MotionEventMod/src/main/AndroidManifest.xml +++ b/MotionEventMod/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ > 0) supportFragmentManager.popBackStack() + else finish() return true } @@ -34,7 +35,6 @@ class SettingsActivity : FragmentActivity() { val disableTouchTimeoutPreference = findPreference("disableTouchTimeout")!! val disableTouchDuringPenPreference = findPreference("disableTouchDuringPen")!! val disableTouchDuringHoverPreference = findPreference("disableTouchDuringHover")!! - val disableTypesPreference = findPreference("disableTypes")!! val testPreference = findPreference("test")!! fun recalculateDependencies() { @@ -47,9 +47,6 @@ class SettingsActivity : FragmentActivity() { true } } - disableTypesPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - parentFragmentManager.beginTransaction().add(R.id.settings, TypeSelectorFragment()).commit() > 0 - } testPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { val intent = Intent(context, InputTestActivity::class.java) requireActivity().startActivity(intent) @@ -60,7 +57,16 @@ class SettingsActivity : FragmentActivity() { class TypeSelectorFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - TODO("Not yet implemented") + preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME + preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE + preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()) + toolTypes.forEach { + val preference = SwitchPreference(requireContext()) + preference.key = toolTypeEnabledKey(it) + preference.title = it + preference.setDefaultValue(true) + preferenceScreen.addPreference(preference) + } } } } diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt index a0a7408..3ec6dc6 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -12,6 +12,11 @@ import de.robv.android.xposed.XSharedPreferences import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam +val toolTypeFields = MotionEvent::class.java.declaredFields.filter { it.name.startsWith("TOOL_TYPE_") && it.type == Int::class.java } +val toolTypes = toolTypeFields.map { it.name } +val toolTypeNames = toolTypeFields.associate { it.getInt(null) to it.name } +fun toolTypeEnabledKey(toolType: String): String = "${toolType.lowercase()}_enabled" + class XposedHook : IXposedHookLoadPackage { val sharedPreferences = XSharedPreferences(APPLICATION_ID, SHARED_PREFERENCES_NAME) val disableTouchDuringPen get() = sharedPreferences.getBoolean("disableTouchDuringPen", false) @@ -50,6 +55,12 @@ class XposedHook : IXposedHookLoadPackage { lastHoverEventTimestamp = System.currentTimeMillis() } + fun shouldDisableMotionEventByToolType(toolType: Int): Boolean { + val name = toolTypeNames[toolType] ?: return false + val key = toolTypeEnabledKey(name) + return !sharedPreferences.getBoolean(key, true) + } + override fun handleLoadPackage(lpparam: LoadPackageParam) { if (lpparam.packageName == "android") return if (lpparam.packageName == APPLICATION_ID) return @@ -62,6 +73,7 @@ class XposedHook : IXposedHookLoadPackage { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as MotionEvent val toolType = event.getToolType() + if (shouldDisableMotionEventByToolType(toolType)) return preventMotionEvent(param) when (toolType) { MotionEvent.TOOL_TYPE_UNKNOWN -> return MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER -> handleStylusEvent(event, param) @@ -79,6 +91,7 @@ class XposedHook : IXposedHookLoadPackage { object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as MotionEvent + if (shouldDisableMotionEventByToolType(event.getToolType())) return preventMotionEvent(param) if (disableHover) return preventMotionEvent(param) when (event.getToolType()) { MotionEvent.TOOL_TYPE_UNKNOWN -> return diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml index b1ac56d..a224e96 100644 --- a/MotionEventMod/src/main/res/xml/root_preferences.xml +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -39,8 +39,7 @@ app:title="@string/disableHover_title" /> Date: Thu, 24 Sep 2026 23:58:19 +0200 Subject: [PATCH 09/12] implement MotionEventMod replayOngoingEvents --- .../MotionEventMod/SettingsActivity.kt | 17 ++-- .../programminghoch10/MotionEventMod/Utils.kt | 22 +++++ .../MotionEventMod/XposedHook.kt | 84 ++++++++++++++++--- .../src/main/res/values/strings.xml | 4 +- .../src/main/res/xml/root_preferences.xml | 7 +- 5 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt index 5ab66fc..c5d9cbb 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt @@ -4,10 +4,8 @@ import android.os.Bundle import androidx.fragment.app.FragmentActivity import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import androidx.preference.PreferenceGroup import androidx.preference.SwitchPreference import androidx.preference.TwoStatePreference -import androidx.preference.children import com.programminghoch10.MotionEventMod.BuildConfig.SHARED_PREFERENCES_NAME class SettingsActivity : FragmentActivity() { @@ -35,18 +33,19 @@ class SettingsActivity : FragmentActivity() { val disableTouchTimeoutPreference = findPreference("disableTouchTimeout")!! val disableTouchDuringPenPreference = findPreference("disableTouchDuringPen")!! val disableTouchDuringHoverPreference = findPreference("disableTouchDuringHover")!! + val replayOngoingEventsPreference = findPreference("replayOngoingEvents")!! val testPreference = findPreference("test")!! fun recalculateDependencies() { - disableTouchTimeoutPreference.isEnabled = disableTouchDuringPenPreference.isChecked || disableTouchDuringHoverPreference.isChecked + disableTouchDuringHoverPreference.isEnabled = disableTouchDuringPenPreference.isEnabledAndChecked + disableTouchTimeoutPreference.isEnabled = + disableTouchDuringPenPreference.isEnabledAndChecked || disableTouchDuringHoverPreference.isEnabledAndChecked + replayOngoingEventsPreference.isEnabled = + disableTouchDuringPenPreference.isEnabledAndChecked || disableTouchDuringHoverPreference.isEnabledAndChecked } + preferenceManager.sharedPreferences!!.registerOnSharedPreferenceChangeListener { _, _ -> recalculateDependencies() } recalculateDependencies() - listOf(disableTouchDuringPenPreference, disableTouchDuringHoverPreference).forEach { - it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> - recalculateDependencies() - true - } - } + testPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { val intent = Intent(context, InputTestActivity::class.java) requireActivity().startActivity(intent) diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt new file mode 100644 index 0000000..b9988b5 --- /dev/null +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt @@ -0,0 +1,22 @@ +package com.programminghoch10.MotionEventMod + +import android.os.Build +import android.os.Parcel +import android.os.Parcelable + +// thanks https://farhanpatel.dev/index.php/2020/06/14/deep-clones-with-android-parcelable/ +// slightly modified for compatibility, extension functions and nullability +fun T.deepClone(): T { + var parcel = Parcel.obtain() + parcel.writeParcelable(this, 0) + parcel.setDataPosition(0) + val clone = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + parcel.readParcelable(this::class.java.classLoader, this::class.java) + } else { + @Suppress("DEPRECATION") parcel.readParcelable(this::class.java.classLoader) + } + // it is important to recycle parcel and free up resources once done + parcel.recycle() + require(clone != null) + return clone +} diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt index 3ec6dc6..43b2ba0 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -1,14 +1,18 @@ package com.programminghoch10.MotionEventMod +import java.lang.reflect.Method import kotlin.math.roundToLong +import android.util.Log import android.view.MotionEvent import android.view.View import com.programminghoch10.MotionEventMod.BuildConfig.APPLICATION_ID import com.programminghoch10.MotionEventMod.BuildConfig.SHARED_PREFERENCES_NAME +import com.programminghoch10.MotionEventMod.BuildConfig.TAG import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook.MethodHookParam import de.robv.android.xposed.XSharedPreferences +import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam @@ -20,39 +24,82 @@ fun toolTypeEnabledKey(toolType: String): String = "${toolType.lowercase()}_enab class XposedHook : IXposedHookLoadPackage { val sharedPreferences = XSharedPreferences(APPLICATION_ID, SHARED_PREFERENCES_NAME) val disableTouchDuringPen get() = sharedPreferences.getBoolean("disableTouchDuringPen", false) - val disableTouchDuringHover get() = sharedPreferences.getBoolean("disableTouchDuringHover", false) + val disableTouchDuringHover get() = disableTouchDuringPen && sharedPreferences.getBoolean("disableTouchDuringHover", false) val markAsHandled get() = sharedPreferences.getBoolean("markAsHandled", true) - val disableTouchTimeout get() = (sharedPreferences.getFloat("disableTouchTimeout", 0f) * 1000L).roundToLong() + val disableTouchTimeoutMs + get() = if (disableTouchDuringPen || disableTouchDuringHover) (sharedPreferences.getFloat("disableTouchTimeout", 0f) * 1000L).roundToLong() + else 0L val disableHover get() = sharedPreferences.getBoolean("disableHover", false) + val replayOngoingEvents get() = (disableTouchDuringPen || disableTouchDuringHover) && sharedPreferences.getBoolean("replayOngoingEvents", false) + fun preventMotionEvent(param: MethodHookParam) = run { param.result = markAsHandled } private var isPenDown: Boolean = false private var isPenHovering: Boolean = false private var lastPenEventTimestamp: Long = 0L private var lastHoverEventTimestamp: Long = 0L - fun isInTimeout(m: Long): Boolean = System.currentTimeMillis() < m + disableTouchTimeout + fun isInTimeout(compareTime: Long, eventTime: Long): Boolean = + disableTouchTimeoutMs > 0 && eventTime in compareTime..>() + + fun handleTouchEvent(event: MotionEvent, param: MethodHookParam, dispatchingView: View) { + val event = event.deepClone() + require(event.getToolType() == MotionEvent.TOOL_TYPE_FINGER) + var preventTouchEvent = shouldPreventTouchEvent(event.eventTime) + val pointerId = event.getPointerId() + if (preventTouchEvent) when (event.action) { + MotionEvent.ACTION_DOWN -> motionEventStorage[pointerId] = mutableListOf(event) + MotionEvent.ACTION_MOVE -> motionEventStorage[pointerId]?.add(event) + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> motionEventStorage.remove(pointerId) + } + if (!preventTouchEvent && event.action in listOf( + MotionEvent.ACTION_MOVE, + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL, + ) && motionEventStorage.contains(pointerId) + ) { + if (replayOngoingEvents) { + Log.d(TAG, "handleTouchEvent: replaying ongoing MotionEvents size=${motionEventStorage[pointerId]?.size}") + motionEventStorage[pointerId]?.forEach { dispatchingView.dispatchTouchEventUnhooked(it) } + motionEventStorage.remove(pointerId) + } else { + //Log.d(TAG, "handleTouchEvent: prevent because started before enabled") + preventTouchEvent = true + if (event.action == MotionEvent.ACTION_UP) motionEventStorage.remove(pointerId) + } + } + if (preventTouchEvent) preventMotionEvent(param) } fun handleStylusEvent(event: MotionEvent, param: MethodHookParam) { + require(event.getToolType() in listOf(MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER)) when (event.action) { MotionEvent.ACTION_DOWN -> isPenDown = true MotionEvent.ACTION_UP -> isPenDown = false } - lastPenEventTimestamp = System.currentTimeMillis() + lastPenEventTimestamp = event.eventTime } fun handleStylusHoverEvent(event: MotionEvent, param: MethodHookParam) { + require(event.getToolType() in listOf(MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER)) + require(event.action in listOf(MotionEvent.ACTION_HOVER_ENTER, MotionEvent.ACTION_HOVER_MOVE, MotionEvent.ACTION_HOVER_EXIT)) when (event.action) { MotionEvent.ACTION_HOVER_ENTER -> isPenHovering = true MotionEvent.ACTION_HOVER_EXIT -> isPenHovering = false } - lastHoverEventTimestamp = System.currentTimeMillis() + lastHoverEventTimestamp = event.eventTime } fun shouldDisableMotionEventByToolType(toolType: Int): Boolean { @@ -71,6 +118,7 @@ class XposedHook : IXposedHookLoadPackage { MotionEvent::class.java, object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { + val view = param.thisObject as View val event = param.args[0] as MotionEvent val toolType = event.getToolType() if (shouldDisableMotionEventByToolType(toolType)) return preventMotionEvent(param) @@ -78,7 +126,7 @@ class XposedHook : IXposedHookLoadPackage { MotionEvent.TOOL_TYPE_UNKNOWN -> return MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER -> handleStylusEvent(event, param) MotionEvent.TOOL_TYPE_MOUSE -> TODO("implement mouse") - MotionEvent.TOOL_TYPE_FINGER -> handleTouchEvent(event, param) + MotionEvent.TOOL_TYPE_FINGER -> handleTouchEvent(event, param, view) } } }, @@ -104,4 +152,16 @@ class XposedHook : IXposedHookLoadPackage { } fun MotionEvent.getPointerId(): Int = getPointerId(actionIndex) -fun MotionEvent.getToolType(): Int = getToolType(getPointerId()) +fun MotionEvent.getToolType(): Int = getToolType(actionIndex) + +private val dispatchTouchEventMethod: Method by lazy { + XposedHelpers.findMethodExact( + View::class.java, + "dispatchTouchEvent", + MotionEvent::class.java, + )!! +} + +fun View.dispatchTouchEventUnhooked(event: MotionEvent) { + XposedBridge.invokeOriginalMethod(dispatchTouchEventMethod, this, arrayOf(event)) +} diff --git a/MotionEventMod/src/main/res/values/strings.xml b/MotionEventMod/src/main/res/values/strings.xml index 2c755af..2aaaccd 100644 --- a/MotionEventMod/src/main/res/values/strings.xml +++ b/MotionEventMod/src/main/res/values/strings.xml @@ -8,8 +8,8 @@ Disable Touch while Pen is hovering Disable Touch Timeout How long to wait after last pen interaction before the pen is re-enabled.\nCurrently: %1$.1f seconds - Replay Events after Timeout - Replay MotionEvents which occurred during the disabled phase, if they continue after the timeout ended. + Replay Ongoing Events + Replay Ongoing MotionEvents which occurred during the disabled phase, if they are still active when re-enabled. Mark Disabled MotionEvents as Handled Tell Android the MotionEvents have been handled. Tell Android the MotionEvents have not been handled. diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml index a224e96..392b8bb 100644 --- a/MotionEventMod/src/main/res/xml/root_preferences.xml +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -18,11 +18,10 @@ app:title="@string/disableTouchTimeout_title" /> Date: Fri, 25 Sep 2026 00:30:49 +0200 Subject: [PATCH 10/12] implement more MotionEventMod disable types --- .../MotionEventMod/SettingsActivity.kt | 4 ++-- .../programminghoch10/MotionEventMod/Utils.kt | 10 +++++++++ .../MotionEventMod/XposedHook.kt | 22 +++++++++---------- .../src/main/res/values/strings.xml | 1 + .../src/main/res/xml/root_preferences.xml | 1 + 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt index c5d9cbb..2f6d055 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt @@ -59,9 +59,9 @@ class SettingsActivity : FragmentActivity() { preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()) - toolTypes.forEach { + (toolTypeFields + sourceClassFields + actionTypeFields).map { it.name }.forEach { val preference = SwitchPreference(requireContext()) - preference.key = toolTypeEnabledKey(it) + preference.key = typeEnabledKey(it) preference.title = it preference.setDefaultValue(true) preferenceScreen.addPreference(preference) diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt index b9988b5..5f0c826 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt @@ -3,6 +3,16 @@ package com.programminghoch10.MotionEventMod import android.os.Build import android.os.Parcel import android.os.Parcelable +import android.view.InputDevice +import android.view.MotionEvent + + +val toolTypeFields = MotionEvent::class.java.declaredFields.filter { it.name.startsWith("TOOL_TYPE_") && it.type == Int::class.java } +val sourceClassFields = InputDevice::class.java.declaredFields.filter { it.name.startsWith("SOURCE_CLASS_") && it.type == Int::class.java } + .filter { it.name != "SOURCE_CLASS_MASK" } +val actionTypeFields = + MotionEvent::class.java.declaredFields.filter { it.name.startsWith("ACTION_") && it.type == Int::class.java }.filter { it.name != "ACTION_MASK" } +fun typeEnabledKey(fieldName: String): String = "${fieldName.lowercase()}_enabled" // thanks https://farhanpatel.dev/index.php/2020/06/14/deep-clones-with-android-parcelable/ // slightly modified for compatibility, extension functions and nullability diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt index 43b2ba0..c96ddbd 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -16,11 +16,6 @@ import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam -val toolTypeFields = MotionEvent::class.java.declaredFields.filter { it.name.startsWith("TOOL_TYPE_") && it.type == Int::class.java } -val toolTypes = toolTypeFields.map { it.name } -val toolTypeNames = toolTypeFields.associate { it.getInt(null) to it.name } -fun toolTypeEnabledKey(toolType: String): String = "${toolType.lowercase()}_enabled" - class XposedHook : IXposedHookLoadPackage { val sharedPreferences = XSharedPreferences(APPLICATION_ID, SHARED_PREFERENCES_NAME) val disableTouchDuringPen get() = sharedPreferences.getBoolean("disableTouchDuringPen", false) @@ -102,10 +97,15 @@ class XposedHook : IXposedHookLoadPackage { lastHoverEventTimestamp = event.eventTime } - fun shouldDisableMotionEventByToolType(toolType: Int): Boolean { - val name = toolTypeNames[toolType] ?: return false - val key = toolTypeEnabledKey(name) - return !sharedPreferences.getBoolean(key, true) + fun shouldDisableMotionEventByType(event: MotionEvent): Boolean { + return listOfNotNull( + toolTypeFields to event.getToolType(), + sourceClassFields to event.source, + actionTypeFields to event.action, + ).map { it.first.associate { field -> field.getInt(null) to field.name } to it.second } + .mapNotNull { it.first[it.second] } + .map(::typeEnabledKey) + .any { !sharedPreferences.getBoolean(it, true) } } override fun handleLoadPackage(lpparam: LoadPackageParam) { @@ -121,7 +121,7 @@ class XposedHook : IXposedHookLoadPackage { val view = param.thisObject as View val event = param.args[0] as MotionEvent val toolType = event.getToolType() - if (shouldDisableMotionEventByToolType(toolType)) return preventMotionEvent(param) + if (shouldDisableMotionEventByType(event)) return preventMotionEvent(param) when (toolType) { MotionEvent.TOOL_TYPE_UNKNOWN -> return MotionEvent.TOOL_TYPE_STYLUS, MotionEvent.TOOL_TYPE_ERASER -> handleStylusEvent(event, param) @@ -139,7 +139,7 @@ class XposedHook : IXposedHookLoadPackage { object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as MotionEvent - if (shouldDisableMotionEventByToolType(event.getToolType())) return preventMotionEvent(param) + if (shouldDisableMotionEventByType(event)) return preventMotionEvent(param) if (disableHover) return preventMotionEvent(param) when (event.getToolType()) { MotionEvent.TOOL_TYPE_UNKNOWN -> return diff --git a/MotionEventMod/src/main/res/values/strings.xml b/MotionEventMod/src/main/res/values/strings.xml index 2aaaccd..ec83276 100644 --- a/MotionEventMod/src/main/res/values/strings.xml +++ b/MotionEventMod/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Tell Android the MotionEvents have been handled. Tell Android the MotionEvents have not been handled. Disable MotionEvents by Type + This is advanced functionality may break apps and other functionality of this module. Test the module configuration seconds Disable Hover diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml index 392b8bb..189c04b 100644 --- a/MotionEventMod/src/main/res/xml/root_preferences.xml +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -41,6 +41,7 @@ app:fragment="com.programminghoch10.MotionEventMod.SettingsActivity$TypeSelectorFragment" app:iconSpaceReserved="false" app:key="disableTypes" + app:summary="@string/disableTypes_summary" app:title="@string/disableTypes_title" /> Date: Fri, 25 Sep 2026 01:07:25 +0200 Subject: [PATCH 11/12] MotionEventMod: enable filtering buttons --- .../MotionEventMod/SettingsActivity.kt | 2 +- .../programminghoch10/MotionEventMod/Utils.kt | 2 ++ .../MotionEventMod/XposedHook.kt | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt index 2f6d055..159433e 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/SettingsActivity.kt @@ -59,7 +59,7 @@ class SettingsActivity : FragmentActivity() { preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE preferenceScreen = preferenceManager.createPreferenceScreen(requireContext()) - (toolTypeFields + sourceClassFields + actionTypeFields).map { it.name }.forEach { + (toolTypeFields + sourceClassFields + actionTypeFields + buttonFields).map { it.name }.forEach { val preference = SwitchPreference(requireContext()) preference.key = typeEnabledKey(it) preference.title = it diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt index 5f0c826..726f1c5 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/Utils.kt @@ -12,6 +12,8 @@ val sourceClassFields = InputDevice::class.java.declaredFields.filter { it.name. .filter { it.name != "SOURCE_CLASS_MASK" } val actionTypeFields = MotionEvent::class.java.declaredFields.filter { it.name.startsWith("ACTION_") && it.type == Int::class.java }.filter { it.name != "ACTION_MASK" } +val buttonFields = MotionEvent::class.java.declaredFields.filter { it.name.startsWith("BUTTON_") && it.type == Int::class.java } +val buttonFieldsMap = buttonFields.associate { field -> field.name to field.getInt(null) } fun typeEnabledKey(fieldName: String): String = "${fieldName.lowercase()}_enabled" // thanks https://farhanpatel.dev/index.php/2020/06/14/deep-clones-with-android-parcelable/ diff --git a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt index c96ddbd..4369cfd 100644 --- a/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt +++ b/MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/XposedHook.kt @@ -148,6 +148,23 @@ class XposedHook : IXposedHookLoadPackage { } }, ) + + XposedHelpers.findAndHookMethod( + MotionEvent::class.java, + "getButtonState", + object : XC_MethodHook() { + override fun afterHookedMethod(param: XC_MethodHook.MethodHookParam) { + var buttons = param.result as Int + buttonFields.asSequence() + .map { it.name } + .filterNot { sharedPreferences.getBoolean(typeEnabledKey(it), true) } + .map { buttonFieldsMap[it]!! } + .map { it.inv() } + .forEach { buttons = buttons and it } + param.result = buttons + } + }, + ) } } From fa8398e409b8fa89fb228b4fda1fd17c2b6053d6 Mon Sep 17 00:00:00 2001 From: programminghoch10 <16062290+programminghoch10@users.noreply.github.com> Date: Fri, 25 Sep 2026 01:31:47 +0200 Subject: [PATCH 12/12] MotionEventMod: purge test activity it was decided its to much effort to implement --- MotionEventMod/src/main/AndroidManifest.xml | 8 -------- .../MotionEventMod/InputTestActivity.kt | 17 ----------------- .../MotionEventMod/SettingsActivity.kt | 8 -------- MotionEventMod/src/main/res/values/strings.xml | 2 -- .../src/main/res/xml/root_preferences.xml | 8 -------- 5 files changed, 43 deletions(-) delete mode 100644 MotionEventMod/src/main/kotlin/com/programminghoch10/MotionEventMod/InputTestActivity.kt diff --git a/MotionEventMod/src/main/AndroidManifest.xml b/MotionEventMod/src/main/AndroidManifest.xml index 4007019..0434c65 100644 --- a/MotionEventMod/src/main/AndroidManifest.xml +++ b/MotionEventMod/src/main/AndroidManifest.xml @@ -19,14 +19,6 @@ - ("disableTouchDuringPen")!! val disableTouchDuringHoverPreference = findPreference("disableTouchDuringHover")!! val replayOngoingEventsPreference = findPreference("replayOngoingEvents")!! - val testPreference = findPreference("test")!! fun recalculateDependencies() { disableTouchDuringHoverPreference.isEnabled = disableTouchDuringPenPreference.isEnabledAndChecked @@ -45,12 +43,6 @@ class SettingsActivity : FragmentActivity() { } preferenceManager.sharedPreferences!!.registerOnSharedPreferenceChangeListener { _, _ -> recalculateDependencies() } recalculateDependencies() - - testPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { - val intent = Intent(context, InputTestActivity::class.java) - requireActivity().startActivity(intent) - true - } } } diff --git a/MotionEventMod/src/main/res/values/strings.xml b/MotionEventMod/src/main/res/values/strings.xml index ec83276..2f1d6bb 100644 --- a/MotionEventMod/src/main/res/values/strings.xml +++ b/MotionEventMod/src/main/res/values/strings.xml @@ -3,7 +3,6 @@ MotionEventMod Configuration MotionEventMod Customize MotionEvent functionality - MotionEvents Test Disable Touch while Pen is in use Disable Touch while Pen is hovering Disable Touch Timeout @@ -15,7 +14,6 @@ Tell Android the MotionEvents have not been handled. Disable MotionEvents by Type This is advanced functionality may break apps and other functionality of this module. - Test the module configuration seconds Disable Hover Disable Hover events completely. diff --git a/MotionEventMod/src/main/res/xml/root_preferences.xml b/MotionEventMod/src/main/res/xml/root_preferences.xml index 189c04b..ed6494e 100644 --- a/MotionEventMod/src/main/res/xml/root_preferences.xml +++ b/MotionEventMod/src/main/res/xml/root_preferences.xml @@ -43,13 +43,5 @@ app:key="disableTypes" app:summary="@string/disableTypes_summary" app:title="@string/disableTypes_title" - /> -