我为52pj搞原创 【空鼠】 手机陀螺仪控制电脑端鼠标(有源码)

折腾了一早上,利用安卓手机的陀螺仪控制鼠标,理论上(理论上)可以隔空玩弄,像激光笔一样指哪里打哪里。
不过精准性还有待待待提高。

使用方法:

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)点击黑猫图标单击鼠标左键,长按黑猫退出程序(尤其是发现电脑端鼠标失控时

)。

我为52pj搞原创 【空鼠】 手机陀螺仪控制电脑端鼠标(有源码)

我为52pj搞原创 【空鼠】 手机陀螺仪控制电脑端鼠标(有源码)

我为52pj搞原创 【空鼠】 手机陀螺仪控制电脑端鼠标(有源码)

老规则,上源码:

下载地址:https://pan.baidu.com/s/1W9RbrEIu0J3402DccRHOoQ?pwd=4qm1

提取码:4qm1

电脑端 win11 pypthon3.10.9

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制作:

//-----------------------悬浮窗------------------------------------
function showAdvancedBtn() {
    if (advancedWin) {
        advancedWin.box.attr("alpha", "0.9");
        advancedWin.setTouchable(true);
        return;
    }
    //<fab src="@drawable/ic_create_black_48dp" w="auto" h="auto" margin="20" tint="#000000" alpha="0.5" />
    advancedWin = floaty.rawWindow(
        <frame alpha="0.9">
            <img circle="true" src="https://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();

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 共33条

请登录后发表评论