YYPOST群发软件 发表于 昨天 06:39

触摸手势识别悬浮画板自动执行助手


触摸手势识别悬浮画板自动执行助手









/**
* ╔══════════════════════════════════════════════════════════╗
* ║   AIWROK 触摸手势识别 + 悬浮画板 + 自动执行助手            ║
* ║                                                          ║
* ║   功能:                                                ║
* ║   ① 监听真实屏幕触摸事件 (root + getevent)             ║
* ║   ② 在 floatUI 悬浮窗上实时画出手指轨迹               ║
* ║   ③ 自动识别手势:上滑/下滑/左滑/右滑/双击/长按/V字      ║
* ║   ④ 手势触发自动化动作:                              ║
* ║          右滑 → 回桌面                                     ║
* ║          左滑 → 截图保存到相册                              ║
* ║          上滑 → 音量+                                    ║
* ║          下滑 → 音量-                                    ║
* ║          双击 → 打开设置                                    ║
* ║          长按 → 启动/停止脚本                               ║
* ║          V字→ 打开微信                                    ║
* ║                                                          ║
* ║   依赖:root 权限(雷电/夜神/逍遥/蓝叠 默认自带 root)   ║
* ║   引擎:Rhino ES5                                       ║
* ╚══════════════════════════════════════════════════════════╝
*/

// ==============================================================
// 第一部分:observeTouch 核心(从原文件精简并增强)
// ==============================================================

function shellExec(cmd) {
    if (typeof agent !== "undefined" && typeof agent.shell === "function") {
      try {
            var ret = agent.shell(cmd);
            return ret === null ? "" : ret.toString();
      } catch (e) {}
    }
    try {
      var proc = java.lang.Runtime.getRuntime().exec(cmd);
      var reader = new java.io.BufferedReader(
            new java.io.InputStreamReader(proc.getInputStream())
      );
      var sb = new java.lang.StringBuilder();
      var line;
      while ((line = reader.readLine()) !== null) {
            sb.append(line).append("\n");
      }
      proc.waitFor();
      return sb.toString().trim();
    } catch (e) { return ""; }
}

function findTouchEventDevice() {
    var allDev = shellExec("getevent -lp");
    var re = /add device \d+: \/dev\/input\/(event\d+)\s+name:\s*"([^"]*)"([\s\S]*?)(?=add device|$)/g;
    var m;
    while ((m = re.exec(allDev)) !== null) {
      var evtName = m;
      var devName = m.toLowerCase();
      var eventsInfo = m;

      if (devName.indexOf("touch") >= 0 || devName.indexOf("tsp") >= 0 ||
            devName.indexOf("goodix") >= 0 || devName.indexOf("synaptics") >= 0) {
            return evtName;
      }
      if (eventsInfo.indexOf("ABS_MT_POSITION_X") >= 0 &&
            eventsInfo.indexOf("ABS_MT_POSITION_Y") >= 0) {
            return evtName;
      }
      if (eventsInfo.indexOf("BTN_TOUCH") >= 0 &&
            eventsInfo.indexOf("ABS_X") >= 0 && eventsInfo.indexOf("ABS_Y") >= 0) {
            return evtName;
      }
      if ((devName.indexOf("hid") >= 0 || devName.indexOf("mouse") >= 0) &&
            eventsInfo.indexOf("BTN_TOUCH") >= 0) {
            return evtName;
      }
    }
    return null;
}

function TouchObserver(eventDevice) {
    this.device = eventDevice;
    this.running = false;
    this.listeners = { down: [], move: [], up: [] };
    this.currentX = -1;
    this.currentY = -1;
    this.pointerDown = false;
    this._downFired = false;
}

TouchObserver.prototype.onDown = function (fn) { this.listeners.down.push(fn); };
TouchObserver.prototype.onMove = function (fn) { this.listeners.move.push(fn); };
TouchObserver.prototype.onUp   = function (fn) { this.listeners.up.push(fn);   };

