触摸手势识别悬浮画板自动执行助手
触摸手势识别悬浮画板自动执行助手
/**
* ╔══════════════════════════════════════════════════════════╗
* ║ 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="👆 手势助手 已运行"/>' +
' <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("👆");
txtStatus.setVisibility(0);// View.GONE
btnClear.setVisibility(0);
btnStop.setVisibility(0);
btnHide.setText("显示");
} else {
f.setPosition(Math.floor((sw - 320) / 2), 100);
txtTitle.setText("👆 手势助手 已运行");
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("👆 手势助手 已运行");
}
};
}
// ==============================================================
// 第四部分:手势 → 自动化动作 映射表
// ==============================================================
/**
* 手势执行器
* 识别到手势后,执行对应的自动化操作
*/
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("👆 单击 " + 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("🔐 检测 root 权限...");
var hasRoot = shellExec("id").indexOf("uid=0") >= 0;
printl("root: " + (hasRoot ? "✓" : "✗"));
if (!hasRoot) {
printl("❌ 需要 root 权限,请在模拟器或 root 手机上运行");
return;
}
// 2. 找触摸设备
printl("🔍 找触摸设备节点...");
var device = findTouchEventDevice();
if (!device) {
printl("❌ 找不到触摸设备");
return;
}
printl("✓ /dev/input/" + device);
// 3. 创建 UI 面板
printl("🎨 创建悬浮面板...");
_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("🔽 DOWN(" + e.x + "," + e.y + ")");
_globalPanel.setStatus("🔽 按下 @ (" + 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("🔼 UP (" + e.x + "," + e.y + ")");
_globalPanel.setStatus("🔼 抬起,分析手势中...", "#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🛑 正在停止...");
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]