开发笔记-摸鱼输入法

源码

MoYuInput

效果

可以通过adb 把电脑的鼠标和键盘事件转发给一个备用机,这个备用机被模拟成蓝牙鼠标和键盘。用自己的主力手机连接。就可以在电脑和手机之前快捷切换输入事件。

从一个"摸鱼"小工具里能学到什么

最近做了一个有点特别的小工具:用 Windows 电脑的鼠标和键盘,去控制另一台支持蓝牙键鼠输入的手机。它不投屏,也不读取被控手机画面,只把电脑上的输入事件转成蓝牙 HID 输入。

这个项目表面上是一个摸鱼工具,实际实现下来,会碰到不少很值得学习的知识点:Windows 全局输入 Hook、ADB 端口转发、Android 前台服务、蓝牙 HID Device、HID 报告描述符、托盘程序、移动端图标适配,以及最后如何把一个原型整理成可以分享的开源项目。

整体思路

项目里有三个角色:

  1. Windows 电脑:采集鼠标和键盘事件。
  2. Android 中转手机:接收 Windows 发来的输入命令,并伪装成蓝牙键盘鼠标。
  3. 被控手机:通过蓝牙连接中转手机,把它当成外接键盘鼠标使用。

这套方案的关键点是:被控手机不需要安装任何 App。只要它支持蓝牙键盘鼠标,就可以把 Android 中转手机识别成一个外设。

数据链路大概是这样:

text 复制代码
Windows 鼠标键盘
        |
        v
Windows 控制程序
        |
        v
ADB forward / TCP
        |
        v
Android 中转 App
        |
        v
Bluetooth HID Device
        |
        v
被控手机

这个结构把"采集输入"和"伪装蓝牙外设"拆开了。Windows 擅长做桌面输入采集,Android 手机则天然有蓝牙能力,可以作为中转设备。

知识点一:为什么不是直接用网络控制

最开始很容易想到:能不能电脑直接通过网络把事件发给被控手机?

问题在于,被控手机如果没有安装接收端 App,普通网络包并不能变成系统级鼠标键盘输入。系统输入事件属于比较高权限的能力,应用层不能随便注入。

蓝牙 HID 的好处是它走的是系统认可的外设协议。对被控手机来说,这不是一个远程控制程序,而是一套蓝牙键盘鼠标。因此这个方案更通用,也更接近真实外设行为。

知识点二:ADB forward 的作用

Windows 和 Android 中转手机之间需要通信。最简单的办法是让 Android App 开一个 TCP 服务,Windows 程序连过去。

但是实际使用时,电脑和手机不一定在同一个局域网,甚至公司网络下可能根本互相访问不到。这时 adb forward 就很好用:

bat 复制代码
adb forward tcp:9876 tcp:9876

它的意思是:把电脑本机的 127.0.0.1:9876 转发到 Android 设备上的 127.0.0.1:9876

这样 Windows 程序只需要连接本机端口,ADB 会通过 USB 帮我们把数据送到 Android App。它解决的不是蓝牙问题,而是"电脑和中转手机之间怎么稳定通信"的问题。

这个过程中也能学到一个工程细节:端口不能写死。Windows 端和 Android 端都支持改端口,才能避开端口占用,也方便不同环境调试。

知识点三:Windows 全局输入 Hook

Windows 端要做两件事:

  1. 在用户点击"控制手机"后捕获鼠标和键盘。
  2. 在用户按 Esc 或快捷键时退出控制。

这里用到的是全局输入 Hook。它可以在程序窗口不一定获得焦点时,仍然监听键盘鼠标事件。

输入采集有一个小坑:鼠标如果只按绝对坐标转发,很容易受到屏幕边界影响。项目里改成了"中心锁定 + 相对位移"的模式:进入控制后,把鼠标不断拉回屏幕中心,只把每次偏移量转发出去。

这个思路和很多远程桌面、游戏视角控制类似。它的核心不是"鼠标在电脑屏幕哪里",而是"这一次移动了多少"。

知识点四:Android 前台服务

Android 端需要长时间保持蓝牙 HID 注册和 TCP 服务运行,所以不能只依赖 Activity。

Activity 适合做界面,Service 才适合做后台工作。项目里用的是前台服务,因为 Android 对后台服务限制很严格。前台服务会有一条通知,让系统知道这个 App 正在执行持续任务。

这也解释了为什么通知栏标题、通知渠道、小图标都要处理好:它们不是装饰,而是 Android 长时间运行服务的一部分。

知识点五:Bluetooth HID Device

Android 端最关键的能力是 BluetoothHidDevice。它可以把手机注册成一个蓝牙 HID 外设。

注册时需要提供一组 SDP 信息,例如设备名、描述、厂商信息,以及最重要的 HID Report Descriptor。

简单说:

  • SDP 信息告诉对方"我是什么设备"。
  • HID 描述符告诉对方"我会发送什么格式的输入报告"。
  • sendReport 负责真正发送键盘、鼠标事件。

项目里把设备名统一成了"摸鱼输入法",所以被控手机蓝牙侧看到的外设名也会更一致。