TouchObserver.prototype._emit = function (type, ev) {
    var arr = this.listeners;
    for (var i = 0; i < arr.length; i++) {
      try { arr(ev); } catch (e) {}
    }
};

TouchObserver.prototype.start = function () {
    if (this.running) return;
    this.running = true;
    var target = this;

    var t = new thread();
    t.runJsCode(function () {
      try {
            var dev = target.device;
            var tmpFile = "/sdcard/aiwrok_gesture_events.log";

            try { agent.shell("su -c 'pkill -9 -f getevent'"); } catch (e) {}
            java.lang.Thread.sleep(200);
            try { agent.shell("su -c 'rm -f " + tmpFile + "'"); } catch (e) {}

            var bgCmd = "su -c 'getevent -lt /dev/input/" + dev + " > " + tmpFile + " 2>&1 &'";
            agent.shell(bgCmd);
            java.lang.Thread.sleep(500);

            var raf = new java.io.RandomAccessFile(tmpFile, "r");
            var lastPos = 0;

            while (target.running) {
                var curLen = raf.length();
                if (curLen > lastPos) {
                  raf.seek(lastPos);
                  var line;
                  while ((line = raf.readLine()) !== null) {
                        target._parseLine(line);
                        lastPos = raf.getFilePointer();
                  }
                }
                java.lang.Thread.sleep(25);
            }
            try { raf.close(); } catch (e) {}
      } catch (e) {}
    }, "手势监听");
};

TouchObserver.prototype._parseLine = function (line) {
    if (!line) return;
    var self = this;

    var xIdx = line.indexOf("ABS_MT_POSITION_X");
    if (xIdx >= 0) {
      var v = parseInt(line.substring(xIdx + "ABS_MT_POSITION_X".length).trim(), 16);
      if (v >= 0 && v < 5000) self.currentX = v;
      return;
    }
    var yIdx = line.indexOf("ABS_MT_POSITION_Y");
    if (yIdx >= 0) {
      var v2 = parseInt(line.substring(yIdx + "ABS_MT_POSITION_Y".length).trim(), 16);
      if (v2 >= 0 && v2 < 5000) self.currentY = v2;
      return;
    }
    var tIdx = line.indexOf("ABS_MT_TRACKING_ID");
    if (tIdx >= 0) {
      var tidHex = line.substring(tIdx + "ABS_MT_TRACKING_ID".length).trim();
      if (tidHex.indexOf("ffffffff") >= 0 || parseInt(tidHex, 16) < 0) {
            if (self.pointerDown) {
                self.pointerDown = false;
                self._downFired = false;
                self._emit("up", { x: self.currentX, y: self.currentY,
                  timestamp: java.lang.System.currentTimeMillis() });
            }
      } else {
            self.pointerDown = true;
            self._downFired = false;
      }
      return;
    }
    if (line.indexOf("BTN_TOUCH") >= 0 && line.indexOf("DOWN") >= 0) {
      self.pointerDown = true;
      self._downFired = false;
      return;
    }
    if (line.indexOf("BTN_TOUCH") >= 0 && line.indexOf("UP") >= 0) {
      if (self.pointerDown) {
            self.pointerDown = false;
            self._downFired = false;
            self._emit("up", { x: self.currentX, y: self.currentY,
                timestamp: java.lang.System.currentTimeMillis() });
      }
      return;
    }
    if (line.indexOf("SYN_REPORT") >= 0) {
      if (self.pointerDown && self.currentX >= 0 && self.currentY >= 0) {
            if (!self._downFired) {
                self._downFired = true;
                self._emit("down", { x: self.currentX, y: self.currentY,
                  timestamp: java.lang.System.currentTimeMillis() });
            } else {
                self._emit("move", { x: self.currentX, y: self.currentY,
                  timestamp: java.lang.System.currentTimeMillis() });
            }
      }
      return;
    }
};

