折腾了一早上,利用安卓手机的陀螺仪控制鼠标,理论上(理论上)可以隔空玩弄,像激光笔一样指哪里打哪里。
不过精准性还有待待待提高。
使用方法:
1 电脑端打开,mouseFly.exe,在右下角显示出一个黑猫图标即表示运行成功(测试环境WIN11 Python3.10)。 双击黑猫可隐藏黑猫,但程序并未退出(因为我不知道如何结束 asyncio.get_event_loop()。
2 安卓安卓安卓 手机安装 P娃儿猫mouseFly_v9.0.2.apk ,必须开启悬浮窗权限,启动以后没有界面,但会显示一个悬浮的黑猫(自动吸附在屏幕边缘),然后根据提示输入电脑端的IP地址加端口(默认端口2309),连接上就开始晃动手机吧,电脑上的鼠标会飞起来的。
(1) 手机沿X轴方向(俯仰)转动,控制鼠标上下。
(2) 手机沿z轴方向(拧螺丝)转动,控制鼠标左右。(为什么不是Y轴,因为我的手机不准,其它手机就不知道了,代码中有注释,可以改)。
(3)点击黑猫图标单击鼠标左键,长按黑猫退出程序(尤其是发现电脑端鼠标失控时)。
老规则,上源码:
下载地址:https://pan.baidu.com/s/1W9RbrEIu0J3402DccRHOoQ?pwd=4qm1
提取码:4qm1
电脑端 win11 pypthon3.10.9
[Asm] 纯文本查看 复制代码
import asyncio
import websockets
import pyautogui
import json
import os
from infi.systray import SysTrayIcon
sw, sh = pyautogui.size()
pyautogui.FAILSAFE = False
async def deal(websocket, path):
async for message in websocket:
try:
o = json.loads(message)
if (o.get("type") is not None):
if (o["type"] == "move"):
pyautogui.moveTo(o["x"]/10000*sw, o["y"]/10000*sh)
# pyautogui.moveTo(0.5*sw, o["y"]/3000*sh)
if (o["type"] == "mouseDown"):
print("server:", message)
pyautogui.click()
except Exception as e:
print(str(e))
def onExit(*self, **args):
print("exit")
systray = SysTrayIcon(os.path.dirname(__file__) + "/Pwaerm.ico", "P娃儿猫鼠标助手", (), on_quit=onExit)
systray.start()
try:
startServer = websockets.serve(deal, "", 2309)
asyncio.get_event_loop().run_until_complete(startServer)
asyncio.get_event_loop().run_forever()
except Exception as e:
print(str(e))
systray.shutdown()
安卓端,基于auto.jsPro 8.3.16制作:
[Asm] 纯文本查看 复制代码
//-----------------------悬浮窗------------------------------------
function showAdvancedBtn() {
if (advancedWin) {
advancedWin.box.attr("alpha", "0.9");
advancedWin.setTouchable(true);
return;
}
//<fab id="advanced" src="@drawable/ic_create_black_48dp" w="auto" h="auto" margin="20" tint="#000000" alpha="0.5" />
advancedWin = floaty.rawWindow(
<frame id="box" alpha="0.9">
<img id="advanced" circle="true" src="http://www.flash023.cn/Pwaerm/icon/Pwaerm.png" w="50" h="50" margin="20" alpha="0.8" />
</frame>
);
setTimeout(function () {
advancedWin.setPosition(device.width * 0.8, device.height * 0.7);
advancedWin.setTouchable(true);
}, 100);
function getDistance(_d, _d2) {
var _xd = Math.abs(_d.x - _d2.x);
var _yd = Math.abs(_d.y - _d2.y);
return Math.sqrt(_xd * _xd + _yd * _yd);
}
function moveTo(_obj, _point, _t) {
var __t = _t * 1000 / 30;//每秒10帧
var _T = setInterval(function () {
var _x = _obj.getX() + (_point.x - _obj.getX()) * 0.3;
var _y = _obj.getY() + (_point.y - _obj.getY()) * 0.3;
_obj.setPosition(_x, _y);
if (getDistance({ x: _x, y: _y }, _point) < 90) {
_obj.setPosition(_point.x, _point.y);
clearInterval(_T);
}
}, __t);
}
//记录按键被按下时的触摸坐标
var downPoint;
//记录按键被按下时的悬浮窗位置
var windowPoint;
//记录按键被按下的时间以便判断长按等动作
var downTime;
var downInterval;
var offsetX = 80;
var isLongTouch = false;
advancedWin.advanced.setOnTouchListener(function (view, event) {
switch (event.getAction()) {
case event.ACTION_DOWN:
downPoint = { x: event.getRawX(), y: event.getRawY() };
windowPoint = { x: advancedWin.getX(), y: advancedWin.getY() };
downTime = new Date().getTime();
isLongTouch = false;
downInterval = setInterval(() => {
var _d = getDistance({ x: event.getRawX(), y: event.getRawY() }, downPoint);
//console.log(_d < 100);
if (new Date().getTime() - downTime > 1600 && _d < 100) {
isLongTouch = true;
console.log("长按");
clearInterval(downInterval);
stopOtherEngines(true);
} else {
if (_d > 100) {
clearInterval(downInterval);
}
}
}, 100);
return true;
case event.ACTION_MOVE:
if (isLongTouch) {
return true;
}
//移动手指时调整悬浮窗位置
advancedWin.setPosition(windowPoint.x + (event.getRawX() - downPoint.x), windowPoint.y + (event.getRawY() - downPoint.y));
return true;
case event.ACTION_UP:
if (downInterval != undefined) {
clearInterval(downInterval);
}
if (isLongTouch) {
return true;
}
//手指弹起时如果偏移很小则判断为点击
if (getDistance({ x: event.getRawX(), y: event.getRawY() }, downPoint) < 100) {
advancedWin.setPosition(windowPoint.x, windowPoint.y);
if (new Date().getTime() - downTime < 1000) {
console.log("短按");
events.broadcast.emit("MOUSE_DOWN", "");
}
if (new Date().getTime() - downTime > 3000) {
console.log("长按后弹起");
}
} else {
var _x = event.getRawX() > device.width * 0.5 ? device.width - advancedWin.width * 0.5 - offsetX : offsetX - advancedWin.width * 0.5;
var _y = windowPoint.y + (event.getRawY() - downPoint.y);
if (event.getRawY() < device.height * 0.1) {
_y = device.height * 0.1;
}
if (event.getRawY() > device.height * 0.8) {
_y = device.height * 0.8;
}
_y -= advancedWin.height * 0.5;
moveTo(advancedWin, { x: _x, y: _y }, 0.5);
}
return true;
}
return true;
});
}
function stopOtherEngines(_exit) {
if (workThread && workThread.isAlive()) {
workThread.interrupt();
}
workThread = null;
if (_exit) {
engines.myEngine().forceStop();
}
}
function startWorkEngines() {
stopOtherEngines();
workThread = threads.start(workHandler);
}
function showUrlInput(_title) {
var _url = rawInput(_title, storage.get("url"));
if (_url) {
if (_url && _url.length >= 4) {
storage.put("url", _url);
showContentDialog("地址已经保存,尝试连接中...");
showAdvancedBtn();
}
};
}
if (typeof storage == "undefined") {
storage = storages.create("mouseFly");
}
function showContentDialog(_title) {
hideConnectDialog();
connectDialog = dialogs.build({
title: _title,
progress: {
max: -1
},
cancelable: false
}).show();
if (!_title) {
toastLog("呼叫超时,请检查...");
}
setTimeout(hideConnectDialog, 5000);
}
function hideConnectDialog() {
if (connectDialog) {
connectDialog.dismiss();
connectDialog = null;
}
}
function init() {
if (!storage.get("url")) {
showUrlInput("请输入电脑端的IP地址和端口号:");
} else {
showContentDialog("正在呼叫电脑端...");
showAdvancedBtn();
}
events.broadcast.on("SHOW_URL_INPUT", showUrlInput);
events.broadcast.on("HIDE_CONNECT_DIALOG", hideConnectDialog)
startWorkEngines();
setInterval(() => { }, 1000);
}
var advancedWin;
var workThread;
var connectDialog;
//-------------------------主要功能区-------------------------
function workHandler() {
var azimuth;
var data;
sensors.register("gyroscope").on("change", (event, _x, _y, _z) => {
//console.log(Math.floor(_y * 1000));
})
sensors.register("orientation").on("change", (event, _x, _y, _z) => {
/*
event SensorEvent 传感器事件,用于获取传感器数据变化时的所有信息
_x {number} 方位角,从地磁指北方向线起,依顺时针方向到y轴之间的水平夹角,单位角度,范围0~359
_y {number} 绕x轴旋转的角度,当设备水平放置时该值为0,当设备顶部翘起时该值为正数,当设备尾部翘起时该值为负数,单位角度,范围-180~180
_z {number} 绕z轴顺时针旋转的角度,单位角度,范围-90~90
*/
//我的手机指南针方向不准,所以改为Z轴方向
_x = -_z;
if (azimuth == undefined) {
azimuth = Math.floor(_x);
}
var _v = 20;
_x -= azimuth;
if (_x < -_v) {
_x = -_v;
}
if (_x > _v) {
_x = _v;
}
_x = Math.floor((_v + _x) / (_v * 2) * 10000);
//console.log(_x)
//限制垂直方向的角度为+-20,精度为10000
if (_y < -_v) {
_y = -_v;
}
if (_y > _v) {
_y = _v;
}
_y = Math.floor((_v + _y) / (_v * 2) * 10000);
data = { type: "move", x: _x, y: _y };
if (ws) {
ws.send(JSON.stringify(data));
}
});
events.broadcast.on("MOUSE_DOWN", () => {
if (ws) {
console.log("---------------短按------------------");
data = { type: "mouseDown" };
ws.send(JSON.stringify(data));
}
})
function socketInit() {
if (!storage.get("url")) {
log(storage.get("url"));
events.broadcast.emit("SHOW_URL_INPUT", "");
return;
}
ws = web.newWebSocket("ws://" + storage.get("url"), {
eventThread: 'this'
});
ws.on("open", (res, ws) => {
events.broadcast.emit("HIDE_CONNECT_DIALOG", "");
toastLog("WebSocket连接成功!");
})
ws.on("failure", (err, res, _ws) => {
//log("WebSocket连接失败");
//console.error(err);
toastLog("socket连接失败,请确认电脑端软件已经开启且地址输入正确。");
events.broadcast.emit("HIDE_CONNECT_DIALOG", "");
events.broadcast.emit("SHOW_URL_INPUT", "socket连接失败,请确认电脑端软件已经开启且地址输入正确。");
ws.removeAllListeners();
ws = null;
setTimeout(socketInit, 5000);
})
ws.on("closed", (code, reason, _ws) => {
ws.removeAllListeners();
ws = null;
});
}
var ws;
socketInit();
setInterval(() => { }, 3000);
}
init();