知识点六:HID 报告描述符很挑剔

HID 不是随便发几个字节就能工作的。键盘、鼠标都有自己的报告格式。

这个项目里最终使用的是复合 HID:

  • Report ID 1:键盘。
  • Report ID 2:鼠标。

鼠标报告使用 4 个字节:

text 复制代码
[buttons, dx, dy, wheel]

这里的 dxdy 是相对移动量,wheel 是滚轮,buttons 表示左键、右键、中键等按钮状态。

调试 HID 的时候,最麻烦的是系统可能会缓存蓝牙设备描述符。如果你改了描述符,但被控手机还按旧描述符解析,就会出现"明明发了鼠标数据,却没有反应"这种情况。遇到这种问题,往往需要取消配对、重新配对,甚至重启蓝牙。

知识点七:桌面程序不只是一个窗口

Windows 端一开始只是一个 Tkinter 窗口。后来为了更像日常工具,补了几个能力:

  • 点击右上角关闭按钮时退到系统托盘。
  • 托盘菜单可以重新打开控制中心。
  • 托盘菜单可以开始/停止控制。
  • "退出程序"才真正停止 Hook 和网络线程。
  • 快捷键默认使用 Ctrl+Shift+M,也支持在界面里修改。

这些细节看起来不大,但它们决定了工具能不能自然地挂在后台使用。

一个小经验是:关闭窗口和退出程序最好区分开。很多工具型程序里,点 X 更像"隐藏到后台",真正退出应该放到菜单或明确按钮上。

知识点八:Android 图标为什么会显示不全

图标适配也踩了坑。

一开始只放普通 PNG,系统启动器会把它当旧式图标,自动套一层白色底板,看起来就像图标没有铺满。

后来改成 adaptive icon,自适应图标可以让背景铺满系统图标容器。但又出现了另一个问题:前景元素太靠边时,会被圆角或圆形遮罩裁掉。

最后的处理方式是:

  • 背景负责铺满。
  • 前景放在 adaptive icon 的安全区内。
  • 旧设备 fallback PNG 也保留安全边距。

这就是 Android 图标适配里很典型的一课:铺满和不裁切是两件事,需要同时考虑。

知识点九:调试要分层验证

这种跨设备链路最怕一句"不能控制"就从头猜。更可靠的做法是分层验证:

  1. Windows 是否捕获到了输入?
  2. Windows 是否把命令发到了本地 TCP 端口?
  3. ADB forward 是否把端口转到了 Android?
  4. Android TCP 服务是否收到命令?
  5. Android 是否成功注册蓝牙 HID?
  6. 被控手机是否把中转手机识别成鼠标/键盘?
  7. HID report 的 ID 和长度是否匹配描述符?

每一层都有自己的日志或检查命令。只要能把链路拆开,问题就不会变成玄学。

知识点十:从原型到开源项目

最后一步是把项目整理成别人能用的形态:

  • 根目录放成品 APK。
  • README 不只写源码结构,更要写使用流程。
  • .gitignore 忽略构建缓存、日志、配置文件。
  • 对需要发布的 APK 单独放行。
  • 提交前检查 diff,避免把本地环境文件带进仓库。

开源项目的第一印象往往不是代码,而是别人能不能在一分钟内知道它是什么、怎么装、怎么跑。

总结

这个项目很小,但它串起了不少系统编程和移动端开发知识:

  • Windows 全局输入监听。
  • TCP 命令协议。
  • ADB USB 端口转发。
  • Android 前台服务。
  • Bluetooth HID Device。
  • HID Report Descriptor。
  • 托盘程序和后台退出逻辑。
  • Android adaptive icon。
  • 开源仓库整理和发布。

它有趣的地方在于:没有试图"黑进"被控手机,也没有依赖投屏,而是换了一个角度,把输入伪装成标准蓝牙外设。很多工程问题都是这样,正面看很难,绕到协议和系统能力认可的路径上,反而会变得简单许多。

相关推荐
疯狂打码的少年4 小时前
【计算机网络】TCP/IP协议族(四层体系结构,与OSI对比)
网络·笔记·tcp/ip·计算机网络
xqqxqxxq4 小时前
AI Agent学习:Agent协作工具技术笔记(李博杰《深入理解 AI Agent》4.6观后总结)
笔记·学习
彧azz6 小时前
数据结构:关于图的学习
c语言·数据结构·笔记·学习·算法
自小吃多6 小时前
高云 FPGA 添加时序约束笔记
笔记·fpga开发
彧azz7 小时前
二叉搜索树学习记录
c语言·数据结构·笔记·算法
蒸蒸yyyyzwd7 小时前
机试和Redis学习笔记
c++·笔记·面试
果壳science8 小时前
《自然本源》如何实现狭义相对论的代数化
笔记·算法·几何学
1314lay_10078 小时前
C#调用Sql Server存储过程,并且使用传入表结构
数据库·经验分享·笔记·sqlserver·c#
志尊宝9 小时前
Vue3 零基础每日笔记(015):条件渲染 v-if 与 v-show——控制 DOM 生死还是控制显隐
前端·vue.js·笔记