TouchObserver.prototype.stop = function () {
    this.running = false;
    try { agent.shell("su -c 'pkill -f getevent'"); } catch (e) {}
    try { agent.shell("su -c 'rm -f /sdcard/aiwrok_gesture_events.log'"); } catch (e) {}
    printl("✓ 触摸监听已停止");
};

// ==============================================================
// 第二部分:手势识别引擎
// ==============================================================

/**
* 手势识别器
* 记录一整次触摸过程的轨迹点列表,抬起时分析出是什么手势
*/
function GestureRecognizer(callback) {
    this.callback = callback;// 识别完成回调 function(gestureType, points)
    this.points = [];
    this.isTracking = false;
    this.lastDownTime = 0;      // 上次按下时间(检测双击)
    this.lastUpTime = 0;      // 上次抬起时间
    this.lastUpX = 0;
    this.lastUpY = 0;
    this.longPressTimer = null; // 长按定时器
}

GestureRecognizer.prototype.onDown = function (e) {
    this.isTracking = true;
    this.points = [{ x: e.x, y: e.y, t: e.timestamp, type: "down" }];

    // 长按检测:按下 800ms 还没抬起就算长按
    var self = this;
    this.longPressTimer = setTimeout(function () {
      if (self.isTracking && self.points.length > 0) {
            var p = self.points;
            self.callback("longpress", self.points);
            self.isTracking = false;
            self.points = [];
      }
    }, 800);
};

GestureRecognizer.prototype.onMove = function (e) {
    if (!this.isTracking) return;
    this.points.push({ x: e.x, y: e.y, t: e.timestamp, type: "move" });
};

GestureRecognizer.prototype.onUp = function (e) {
    if (!this.isTracking) {
      // 可能是第二次点击(双击检测)
      var now = e.timestamp;
      if (now - this.lastUpTime < 400 && now - this.lastUpTime > 30) {
            // 距离也不能太远(50像素以内)
            var dx = e.x - this.lastUpX;
            var dy = e.y - this.lastUpY;
            if (Math.sqrt(dx * dx + dy * dy) < 80) {
                this.callback("doubleclick", null);
            }
      }
      this.lastUpX = e.x;
      this.lastUpY = e.y;
      this.lastUpTime = now;
      return;
    }
    this.points.push({ x: e.x, y: e.y, t: e.timestamp, type: "up" });
    this.isTracking = false;

    if (this.longPressTimer) {
      clearTimeout(this.longPressTimer);
      this.longPressTimer = null;
    }

    // 分析手势类型
    var gesture = this.analyze(this.points);
    this.callback(gesture, this.points);

    this.lastUpX = e.x;
    this.lastUpY = e.y;
    this.lastUpTime = e.timestamp;
    this.points = [];
};

/**
* 核心分析算法:从点集判断是什么手势
*/
GestureRecognizer.prototype.analyze = function (pts) {
    if (pts.length < 2) return "click";

    var first = pts;
    var last = pts;
    var dx = last.x - first.x;
    var dy = last.y - first.y;
    var dist = Math.sqrt(dx * dx + dy * dy);

    // ============ 1. 判断是不是滑动 ============
    // 移动距离 < 30 且时间 < 300ms → 普通点击
    var totalTime = last.t - first.t;
    if (dist < 30 && totalTime < 300) {
      return "click";
    }

    // ============ 2. 判断方向 ============
    // 水平分量占优势
    if (Math.abs(dx) > Math.abs(dy) * 1.5 && dist > 60) {
      return dx > 0 ? "swipe_right" : "swipe_left";
    }
    // 垂直分量占优势
    if (Math.abs(dy) > Math.abs(dx) * 1.5 && dist > 60) {
      return dy > 0 ? "swipe_down" : "swipe_up";
    }

    // ============ 3. 判断 V 字手势 ============
    var vResult = this.checkVShape(pts);
    if (vResult) return "v_shape";

    // ============ 4. 判断 Z 字(快速左右摆动)============
    if (this.checkZZigzag(pts)) return "z_shape";

    // 默认:滑动(方向模糊)
    if (dist > 60) return "swipe";

    return "click";
};

