源码
效果
可以通过adb 把电脑的鼠标和键盘事件转发给一个备用机,这个备用机被模拟成蓝牙鼠标和键盘。用自己的主力手机连接。就可以在电脑和手机之前快捷切换输入事件。
从一个"摸鱼"小工具里能学到什么
最近做了一个有点特别的小工具:用 Windows 电脑的鼠标和键盘,去控制另一台支持蓝牙键鼠输入的手机。它不投屏,也不读取被控手机画面,只把电脑上的输入事件转成蓝牙 HID 输入。
这个项目表面上是一个摸鱼工具,实际实现下来,会碰到不少很值得学习的知识点:Windows 全局输入 Hook、ADB 端口转发、Android 前台服务、蓝牙 HID Device、HID 报告描述符、托盘程序、移动端图标适配,以及最后如何把一个原型整理成可以分享的开源项目。
整体思路
项目里有三个角色:
- Windows 电脑:采集鼠标和键盘事件。
- Android 中转手机:接收 Windows 发来的输入命令,并伪装成蓝牙键盘鼠标。
- 被控手机:通过蓝牙连接中转手机,把它当成外接键盘鼠标使用。
这套方案的关键点是:被控手机不需要安装任何 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 端要做两件事:
- 在用户点击"控制手机"后捕获鼠标和键盘。
- 在用户按
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]
这里的 dx、dy 是相对移动量,wheel 是滚轮,buttons 表示左键、右键、中键等按钮状态。
调试 HID 的时候,最麻烦的是系统可能会缓存蓝牙设备描述符。如果你改了描述符,但被控手机还按旧描述符解析,就会出现"明明发了鼠标数据,却没有反应"这种情况。遇到这种问题,往往需要取消配对、重新配对,甚至重启蓝牙。
知识点七:桌面程序不只是一个窗口
Windows 端一开始只是一个 Tkinter 窗口。后来为了更像日常工具,补了几个能力:
- 点击右上角关闭按钮时退到系统托盘。
- 托盘菜单可以重新打开控制中心。
- 托盘菜单可以开始/停止控制。
- "退出程序"才真正停止 Hook 和网络线程。
- 快捷键默认使用
Ctrl+Shift+M,也支持在界面里修改。
这些细节看起来不大,但它们决定了工具能不能自然地挂在后台使用。
一个小经验是:关闭窗口和退出程序最好区分开。很多工具型程序里,点 X 更像"隐藏到后台",真正退出应该放到菜单或明确按钮上。
知识点八:Android 图标为什么会显示不全
图标适配也踩了坑。
一开始只放普通 PNG,系统启动器会把它当旧式图标,自动套一层白色底板,看起来就像图标没有铺满。
后来改成 adaptive icon,自适应图标可以让背景铺满系统图标容器。但又出现了另一个问题:前景元素太靠边时,会被圆角或圆形遮罩裁掉。
最后的处理方式是:
- 背景负责铺满。
- 前景放在 adaptive icon 的安全区内。
- 旧设备 fallback PNG 也保留安全边距。
这就是 Android 图标适配里很典型的一课:铺满和不裁切是两件事,需要同时考虑。
知识点九:调试要分层验证
这种跨设备链路最怕一句"不能控制"就从头猜。更可靠的做法是分层验证:
- Windows 是否捕获到了输入?
- Windows 是否把命令发到了本地 TCP 端口?
- ADB forward 是否把端口转到了 Android?
- Android TCP 服务是否收到命令?
- Android 是否成功注册蓝牙 HID?
- 被控手机是否把中转手机识别成鼠标/键盘?
- HID report 的 ID 和长度是否匹配描述符?
每一层都有自己的日志或检查命令。只要能把链路拆开,问题就不会变成玄学。
知识点十:从原型到开源项目
最后一步是把项目整理成别人能用的形态:
- 根目录放成品 APK。
- README 不只写源码结构,更要写使用流程。
.gitignore忽略构建缓存、日志、配置文件。- 对需要发布的 APK 单独放行。
- 提交前检查 diff,避免把本地环境文件带进仓库。
开源项目的第一印象往往不是代码,而是别人能不能在一分钟内知道它是什么、怎么装、怎么跑。
总结
这个项目很小,但它串起了不少系统编程和移动端开发知识:
- Windows 全局输入监听。
- TCP 命令协议。
- ADB USB 端口转发。
- Android 前台服务。
- Bluetooth HID Device。
- HID Report Descriptor。
- 托盘程序和后台退出逻辑。
- Android adaptive icon。
- 开源仓库整理和发布。
它有趣的地方在于:没有试图"黑进"被控手机,也没有依赖投屏,而是换了一个角度,把输入伪装成标准蓝牙外设。很多工程问题都是这样,正面看很难,绕到协议和系统能力认可的路径上,反而会变得简单许多。