AIWROK 屏幕触摸事件监听工具

455

主题

212

回帖

2759

积分

管理员

积分
2759
AIWROK 屏幕触摸事件监听工具

QQ截图20260921114835.png


QQ截图20260921114845.png


QQ截图20260921114856.png


  1. /**
  2. * ╔══════════════════════════════════════════════════╗
  3. * ║     AIWROK 屏幕触摸事件监听工具                   ║
  4. * ║     (模拟 events.observeTouch 功能)               ║
  5. * ╚══════════════════════════════════════════════════╝
  6. *
  7. * 核心能力:
  8. *   ① 检测是否有 root 权限
  9. *   ② 自动查找触摸设备节点(/dev/input/eventX)
  10. *   ③ 实时解析触摸事件 → 回调 onTouchDown/Move/Up
  11. *   ④ 自动坐标转换(ABS_MT_POSITION_X/Y)
  12. *   ⑤ 提供 observeTouch() 统一入口
  13. *
  14. * 原理:通过 su + getevent -lt 读取 /dev/input/eventX 原始事件流
  15. *
  16. * 依赖:需要 root 权限(雷电/夜神/逍遥模拟器默认自带 root)
  17. */

  18. // ======================== Shell 执行工具 ========================

  19. /**
  20. * 统一 shell 执行入口
  21. * 优先用 AIWROK 自带的 agent.shell(已经处理了 root)
  22. * 兜底用 java.lang.Runtime.exec
  23. */
  24. function shellExec(cmd) {
  25.     // 优先走 agent.shell
  26.     if (typeof agent !== "undefined" && typeof agent.shell === "function") {
  27.         try {
  28.             var ret = agent.shell(cmd);
  29.             return ret === null ? "" : ret.toString();
  30.         } catch (e) {
  31.             printl("[shell] agent.shell('" + cmd + "') 异常: " + e.message);
  32.         }
  33.     }
  34.     // 兜底:Runtime.exec(不要 su -c,雷电自带 root 直接跑就行)
  35.     try {
  36.         var proc = java.lang.Runtime.getRuntime().exec(cmd);
  37.         var reader = new java.io.BufferedReader(
  38.             new java.io.InputStreamReader(proc.getInputStream())
  39.         );
  40.         var errReader = new java.io.BufferedReader(
  41.             new java.io.InputStreamReader(proc.getErrorStream())
  42.         );
  43.         var sb = new java.lang.StringBuilder();
  44.         var errSb = new java.lang.StringBuilder();
  45.         var line;
  46.         while ((line = reader.readLine()) !== null) {
  47.             sb.append(line).append("\n");
  48.         }
  49.         while ((line = errReader.readLine()) !== null) {
  50.             errSb.append(line).append("\n");
  51.         }
  52.         proc.waitFor();
  53.         var stdout = sb.toString();
  54.         var stderr = errSb.toString();
  55.         if (stderr && stderr.length() > 0) {
  56.             printl("[shell] cmd=" + cmd + "  stderr=" + stderr.trim());
  57.         }
  58.         return stdout.trim();
  59.     } catch (e2) {
  60.         printl("[shell] Runtime.exec('" + cmd + "') 异常: " + e2.message);
  61.         return "";
  62.     }
  63. }

  64. // ======================== 设备检测 ========================

  65. /**
  66. * 检测设备是否有 root 权限
  67. * 直接跑 id 看 uid=0
  68. */
  69. function isRootAvailable() {
  70.     // AIWROK 里 agent.shell 本身就需要 root 才能用
  71.     if (typeof agent !== "undefined" && typeof agent.shell === "function") {
  72.         try {
  73.             var ret = shellExec("id");
  74.             printl("[root检测] agent.shell('id') → " + ret);
  75.             if (ret && ret.indexOf("uid=0") >= 0) return true;
  76.         } catch (e) {}
  77.     }

  78.     // 再试 Runtime 方式
  79.     try {
  80.         var r2 = shellExec("id");
  81.         printl("[root检测] Runtime exec('id') → " + r2);
  82.         if (r2 && r2.indexOf("uid=0") >= 0) return true;
  83.     } catch (e2) {}

  84.     printl("[root检测] 所有策略均失败");
  85.     return false;
  86. }

  87. /**
  88. * 查找触摸设备 eventX 节点
  89. * 策略1: cat /proc/bus/input/devices 找 Name 含 touch 的
  90. * 策略2(兜底): getevent -lp 找含 ABS_MT_POSITION_X/Y 的
  91. */
  92. function findTouchEventDevice() {
  93.     // 先把每个 eventX 的完整配置都打出来
  94.     printl("[触摸检测] ========= 所有设备完整信息 ==========");
  95.     var allDev = shellExec("getevent -lp");
  96.     // 按 "add device" 分段
  97.     var devBlocks = allDev.split(/add device \d+: /);
  98.     for (var b = 0; b < devBlocks.length; b++) {
  99.         var block = devBlocks[b].trim();
  100.         if (!block) continue;
  101.         // 提取 eventX 名
  102.         var evtMatch = block.match(/^\/dev\/input\/(event\d+)/);
  103.         if (!evtMatch) continue;
  104.         // 提取 name
  105.         var nameMatch = block.match(/name:\s*"([^"]*)"/);
  106.         // 提取 events 描述
  107.         var eventsStr = "";
  108.         var evMatch = block.match(/events:([\s\S]*?)(?=input props|add device|$)/);
  109.         if (evMatch) eventsStr = evMatch[1].trim();
  110.         printl("");
  111.         printl("  设备: " + evtMatch[1]);
  112.         printl("    名称: " + (nameMatch ? nameMatch[1] : "(无)"));
  113.         // 只看前 300 字的 events,别太长
  114.         printl("    events: " + eventsStr.substring(0, Math.min(300, eventsStr.length())));
  115.     }
  116.     printl("[触摸检测] ====================================");

  117.     // 再扫一遍完整输出找触摸特征
  118.     if (allDev && allDev.length() > 0) {
  119.         var re = /add device \d+: \/dev\/input\/(event\d+)\s+name:\s*"([^"]*)"([\s\S]*?)(?=add device|$)/g;
  120.         var m;
  121.         while ((m = re.exec(allDev)) !== null) {
  122.             var evtName = m[1];
  123.             var devName = m[2].toLowerCase();
  124.             var eventsInfo = m[3];

  125.             // 策略 A: 名字含触摸关键词
  126.             if (devName.indexOf("touch") >= 0 ||
  127.                 devName.indexOf("tsp") >= 0 ||
  128.                 devName.indexOf("panel") >= 0 ||
  129.                 devName.indexOf("goodix") >= 0 ||
  130.                 devName.indexOf("synaptics") >= 0) {
  131.                 printl("[触摸检测] ✓ 名字匹配: " + evtName + " (" + m[2] + ")");
  132.                 return evtName;
  133.             }

  134.             // 策略 B: events 含 ABS_MT_POSITION(多点触控)
  135.             if (eventsInfo.indexOf("ABS_MT_POSITION_X") >= 0 &&
  136.                 eventsInfo.indexOf("ABS_MT_POSITION_Y") >= 0) {
  137.                 printl("[触摸检测] ✓ 多点触控特征: " + evtName + " (" + m[2] + ")");
  138.                 return evtName;
  139.             }

  140.             // 策略 C: events 含 BTN_TOUCH + ABS_X + ABS_Y(单点触控)
  141.             if (eventsInfo.indexOf("BTN_TOUCH") >= 0 &&
  142.                 eventsInfo.indexOf("ABS_X") >= 0 &&
  143.                 eventsInfo.indexOf("ABS_Y") >= 0) {
  144.                 printl("[触摸检测] ✓ 单点触控特征: " + evtName + " (" + m[2] + ")");
  145.                 return evtName;
  146.             }

  147.             // 策略 D: HID 触摸(雷电常见)
  148.             if ((devName.indexOf("hid") >= 0 || devName.indexOf("mouse") >= 0) &&
  149.                 eventsInfo.indexOf("BTN_TOUCH") >= 0) {
  150.                 printl("[触摸检测] ✓ HID 触摸: " + evtName + " (" + m[2] + ")");
  151.                 return evtName;
  152.             }
  153.         }
  154.     }

  155.     printl("[触摸检测] 所有策略均未找到触摸设备");
  156.     return null;
  157. }

  158. // ======================== 触摸观察器 ========================

  159. /**
  160. * 触摸观察器
  161. * 后台线程跑 getevent -lt,解析后回调 down/move/up
  162. */
  163. function TouchObserver(eventDevice) {
  164.     this.device = eventDevice;
  165.     this.running = false;
  166.     this.process = null;
  167.     this.listeners = { down: [], move: [], up: [], touch: [] };
  168.     this.currentX = -1;
  169.     this.currentY = -1;
  170.     this.currentPointer = -1;
  171.     this.pointerDown = false;
  172.     this._downFired = false;
  173. }

  174. TouchObserver.prototype.onDown = function (fn) { this.listeners.down.push(fn); };
  175. TouchObserver.prototype.onMove = function (fn) { this.listeners.move.push(fn); };
  176. TouchObserver.prototype.onUp   = function (fn) { this.listeners.up.push(fn);   };
  177. TouchObserver.prototype.onTouch = function (fn) { this.listeners.touch.push(fn); };

  178. TouchObserver.prototype._emit = function (type, ev) {
  179.     var i, arr;
  180.     // 专属回调
  181.     arr = this.listeners[type];
  182.     for (i = 0; i < arr.length; i++) {
  183.         try { arr[i](ev); } catch (e) { printl("[触摸回调错] " + e.message); }
  184.     }
  185.     // 统一回调:(type, event)
  186.     arr = this.listeners.touch;
  187.     for (i = 0; i < arr.length; i++) {
  188.         try { arr[i](type, ev); } catch (e2) { printl("[触摸回调错] " + e2.message); }
  189.     }
  190. };

  191. TouchObserver.prototype.start = function () {
  192.     if (this.running) { printl("[触摸] 已在运行"); return; }
  193.     if (!this.device) { printl("[触摸] 无有效设备节点"); return; }

  194.     var target = this;
  195.     target.running = true;

  196.     // AIWROK 正确的线程写法:new thread().runJsCode(fn, "线程名")
  197.     var t = new thread();
  198.     target._thread = t;
  199.     t.runJsCode(function () {
  200.         try {
  201.             var dev = target.device;
  202.             var tmpFile = "/sdcard/aiwrok_touch_events.log";
  203.             target._tmpFile = tmpFile;

  204.             // 先杀掉可能残留的旧 getevent 进程,避免重复输出
  205.             try { agent.shell("su -c 'pkill -9 -f getevent'"); } catch (e) {}
  206.             java.lang.Thread.sleep(200);

  207.             // 清理旧文件
  208.             try { agent.shell("su -c 'rm -f " + tmpFile + "'"); } catch (e) {}

  209.             // 后台运行 getevent,输出重定向到临时文件
  210.             var bgCmd = "su -c 'getevent -lt /dev/input/" + dev + " > " + tmpFile + " 2>&1 &'";
  211.             printl("[触摸] 启动 getevent: " + bgCmd);
  212.             agent.shell(bgCmd);

  213.             // 等一下让进程启动
  214.             java.lang.Thread.sleep(500);

  215.             printl("[触摸] ✓ 启动完成,去屏幕上滑动吧!");

  216.             // 用 RandomAccessFile 自己的 readLine(),自己维护偏移量
  217.             var raf = new java.io.RandomAccessFile(tmpFile, "r");
  218.             var lastPos = 0;  // 上次读到的位置
  219.             var gotFirst = false;

  220.             while (target.running) {
  221.                 try {
  222.                     var curLen = raf.length();

  223.                     if (curLen > lastPos) {
  224.                         raf.seek(lastPos);
  225.                         var line;
  226.                         while ((line = raf.readLine()) !== null) {
  227.                             if (!gotFirst) {
  228.                                 gotFirst = true;
  229.                                 printl("[触摸] ✓✓✓ 收到触摸事件!滑动中...");
  230.                             }
  231.                             target._parseLine(line);
  232.                             lastPos = raf.getFilePointer();
  233.                         }
  234.                     }

  235.                     java.lang.Thread.sleep(30);
  236.                 } catch (ie) {
  237.                     printl("[触摸] 读异常: " + ie.message);
  238.                     break;
  239.                 }
  240.             }

  241.             try { raf.close(); } catch (e) {}
  242.             printl("[触摸] 监听线程已退出");
  243.         } catch (e) {
  244.             if (target.running) {
  245.                 printl("[触摸] 线程异常: " + e.message);
  246.             }
  247.         }
  248.     }, "触摸监听");
  249. };

  250. /** 解析一行 getevent 输出 */
  251. TouchObserver.prototype._parseLine = function (line) {
  252.     if (!line) return;

  253.     // 雷电格式:
  254.     // [    1950.000612] EV_ABS       ABS_MT_POSITION_X    00000385            
  255.     // [    1950.152524] EV_ABS       ABS_MT_TRACKING_ID   ffffffff            
  256.     // [    1950.152524] EV_KEY       BTN_TOUCH            UP                  
  257.     // [    1952.376642] EV_ABS       ABS_MT_TRACKING_ID   00000096            
  258.     // [    1952.376642] EV_KEY       BTN_TOUCH            DOWN               
  259.     // [    1952.376642] EV_SYN       SYN_REPORT           00000000            

  260.     // 直接字符串匹配,绕开 split 的各种坑
  261.     var self = this;

  262.     // ABS_MT_POSITION_X
  263.     var xIdx = line.indexOf("ABS_MT_POSITION_X");
  264.     if (xIdx >= 0) {
  265.         var hex = line.substring(xIdx + "ABS_MT_POSITION_X".length).trim();
  266.         var v = parseInt(hex, 16);
  267.         // 过滤异常值:屏幕分辨率 1080x1920,超过 2000 或负数都是垃圾数据
  268.         if (v >= 0 && v < 5000) self.currentX = v;
  269.         return;
  270.     }

  271.     // ABS_MT_POSITION_Y
  272.     var yIdx = line.indexOf("ABS_MT_POSITION_Y");
  273.     if (yIdx >= 0) {
  274.         var hex2 = line.substring(yIdx + "ABS_MT_POSITION_Y".length).trim();
  275.         var v2 = parseInt(hex2, 16);
  276.         if (v2 >= 0 && v2 < 5000) self.currentY = v2;
  277.         return;
  278.     }

  279.     // ABS_MT_TRACKING_ID
  280.     var tIdx = line.indexOf("ABS_MT_TRACKING_ID");
  281.     if (tIdx >= 0) {
  282.         var hex3 = line.substring(tIdx + "ABS_MT_TRACKING_ID".length).trim();
  283.         var tid = parseInt(hex3, 16);
  284.         if (tid < 0 || hex3.indexOf("ffffffff") >= 0) {
  285.             // 抬起
  286.             if (self.pointerDown) {
  287.                 self.pointerDown = false;
  288.                 self._downFired = false;
  289.                 self._emit("up", {
  290.                     x: self.currentX, y: self.currentY,
  291.                     pointerId: self.currentPointer,
  292.                     timestamp: java.lang.System.currentTimeMillis()
  293.                 });
  294.             }
  295.         } else {
  296.             // 按下
  297.             self.currentPointer = tid;
  298.             self.pointerDown = true;
  299.             self._downFired = false;
  300.         }
  301.         return;
  302.     }

  303.     // BTN_TOUCH DOWN
  304.     if (line.indexOf("BTN_TOUCH") >= 0 && line.indexOf("DOWN") >= 0) {
  305.         self.pointerDown = true;
  306.         self._downFired = false;
  307.         return;
  308.     }

  309.     // BTN_TOUCH UP
  310.     if (line.indexOf("BTN_TOUCH") >= 0 && line.indexOf("UP") >= 0) {
  311.         if (self.pointerDown) {
  312.             self.pointerDown = false;
  313.             self._downFired = false;
  314.             self._emit("up", {
  315.                 x: self.currentX, y: self.currentY,
  316.                 pointerId: self.currentPointer,
  317.                 timestamp: java.lang.System.currentTimeMillis()
  318.             });
  319.         }
  320.         return;
  321.     }

  322.     // SYN_REPORT —— 一帧数据结束,触发回调
  323.     if (line.indexOf("SYN_REPORT") >= 0) {
  324.         if (self.pointerDown && self.currentX >= 0 && self.currentY >= 0) {
  325.             if (!self._downFired) {
  326.                 self._downFired = true;
  327.                 self._emit("down", {
  328.                     x: self.currentX, y: self.currentY,
  329.                     pointerId: self.currentPointer,
  330.                     timestamp: java.lang.System.currentTimeMillis()
  331.                 });
  332.             } else {
  333.                 self._emit("move", {
  334.                     x: self.currentX, y: self.currentY,
  335.                     pointerId: self.currentPointer,
  336.                     timestamp: java.lang.System.currentTimeMillis()
  337.                 });
  338.             }
  339.         }
  340.         return;
  341.     }
  342. };

  343. TouchObserver.prototype.stop = function () {
  344.     this.running = false;
  345.     // 用 agent.shell kill 后台 getevent 进程
  346.     try {
  347.         agent.shell("su -c 'pkill -f getevent'");
  348.     } catch (e) {}
  349.     // 清理临时文件
  350.     try {
  351.         if (this._tmpFile) {
  352.             agent.shell("su -c 'rm -f " + this._tmpFile + "'");
  353.         }
  354.     } catch (e) {}
  355.     if (this.process) {
  356.         try { this.process.destroy(); } catch (e) {}
  357.         this.process = null;
  358.     }
  359.     if (this._thread) {
  360.         try { this._thread.stop(); } catch (e) {}
  361.         this._thread = null;
  362.     }
  363.     printl("[触摸] 已停止");
  364. };

  365. // ======================== 统一入口 observeTouch ========================

  366. var _currentObserver = null;

  367. /**
  368. * 启动屏幕触摸监听(模拟 Hamibot events.observeTouch)
  369. * @param {Object} options
  370. * @param {Function} [options.onTouchDown]  按下回调 function(event){}
  371. * @param {Function} [options.onTouchMove]  移动回调 function(event){}
  372. * @param {Function} [options.onTouchUp]    抬起回调 function(event){}
  373. * @param {Function} [options.onTouch]      统一回调 function(type, event){}
  374. * @returns {TouchObserver} 可调用 .stop() 停止;无 root 返回 null
  375. *
  376. * event 结构: { x, y, pointerId, timestamp }
  377. * type: "down" | "move" | "up"
  378. */
  379. function observeTouch(options) {
  380.     options = options || {};

  381.     if (!isRootAvailable()) {
  382.         printl("✗ observeTouch 需要 root 权限,当前设备无 root");
  383.         printl("  雷电 / 夜神 / 逍遥 模拟器默认自带 root");
  384.         return null;
  385.     }

  386.     printl("🔍 正在检测触摸设备节点...");
  387.     var device = findTouchEventDevice();
  388.     if (!device) {
  389.         printl("✗ 找不到触摸设备节点");
  390.         return null;
  391.     }
  392.     printl("✓ 触摸设备: /dev/input/" + device);

  393.     var obs = new TouchObserver(device);
  394.     if (typeof options.onTouchDown === "function") obs.onDown(options.onTouchDown);
  395.     if (typeof options.onTouchMove === "function") obs.onMove(options.onTouchMove);
  396.     if (typeof options.onTouchUp   === "function") obs.onUp(options.onTouchUp);
  397.     if (typeof options.onTouch     === "function") obs.onTouch(options.onTouch);

  398.     obs.start();
  399.     _currentObserver = obs;
  400.     return obs;
  401. }

  402. /** 停止全局触摸监听 */
  403. function stopObserveTouch() {
  404.     if (_currentObserver) {
  405.         _currentObserver.stop();
  406.         _currentObserver = null;
  407.     }
  408. }

  409. // ======================== demo ========================

  410. function demo() {
  411.     printl("╔══════════════════════════════════════════════╗");
  412.     printl("║   AIWROK 屏幕触摸事件监听 - 演示             ║");
  413.     printl("╚══════════════════════════════════════════════╝");
  414.     sleep.millisecond(600);

  415.     // ---- 步骤 1: 检查 root ----
  416.     printl("\n🔐 [1/4] 检查 root 权限...");
  417.     sleep.millisecond(300);
  418.     var hasRoot = isRootAvailable();
  419.     printl("  root 权限: " + (hasRoot ? "✓ 可用" : "✗ 不可用"));
  420.     sleep.millisecond(800);

  421.     if (!hasRoot) {
  422.         printl("\n⚠️  无 root,演示停止。请先 root 设备后再试。");
  423.         return;
  424.     }

  425.     // ---- 步骤 2: 找设备 ----
  426.     printl("\n🔍 [2/4] 扫描触摸设备节点...");
  427.     sleep.millisecond(300);
  428.     var device = findTouchEventDevice();
  429.     if (device) {
  430.         printl("  ✓ 找到: /dev/input/" + device);
  431.     } else {
  432.         printl("  ✗ 未找到触摸设备");
  433.         return;
  434.     }
  435.     sleep.millisecond(800);

  436.     // ---- 步骤 3: 启动监听 ----
  437.     printl("\n👆 [3/4] 启动触摸监听");
  438.     sleep.millisecond(300);
  439.     printl("  👉 请在屏幕上滑动/点击,观察日志");
  440.     printl("  15 秒后自动停止(也可手动按停止按钮)");
  441.     sleep.millisecond(500);

  442.     var lastMoveLog = 0;  // 节流 move 日志
  443.     var obs = observeTouch({
  444.         onTouchDown: function (e) {
  445.             printl("  🔽 DOWN   x=" + e.x + "  y=" + e.y);
  446.         },
  447.         onTouchMove: function (e) {
  448.             // move 很密集,每 80ms 最多打一条
  449.             var now = java.lang.System.currentTimeMillis();
  450.             if (now - lastMoveLog > 80) {
  451.                 lastMoveLog = now;
  452.                 printl("  ➡️  MOVE   x=" + e.x + "  y=" + e.y);
  453.             }
  454.         },
  455.         onTouchUp: function (e) {
  456.             printl("  🔼 UP     x=" + e.x + "  y=" + e.y);
  457.         }
  458.     });

  459.     // ---- 步骤 4: 15 秒后自动停 ----
  460.     sleep.millisecond(15000);

  461.     if (obs) obs.stop();

  462.     printl("\n╔══════════════════════════════════════════════╗");
  463.     printl("║              演示结束!                       ║");
  464.     printl("║  observeTouch() 可直接集成到你的脚本          ║");
  465.     printl("╚══════════════════════════════════════════════╝");
  466. }

  467. demo();
复制代码


举报 回复