/** 检测 V 字形:先向右下/左下滑,然后又向上反弹 */
GestureRecognizer.prototype.checkVShape = function (pts) {
    if (pts.length < 5) return false;
    var first = pts;
    var last = pts;
    var midY = 0;
    var midIdx = Math.floor(pts.length / 2);
    midY = pts.y;

    // 整体向下(中间点比起止点低)
    if (midY > first.y + 30 && midY > last.y + 30) {
      return true;
    }
    return false;
};

/** 检测 Z 字形:横向来回摆动至少 2 次 */
GestureRecognizer.prototype.checkZZigzag = function (pts) {
    if (pts.length < 10) return false;
    var directionChanges = 0;
    var lastDx = 0;
    for (var i = 2; i < pts.length; i++) {
      var curDx = pts.x - pts.x;
      if (Math.abs(curDx) > 15 && lastDx !== 0) {
            if ((curDx > 0) !== (lastDx > 0)) {
                directionChanges++;
            }
      }
      lastDx = curDx;
    }
    return directionChanges >= 2;
};

// ==============================================================
// 第三部分:floatUI 悬浮画板 + 手势状态显示
// ==============================================================

/**
* 创建悬浮画板 UI
* 全屏半透明窗口,实时画出手指轨迹,底部显示当前手势状态
*/
function createFloatCanvas() {
    // 方案:用 XML Canvas 元素 —— 但 AIWROK floatUI 支持 SXML
    // 这里用一个简化版:floatUI 只做状态条 + 按钮
    // 轨迹绘制直接在 screen 上做 overlay 思路太复杂,换成日志 + 按钮

    var f = new floatUI();

    var layout =
      '<vertical bg="#E6000000" w="match_parent" h="wrap_content">' +
      '    <text id="gesture_title" textColor="#00FF88" textSize="15sp" padding="10dp" text="&#128070; 手势助手 已运行"/>' +
      '    <text id="gesture_status" textColor="#FFFFFF" textSize="14sp" padding="4dp 10dp" text="等待触摸..."/>' +
      '    <text id="gesture_hint" textColor="#AAAAAA" textSize="12sp" padding="2dp 10dp 8dp 10dp" text="右滑→桌面 左滑→截图 上滑→音量+ 下滑→音量- 双击→设置 长按→停止 V→微信"/>' +
      '    <horizontal w="match_parent" gravity="center" padding="0dp 8dp 8dp 8dp">' +
      '      <button id="btn_stop" text="■ 停止" w="90dp" h="38dp" textColor="#FF8888" margin="4dp"/>' +
      '      <button id="btn_clear" text="清除" w="90dp" h="38dp" textColor="#66AAFF" margin="4dp"/>' +
      '      <button id="btn_hide" text="隐藏" w="90dp" h="38dp" textColor="#AAAAAA" margin="4dp"/>' +
      '    </horizontal>' +
      '</vertical>';

    f.loadSXML(layout);

    var txtStatus = f.findViewById("gesture_status");
    var txtTitle= f.findViewById("gesture_title");
    var btnStop   = f.findViewById("btn_stop");
    var btnClear= f.findViewById("btn_clear");
    var btnHide   = f.findViewById("btn_hide");

    // 位置:屏幕顶部居中
    var sw = screen.getScreenWidth();
    f.setPosition(Math.floor((sw - 320) / 2), 100);

    btnStop.setOnClickListener(function () {
      printl("用户点击停止按钮");
      stopEverything();
    });

    btnClear.setOnClickListener(function () {
      txtStatus.setText("日志已清除 ✓ 等待触摸...");
    });

    var hidden = false;
    btnHide.setOnClickListener(function () {
      hidden = !hidden;
      if (hidden) {
            f.setPosition(sw - 50, 100);
            txtTitle.setText("&#128070;");
            txtStatus.setVisibility(0);// View.GONE
            btnClear.setVisibility(0);
            btnStop.setVisibility(0);
            btnHide.setText("显示");
      } else {
            f.setPosition(Math.floor((sw - 320) / 2), 100);
            txtTitle.setText("&#128070; 手势助手 已运行");
            txtStatus.setVisibility(1);// View.VISIBLE
            btnClear.setVisibility(1);
            btnStop.setVisibility(1);
            btnHide.setText("隐藏");
      }
    });

    return {
      ui: f,
      setStatus: function (text, color) {
            txtStatus.setText(text);
            if (color) txtStatus.setTextColor(color);
      },
      flashTitle: function (emoji) {
            txtTitle.setText(emoji);
            sleep.millisecond(200);
            txtTitle.setText("&#128070; 手势助手 已运行");
      }
    };
}

