From 0ec422debac9039155168dd3c70f0d10c7201a65 Mon Sep 17 00:00:00 2001
From: iidamie <127737368+iidamie@users.noreply.github.com>
Date: Sat, 8 Aug 2026 19:49:22 +0800
Subject: [PATCH 1/2] fix(android): add input broadcast APIs
---
.../Native/AndroidInput.cs | 44 ++-
.../Native/InputEvents.cs | 310 ++++++++++++++++++
.../UI/ImGuiInputHandler.cs | 107 ++----
.../RuntimeAbstractions/RuntimeManager.cs | 27 ++
4 files changed, 403 insertions(+), 85 deletions(-)
create mode 100644 StArray.ModManager.Android/Native/InputEvents.cs
diff --git a/StArray.ModManager.Android/Native/AndroidInput.cs b/StArray.ModManager.Android/Native/AndroidInput.cs
index 3699bca..d67c91d 100644
--- a/StArray.ModManager.Android/Native/AndroidInput.cs
+++ b/StArray.ModManager.Android/Native/AndroidInput.cs
@@ -33,7 +33,7 @@ public enum KeyAction
Multiple = 2,
}
- /// 触摸动作(主动作需用 ActionMask 提取)
+ /// 触摸动作(主动作需先去除指针索引位)
public enum MotionAction
{
Down = 0,
@@ -43,11 +43,12 @@ public enum MotionAction
Outside = 4,
PointerDown = 5,
PointerUp = 6,
- HoverEnter = 7,
- HoverMove = 8,
- HoverExit = 9,
- ButtonPress = 10,
- ButtonRelease = 11,
+ HoverMove = 7,
+ Scroll = 8,
+ HoverEnter = 9,
+ HoverExit = 10,
+ ButtonPress = 11,
+ ButtonRelease = 12,
}
/// Meta 键状态
@@ -71,6 +72,13 @@ public static class MotionMask
public const int PointerIndexShift = 8;
}
+ // Keep integer helpers available to the timestamp broadcast path. The raw
+ // action is read only once per native event, avoiding repeated P/Invoke
+ // calls on the Android input thread.
+ public const int ActionMask = MotionMask.Action;
+ public const int ActionPointerIndexMask = MotionMask.PointerIndex;
+ public const int ActionPointerIndexShift = MotionMask.PointerIndexShift;
+
// ======================== 不透明句柄结构体 ========================
/// 不透明输入事件句柄
@@ -156,21 +164,39 @@ int handled
[DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
public static extern int AMotionEvent_getPointerId(IntPtr ev, int pointerIndex);
+ ///
+ /// 事件发生时刻,单位纳秒,时钟源为 CLOCK_MONOTONIC。
+ /// 该时间戳独立于渲染帧,可用于还原触摸真实发生时刻。
+ ///
+ [DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
+ public static extern long AMotionEvent_getEventTime(IntPtr ev);
+
+ [DllImport(Lib, CallingConvention = CallingConvention.Cdecl)]
+ public static extern long AMotionEvent_getDownTime(IntPtr ev);
+
// ======================== 辅助方法(扩展风格) ========================
+ /// 从原始动作值取出主动作。
+ public static MotionAction GetMainAction(int rawAction)
+ => (MotionAction)(rawAction & ActionMask);
+
+ /// 从原始动作值取出指针索引。
+ public static int GetPointerIndex(int rawAction)
+ => (rawAction & ActionPointerIndexMask) >> ActionPointerIndexShift;
+
/// 获取触摸事件的主动作(去除指针索引)
public static MotionAction GetMainAction(this IntPtr ev)
{
if (AInputEvent_getType(ev) != EventType.Motion)
throw new InvalidOperationException("事件不是 Motion 类型");
int raw = AMotionEvent_getAction(ev);
- return (MotionAction)(raw & MotionMask.Action);
+ return GetMainAction(raw);
}
/// 获取触摸事件的指针索引(用于多点触控)
public static int GetPointerIndex(this IntPtr ev)
{
int raw = AMotionEvent_getAction(ev);
- return (raw & MotionMask.PointerIndex) >> MotionMask.PointerIndexShift;
+ return GetPointerIndex(raw);
}
-}
\ No newline at end of file
+}
diff --git a/StArray.ModManager.Android/Native/InputEvents.cs b/StArray.ModManager.Android/Native/InputEvents.cs
new file mode 100644
index 0000000..4dca2df
--- /dev/null
+++ b/StArray.ModManager.Android/Native/InputEvents.cs
@@ -0,0 +1,310 @@
+using System.Diagnostics;
+using StArray.ModManager.Manager;
+
+namespace StArray.ModManager.Android.Native;
+
+/// 一次完整触摸事件的快照。所有字段在广播时即已从原生事件读出。
+/// 主动作,已去除指针索引
+/// 指针索引
+/// 稳定的指针 ID,比索引更适合跨事件跟踪多指输入
+/// 事件发生时刻,纳秒,时钟源为 CLOCK_MONOTONIC
+/// 触点 X 坐标(像素)
+/// 触点 Y 坐标(像素)
+public readonly record struct TouchEventInfo(
+ AndroidInput.MotionAction Action,
+ int PointerIndex,
+ int PointerId,
+ long EventTimeNanos,
+ float X,
+ float Y)
+{
+ // Source compatibility for mods built against the first broadcast API.
+ public TouchEventInfo(
+ AndroidInput.MotionAction action,
+ int pointerIndex,
+ long eventTimeNanos,
+ float x,
+ float y)
+ : this(action, pointerIndex, -1, eventTimeNanos, x, y)
+ {
+ }
+}
+
+///
+/// 只包含异步输入所需字段的原始触摸快照。
+///
+///
+/// 该事件不读取坐标,且只广播 Down/Up/Cancel。高 KPS 时订阅方不需要为 Move
+/// 事件读取坐标或创建完整手势快照。
+///
+public readonly record struct TouchTimestampInfo(
+ AndroidInput.MotionAction Action,
+ int PointerId,
+ long EventTimeNanos);
+
+///
+/// 输入事件广播点。原生输入 Hook 只负责解析一次事件,订阅方在自己的队列中异步处理。
+///
+///
+/// 回调运行在 Android 输入分发线程,不是 Unity 主线程。订阅方只能做廉价的值类型快照
+/// 和入队操作,不能访问 Unity 对象或执行 IL2CPP 游戏逻辑。
+///
+public static class InputEvents
+{
+ private const string LogTag = nameof(InputEvents);
+ private const long DuplicateWindowMilliseconds = 8L;
+
+ private static Action? s_onTouch;
+ private static Action? s_onTouchTimestamp;
+ private static int s_touchSubscriberCount;
+ private static int s_touchTimestampSubscriberCount;
+ private static int s_faultLogged;
+
+ private static readonly object DedupLock = new();
+ private static int s_lastRawAction;
+ private static int s_lastPointerIndex;
+ private static int s_lastPointerCount;
+ private static int s_lastPointerId;
+ private static long s_lastEventTimeNanos;
+ private static long s_lastDispatchTicks;
+
+ /// 是否已有任一类订阅者。
+ public static bool HasSubscribers =>
+ Volatile.Read(ref s_touchSubscriberCount) > 0
+ || Volatile.Read(ref s_touchTimestampSubscriberCount) > 0;
+
+ /// 完整触摸事件广播,保留坐标和 Move 事件。
+ public static event Action? OnTouch
+ {
+ add
+ {
+ if (value == null) return;
+ lock (DedupLock)
+ {
+ s_onTouch += value;
+ Interlocked.Increment(ref s_touchSubscriberCount);
+ }
+ }
+ remove
+ {
+ if (value == null) return;
+ lock (DedupLock)
+ {
+ s_onTouch -= value;
+ Interlocked.Decrement(ref s_touchSubscriberCount);
+ }
+ }
+ }
+
+ ///
+ /// 异步输入时间戳快速广播,只处理 Down、PointerDown、Up、PointerUp 和 Cancel。
+ ///
+ public static event Action? OnTouchTimestamp
+ {
+ add
+ {
+ if (value == null) return;
+ lock (DedupLock)
+ {
+ s_onTouchTimestamp += value;
+ Interlocked.Increment(ref s_touchTimestampSubscriberCount);
+ }
+ }
+ remove
+ {
+ if (value == null) return;
+ lock (DedupLock)
+ {
+ s_onTouchTimestamp -= value;
+ Interlocked.Decrement(ref s_touchTimestampSubscriberCount);
+ }
+ }
+ }
+
+ ///
+ /// 从原生 AInputEvent 解析并广播。输入事件只在这里读取一次,避免每个 Mod 重复访问
+ /// 原生对象;重复的同一事件只在广播层过滤一次。
+ ///
+ internal static void RaiseFrom(nint inputEvent)
+ {
+ Action? handlers;
+ Action? timestampHandlers;
+ lock (DedupLock)
+ {
+ handlers = s_onTouch;
+ timestampHandlers = s_onTouchTimestamp;
+ }
+
+ if ((handlers == null && timestampHandlers == null) || inputEvent == 0)
+ return;
+
+ try
+ {
+ if (AndroidInput.AInputEvent_getType(inputEvent) != AndroidInput.EventType.Motion)
+ return;
+
+ int rawAction = AndroidInput.AMotionEvent_getAction(inputEvent);
+ AndroidInput.MotionAction action = AndroidInput.GetMainAction(rawAction);
+ int pointerIndex = AndroidInput.GetPointerIndex(rawAction);
+ bool timestampAction = action is AndroidInput.MotionAction.Down
+ or AndroidInput.MotionAction.PointerDown
+ or AndroidInput.MotionAction.Up
+ or AndroidInput.MotionAction.PointerUp
+ or AndroidInput.MotionAction.Cancel;
+
+ // AsyncInput subscribes only to the timestamp channel. Do not
+ // inspect Move coordinates, pointer IDs, or pointer counts on the
+ // hot path when no full-gesture subscriber exists.
+ if (!timestampAction && handlers == null)
+ return;
+
+ int pointerCount = AndroidInput.AMotionEvent_getPointerCount(inputEvent);
+ long eventTimeNanos = AndroidInput.AMotionEvent_getEventTime(inputEvent);
+ int pointerId = action == AndroidInput.MotionAction.Cancel
+ ? -1
+ : AndroidInput.AMotionEvent_getPointerId(inputEvent, pointerIndex);
+
+ if (IsDuplicate(
+ rawAction,
+ pointerIndex,
+ pointerCount,
+ pointerId,
+ eventTimeNanos))
+ return;
+
+ if (timestampHandlers != null && timestampAction)
+ {
+ DispatchTimestampHandlers(
+ timestampHandlers,
+ new TouchTimestampInfo(action, pointerId, eventTimeNanos));
+ }
+
+ if (handlers == null)
+ return;
+
+ TouchEventInfo info = new(
+ action,
+ pointerIndex,
+ pointerId,
+ eventTimeNanos,
+ AndroidInput.AMotionEvent_getX(inputEvent, pointerIndex),
+ AndroidInput.AMotionEvent_getY(inputEvent, pointerIndex));
+ DispatchTouchHandlers(handlers, info);
+ }
+ catch (Exception exception)
+ {
+ LogOnce($"Failed to read native input event: {exception}");
+ }
+ }
+
+ private static bool IsDuplicate(
+ int rawAction,
+ int pointerIndex,
+ int pointerCount,
+ int pointerId,
+ long eventTimeNanos)
+ {
+ long now = Stopwatch.GetTimestamp();
+ long windowTicks = Math.Max(
+ 1L,
+ Stopwatch.Frequency * DuplicateWindowMilliseconds / 1000L);
+
+ lock (DedupLock)
+ {
+ long elapsed = now - s_lastDispatchTicks;
+ bool sameEventPayload = s_lastRawAction == rawAction
+ && s_lastPointerIndex == pointerIndex
+ && s_lastPointerCount == pointerCount
+ && s_lastPointerId == pointerId
+ && s_lastEventTimeNanos == eventTimeNanos;
+ bool duplicate = sameEventPayload
+ && elapsed >= 0L
+ && elapsed <= windowTicks;
+
+ if (duplicate)
+ return true;
+
+ s_lastRawAction = rawAction;
+ s_lastPointerIndex = pointerIndex;
+ s_lastPointerCount = pointerCount;
+ s_lastPointerId = pointerId;
+ s_lastEventTimeNanos = eventTimeNanos;
+ s_lastDispatchTicks = now;
+ return false;
+ }
+ }
+
+ private static void DispatchTimestampHandlers(
+ Action handlers,
+ TouchTimestampInfo info)
+ {
+ if (Volatile.Read(ref s_touchTimestampSubscriberCount) == 1)
+ {
+ try
+ {
+ handlers(info);
+ }
+ catch (Exception exception)
+ {
+ LogOnce($"Touch timestamp subscriber threw: {exception}");
+ }
+ return;
+ }
+
+ foreach (Delegate handler in handlers.GetInvocationList())
+ {
+ try
+ {
+ ((Action)handler)(info);
+ }
+ catch (Exception exception)
+ {
+ LogOnce($"Touch timestamp subscriber threw: {exception}");
+ }
+ }
+ }
+
+ private static void DispatchTouchHandlers(
+ Action handlers,
+ TouchEventInfo info)
+ {
+ if (Volatile.Read(ref s_touchSubscriberCount) == 1)
+ {
+ try
+ {
+ handlers(info);
+ }
+ catch (Exception exception)
+ {
+ LogOnce($"Touch event subscriber threw: {exception}");
+ }
+ return;
+ }
+
+ foreach (Delegate handler in handlers.GetInvocationList())
+ {
+ try
+ {
+ ((Action)handler)(info);
+ }
+ catch (Exception exception)
+ {
+ LogOnce($"Touch event subscriber threw: {exception}");
+ }
+ }
+ }
+
+ private static void LogOnce(string message)
+ {
+ if (Interlocked.Exchange(ref s_faultLogged, 1) != 0)
+ return;
+ try
+ {
+ Logger.Error(LogTag, $"{message} (further occurrences suppressed)");
+ }
+ catch
+ {
+ // Never allow an exception to escape back through the native input stack.
+ }
+ }
+}
diff --git a/StArray.ModManager.Android/UI/ImGuiInputHandler.cs b/StArray.ModManager.Android/UI/ImGuiInputHandler.cs
index 5cd20ae..10ca52d 100644
--- a/StArray.ModManager.Android/UI/ImGuiInputHandler.cs
+++ b/StArray.ModManager.Android/UI/ImGuiInputHandler.cs
@@ -1,4 +1,3 @@
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ImGuiNET;
using StArray.ModManager.Android.Native;
@@ -68,86 +67,42 @@ public static void InstallInputHooks()
IsInitialized = true;
}
- /*
- /// 触摸事件 Hook 回调
- [NativeHook("libinput.so","_ZN7android13InputConsumer14consumeSamplesEPNS_26InputEventFactoryInterfaceERNS0_5BatchEmPjPPNS_10InputEventE")]
- public unsafe static long OnConsumeSamples(void* thiz,void* factory, IntPtr batch,
- ulong count, uint* outSeq, void** outEvent)
+ /// 在 Android 输入消费完成后读取本次输出事件。
+ [NativeHook("libinput.so", "_ZN7android13InputConsumer7consumeEPNS_26InputEventFactoryInterfaceEblPjPPNS_10InputEventE",
+ Convention = CallingConvention.Cdecl)]
+ public unsafe static int OnConsume(
+ void* thiz,
+ void* factory,
+ bool consumeBatches,
+ long frameTime,
+ uint* outSeq,
+ void** outEvent)
{
- var result = OnConsumeSamplesOriginal(thiz,factory, batch, count, outSeq, outEvent);
- if (IsInitialized && *outEvent != null) ImGuiImplAndroid.HandleInputEvent(new IntPtr(*outEvent));
- return result;
- }
-
- [NativeHook("libinput.so","_ZN7android13InputConsumer7consumeEPNS_26InputEventFactoryInterfaceEblPjPPNS_10InputEventE")]
- public unsafe static long OnConsume(void* thiz, void* factory, bool consumeBatches, ulong frameTime, uint* outSeq, void** outEvent)
- {
- var result = OnConsumeOriginal(thiz, factory, consumeBatches, frameTime, outSeq, outEvent);
- if (IsInitialized && *outEvent != null) ImGuiImplAndroid.HandleInputEvent(new IntPtr(*outEvent));
- return result;
- }*/
-
- [NativeHook("GetInitializeMotionEventAddress")]
- public unsafe static bool OnInitializeMotionEvent(void* @event, void* message)
- {
- var result = OnInitializeMotionEventOriginal(@event, message);
- var x = AndroidInput.AMotionEvent_getX(new(@event), 0);
- var y = AndroidInput.AMotionEvent_getY(new(@event), 0);
- ImGuiImplAndroid.HandleInputEvent(new IntPtr(@event));
+ var result = OnConsumeOriginal(
+ thiz,
+ factory,
+ consumeBatches,
+ frameTime,
+ outSeq,
+ outEvent);
+ if (outEvent != null && *outEvent != null)
+ DispatchInputEvent(new IntPtr(*outEvent));
return result;
}
- private static nint GetInitializeMotionEventAddress()
+ ///
+ /// 把一个原生输入事件同时送往 ImGui 和 订阅方。
+ ///
+ ///
+ /// 输入广播不依赖 ImGui 上下文是否已经初始化;这样异步输入 Mod 可以在
+ /// 菜单、暂停和非游玩阶段继续收到原始事件并自行决定是否消费。
+ ///
+ private static void DispatchInputEvent(IntPtr inputEvent)
{
- byte?[] sig = NativeFuncResolver.ParseHexPattern(
- "e8 0f 19 fc fd 7b 01 a9 fc 6f 02 a9 fa 67 03 a9 " +
- "f8 5f 04 a9 f6 57 05 a9 f4 4f 06 a9 fd 43 00 91");
- var r = new NativeFuncResolver("/system/lib64/libinput.so");
- long rva = r.FindSymbolRva("_ZN7android13InputConsumer21initializeMotionEventEPNS_11MotionEventEPKNS_12InputMessageE");
- try
- {
- if (rva < 0)
- {
- var text = r.TextBytes;
- long textAddr = r.TextBaseAddress;
- int pos = 0;
- while ((pos = NativeFuncResolver.Search(text, sig, pos)) >= 0)
- {
- int ctxEnd = Math.Min(pos + 60, text.Length);
- bool hasMrs = false, hasLdr = false;
- for (int j = pos + 32; j <= ctxEnd - 4; j += 4)
- {
- int inst = BitConverter.ToInt32(text.AsSpan(j));
- if (!hasMrs && (inst & 0xffffffe0) == 0xd53bd040)
- hasMrs = true;
- if (!hasLdr && (inst & 0xffe003e0) == 0xb9400020)
- {
- int imm12 = (inst >> 10) & 0xfff;
- if (imm12 * 4 == 0xc) hasLdr = true;
- }
- if (hasMrs && hasLdr) break;
- }
- if (hasMrs && hasLdr)
- {
- rva = textAddr + pos;
- if (pos >= 4)
- {
- int prev = BitConverter.ToInt32(text.AsSpan(pos - 4));
- if (prev == unchecked((int)0xd503233f)) rva -= 4;
- }
- break;
- }
- pos++;
- }
- if (rva < 0) throw new KeyNotFoundException("initializeMotionEvent not found by signature.");
- }
- }
- catch (Exception ex)
- {
- Logger.Error(nameof(ImGuiInputHandler), ex.ToString());
- }
- r.Load();
- return r.GetFuncPtr(rva);
+ if (InputEvents.HasSubscribers)
+ InputEvents.RaiseFrom(inputEvent);
+ if (IsInitialized)
+ ImGuiImplAndroid.HandleInputEvent(inputEvent);
}
private static JavaClass? s_utilsClass;
diff --git a/StArray.ModManager/RuntimeAbstractions/RuntimeManager.cs b/StArray.ModManager/RuntimeAbstractions/RuntimeManager.cs
index 9e72119..eb65f63 100644
--- a/StArray.ModManager/RuntimeAbstractions/RuntimeManager.cs
+++ b/StArray.ModManager/RuntimeAbstractions/RuntimeManager.cs
@@ -72,6 +72,33 @@ public static RuntimeBackend Detect()
return null;
}
+ ///
+ /// 从运行时对象取得其真实类型。
+ ///
+ ///
+ /// 泛型容器只有在对象实例创建后才能拿到具体方法表。调用方可缓存返回类型的
+ /// Add/Clear 等方法,避免每次事件通过字符串重新解析。
+ ///
+ public static IRuntimeClass? GetObjectClass(nint objectPtr)
+ {
+ if (objectPtr == 0)
+ return null;
+
+ if (IsIl2Cpp)
+ {
+ nint klass = Il2CppFunctions.il2cpp_object_get_class(objectPtr);
+ return klass == 0 ? null : new Il2CppClass(klass);
+ }
+
+ if (IsMono)
+ {
+ nint klass = MonoFunctions.MonoObjectGetClass(objectPtr);
+ return klass == 0 ? null : new MonoClass(klass);
+ }
+
+ return null;
+ }
+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
From d95b75aa11fea82a148267dbe4f49a09ef8a6f0f Mon Sep 17 00:00:00 2001
From: iidamie <127737368+iidamie@users.noreply.github.com>
Date: Sat, 8 Aug 2026 22:21:18 +0800
Subject: [PATCH 2/2] fix(android): preserve existing input hook
---
.../UI/ImGuiInputHandler.cs | 109 +++++++++++++-----
1 file changed, 78 insertions(+), 31 deletions(-)
diff --git a/StArray.ModManager.Android/UI/ImGuiInputHandler.cs b/StArray.ModManager.Android/UI/ImGuiInputHandler.cs
index 10ca52d..2eb0cd4 100644
--- a/StArray.ModManager.Android/UI/ImGuiInputHandler.cs
+++ b/StArray.ModManager.Android/UI/ImGuiInputHandler.cs
@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ImGuiNET;
using StArray.ModManager.Android.Native;
@@ -67,42 +68,88 @@ public static void InstallInputHooks()
IsInitialized = true;
}
- /// 在 Android 输入消费完成后读取本次输出事件。
- [NativeHook("libinput.so", "_ZN7android13InputConsumer7consumeEPNS_26InputEventFactoryInterfaceEblPjPPNS_10InputEventE",
- Convention = CallingConvention.Cdecl)]
- public unsafe static int OnConsume(
- void* thiz,
- void* factory,
- bool consumeBatches,
- long frameTime,
- uint* outSeq,
- void** outEvent)
+ /*
+ /// 触摸事件 Hook 回调
+ [NativeHook("libinput.so","_ZN7android13InputConsumer14consumeSamplesEPNS_26InputEventFactoryInterfaceERNS0_5BatchEmPjPPNS_10InputEventE")]
+ public unsafe static long OnConsumeSamples(void* thiz,void* factory, IntPtr batch,
+ ulong count, uint* outSeq, void** outEvent)
{
- var result = OnConsumeOriginal(
- thiz,
- factory,
- consumeBatches,
- frameTime,
- outSeq,
- outEvent);
- if (outEvent != null && *outEvent != null)
- DispatchInputEvent(new IntPtr(*outEvent));
+ var result = OnConsumeSamplesOriginal(thiz,factory, batch, count, outSeq, outEvent);
+ if (IsInitialized && *outEvent != null) ImGuiImplAndroid.HandleInputEvent(new IntPtr(*outEvent));
return result;
}
-
- ///
- /// 把一个原生输入事件同时送往 ImGui 和 订阅方。
- ///
- ///
- /// 输入广播不依赖 ImGui 上下文是否已经初始化;这样异步输入 Mod 可以在
- /// 菜单、暂停和非游玩阶段继续收到原始事件并自行决定是否消费。
- ///
- private static void DispatchInputEvent(IntPtr inputEvent)
+
+ [NativeHook("libinput.so","_ZN7android13InputConsumer7consumeEPNS_26InputEventFactoryInterfaceEblPjPPNS_10InputEventE")]
+ public unsafe static long OnConsume(void* thiz, void* factory, bool consumeBatches, ulong frameTime, uint* outSeq, void** outEvent)
{
+ var result = OnConsumeOriginal(thiz, factory, consumeBatches, frameTime, outSeq, outEvent);
+ if (IsInitialized && *outEvent != null) ImGuiImplAndroid.HandleInputEvent(new IntPtr(*outEvent));
+ return result;
+ }*/
+
+ [NativeHook("GetInitializeMotionEventAddress")]
+ public unsafe static bool OnInitializeMotionEvent(void* @event, void* message)
+ {
+ var result = OnInitializeMotionEventOriginal(@event, message);
+ var x = AndroidInput.AMotionEvent_getX(new(@event), 0);
+ var y = AndroidInput.AMotionEvent_getY(new(@event), 0);
if (InputEvents.HasSubscribers)
- InputEvents.RaiseFrom(inputEvent);
- if (IsInitialized)
- ImGuiImplAndroid.HandleInputEvent(inputEvent);
+ InputEvents.RaiseFrom(new IntPtr(@event));
+ ImGuiImplAndroid.HandleInputEvent(new IntPtr(@event));
+ return result;
+ }
+
+ private static nint GetInitializeMotionEventAddress()
+ {
+ byte?[] sig = NativeFuncResolver.ParseHexPattern(
+ "e8 0f 19 fc fd 7b 01 a9 fc 6f 02 a9 fa 67 03 a9 " +
+ "f8 5f 04 a9 f6 57 05 a9 f4 4f 06 a9 fd 43 00 91");
+ var r = new NativeFuncResolver("/system/lib64/libinput.so");
+ long rva = r.FindSymbolRva("_ZN7android13InputConsumer21initializeMotionEventEPNS_11MotionEventEPKNS_12InputMessageE");
+ try
+ {
+ if (rva < 0)
+ {
+ var text = r.TextBytes;
+ long textAddr = r.TextBaseAddress;
+ int pos = 0;
+ while ((pos = NativeFuncResolver.Search(text, sig, pos)) >= 0)
+ {
+ int ctxEnd = Math.Min(pos + 60, text.Length);
+ bool hasMrs = false, hasLdr = false;
+ for (int j = pos + 32; j <= ctxEnd - 4; j += 4)
+ {
+ int inst = BitConverter.ToInt32(text.AsSpan(j));
+ if (!hasMrs && (inst & 0xffffffe0) == 0xd53bd040)
+ hasMrs = true;
+ if (!hasLdr && (inst & 0xffe003e0) == 0xb9400020)
+ {
+ int imm12 = (inst >> 10) & 0xfff;
+ if (imm12 * 4 == 0xc) hasLdr = true;
+ }
+ if (hasMrs && hasLdr) break;
+ }
+ if (hasMrs && hasLdr)
+ {
+ rva = textAddr + pos;
+ if (pos >= 4)
+ {
+ int prev = BitConverter.ToInt32(text.AsSpan(pos - 4));
+ if (prev == unchecked((int)0xd503233f)) rva -= 4;
+ }
+ break;
+ }
+ pos++;
+ }
+ if (rva < 0) throw new KeyNotFoundException("initializeMotionEvent not found by signature.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(nameof(ImGuiInputHandler), ex.ToString());
+ }
+ r.Load();
+ return r.GetFuncPtr(rva);
}
private static JavaClass? s_utilsClass;