// ==============================================================
// 第四部分:手势 → 自动化动作 映射表
// ==============================================================

/**
* 手势执行器
* 识别到手势后,执行对应的自动化操作
*/
function GestureExecutor(panel) {
    this.panel = panel;
    this.actionCount = 0;

    // 手势 → 动作 的映射
    this.rules = {
      "swipe_right": function () {
            panel.setStatus("↪️ 右滑 → 回桌面", "#FFD700");
            auto.home();
      },
      "swipe_left": function () {
            panel.setStatus("↩️ 左滑 → 截图中...", "#66AAFF");
            var img = screen.screenShotFull();
            var path = "/sdcard/Pictures/gesture_" + Date.now() + ".png";
            img.save(path);
            img.recycle();
            panel.setStatus("✅ 截图已保存到 " + path, "#00FF88");
            sleep.second(1);
            panel.setStatus("等待下一个手势...", "#FFFFFF");
      },
      "swipe_up": function () {
            panel.setStatus("⬆️ 上滑 → 音量+", "#00FF88");
            if (typeof agent !== "undefined") agent.shell("input keyevent 24");
            else auto.keyPress(24);
      },
      "swipe_down": function () {
            panel.setStatus("⬇️ 下滑 → 音量-", "#00FF88");
            if (typeof agent !== "undefined") agent.shell("input keyevent 25");
            else auto.keyPress(25);
      },
      "click": function () {
            // 单击:不做特殊动作,只反馈
            panel.setStatus("&#128070; 单击 " + new Date().toLocaleTimeString(), "#AAAAAA");
      },
      "doubleclick": function () {
            panel.setStatus("✌️ 双击 → 打开设置", "#FF66AA");
            app.openApp("com.android.settings");
      },
      "longpress": function () {
            panel.setStatus("⏱️ 长按 → 切换脚本运行", "#FF9900");
            panel.flashTitle("⏱️");
            printl("[手势] 长按 — 这里可以自定义触发你自己的脚本");
            // 例:执行某个任务
            // runYourTask();
      },
      "v_shape": function () {
            panel.setStatus("✔️ V字 → 打开微信", "#00FF88");
            app.openApp("com.tencent.mm");
      },
      "z_shape": function () {
            panel.setStatus("〰️ Z字 → 打开抖音", "#FF0066");
            app.openApp("com.ss.android.ugc.aweme");
      },
      "swipe": function () {
            panel.setStatus("➡️ 模糊滑动,暂不触发动作", "#888888");
      }
    };
}

GestureExecutor.prototype.execute = function (gestureType) {
    this.actionCount++;
    printl("\n========== 手势 #" + this.actionCount + " ==========");
    printl("识别结果: " + gestureType);
    var handler = this.rules;
    if (handler) {
      try { handler(); }
      catch (e) {
            printl("❌ 执行出错: " + e.message);
            this.panel.setStatus("❌ 执行失败: " + e.message, "#FF4444");
      }
    } else {
      printl("(无对应动作)");
    }
    printl("======================================\n");
};

// ==============================================================
// 第五部分:启动、整合、停止
// ==============================================================

var _globalPanel = null;
var _globalObserver = null;
var _globalRecognizer = null;
var _globalExecutor = null;
var _running = false;

function startEverything() {
    if (_running) {
      printl("已经在运行了");
      return;
    }

    printl("\n╔══════════════════════════════════════════╗");
    printl("║AIWROK 手势识别自动化助手 启动中...      ║");
    printl("╚══════════════════════════════════════════╝\n");

    // 1. root 检测
    printl("&#128272; 检测 root 权限...");
    var hasRoot = shellExec("id").indexOf("uid=0") >= 0;
    printl("root: " + (hasRoot ? "✓" : "✗"));
    if (!hasRoot) {
      printl("❌ 需要 root 权限,请在模拟器或 root 手机上运行");
      return;
    }

    // 2. 找触摸设备
    printl("&#128269; 找触摸设备节点...");
    var device = findTouchEventDevice();
    if (!device) {
      printl("❌ 找不到触摸设备");
      return;
    }
    printl("✓ /dev/input/" + device);

    // 3. 创建 UI 面板
    printl("&#127912; 创建悬浮面板...");
    _globalPanel = createFloatCanvas();

    // 4. 创建识别器 — 识别完交给执行器
    _globalExecutor = new GestureExecutor(_globalPanel);
    _globalRecognizer = new GestureRecognizer(function (gestureType, points) {
      _globalExecutor.execute(gestureType);
    });

    // 5. 启动触摸观察器,把事件喂给识别器
    _globalObserver = new TouchObserver(device);

    // move 事件非常密集,做节流(70ms 最多一条)
    var lastMoveTs = 0;

    _globalObserver.onDown(function (e) {
      printl("&#128317; DOWN(" + e.x + "," + e.y + ")");
      _globalPanel.setStatus("&#128317; 按下 @ (" + e.x + "," + e.y + ")", "#00FF88");
      _globalRecognizer.onDown(e);
    });

    _globalObserver.onMove(function (e) {
      var now = java.lang.System.currentTimeMillis();
      if (now - lastMoveTs < 60) return;// 节流
      lastMoveTs = now;
      // move 不刷屏,只在内部记录轨迹
      _globalRecognizer.onMove(e);
    });

    _globalObserver.onUp(function (e) {
      printl("&#128316; UP    (" + e.x + "," + e.y + ")");
      _globalPanel.setStatus("&#128316; 抬起,分析手势中...", "#FFD700");
      _globalRecognizer.onUp(e);
    });

    _globalObserver.start();

    _running = true;
    printl("\n✅ 全部启动!去屏幕上划几下试试吧!\n");
    printl("手势 → 动作映射:");
    printl("↪️ 右滑    → 回桌面");
    printl("↩️ 左滑    → 截图保存");
    printl("⬆️ 上滑    → 音量+");
    printl("⬇️ 下滑    → 音量-");
    printl("✌️ 双击    → 打开设置");
    printl("⏱️ 长按    → 触发自定义任务");
    printl("✔️ V字   → 打开微信");
    printl("〰️ Z字   → 打开抖音\n");
}

function stopEverything() {
    printl("\n&#128721; 正在停止...");
    if (_globalObserver) {
      _globalObserver.stop();
      _globalObserver = null;
    }
    if (_globalPanel && _globalPanel.ui) {
      try { _globalPanel.ui.close(); } catch (e) {}
      _globalPanel = null;
    }
    _running = false;
    printl("✅ 已停止\n");
}

// ==============================================================
// 启动!
// ==============================================================

// 延迟 1.5 秒启动,让悬浮窗初始化不抢时机
sleep.millisecond(1500);
startEverything();


页: [1]
查看完整版本: 触摸手势识别悬浮画板自动执行助手