Windows + macOS,两台机器各接一只 G 鼠标,共用一把 MX 键盘(Logi Bolt / 蓝牙多通道)。 动了哪台机器的鼠标,键盘就自动跟到哪台 —— 不用再按键盘上的 F2 / F3。
Windows macOS
┌────────────────┐ ┌────────────────┐
│ G 鼠标 │ │ G 鼠标 │
│ kbsync │◄──── TCP :52845 ────►│ kbsync │
│ (原生 HID++) │ 局域网协商 │ (原生 HID++) │
└───────┬────────┘ └────────┬───────┘
│ HID++ CHANGE_HOST (0x1814) │
└───────────────► MX 键盘 ◄───────────────┘
Easy-Switch 通道 F2 / F3
罗技官方有 Flow 功能(键盘跟着鼠标跨电脑走),但它只认 Flow 认证的鼠标(MX 系列)。 G 系列(G Pro 等游戏鼠标)走 G HUB,不能当 Flow 的触发源。
于是有两条路:
- 买一只 MX 鼠标来触发 Flow(很多人不接受那只鼠标的手感);
- 自己实现:用 HID++ 的
CHANGE_HOST指令直接切键盘的 Easy-Switch 通道 —— 就是本项目。
多主机键盘同一时刻只对当前主机开放设备:别的机器既看不到它,也发不出指令。 所以:
- 谁持有键盘,谁才有能力把它切走;
- 想从别人那里拿键盘,必须请求对方,由对方执行切换。
kbsync 的每台机器维护一个状态:
| 状态 | 含义 | 本机鼠标动了时 |
|---|---|---|
HOST |
键盘在我这 | 什么都不做(只低频复核一次) |
NOT_HOST |
键盘不在我这 | 向对端发请求;若对端是 HOST,它就把键盘推给我 |
协商报文是一行 JSON(见文末「协议」),跑在局域网 TCP 52845 上,带一个 secret 防止被同网段的人乱发。
本项目在 MX Mechanical Mini + Logi Bolt 接收器 + 蓝牙双通道 这套硬件上反复实测,得到几个很关键的事实:
| 事实 | 影响 |
|---|---|
| 键盘空闲约 1 分钟就进入深度休眠,休眠后不再回应任何 HID++ 报文 | 休眠期间任何机器都切不动它,只能靠人敲一下键唤醒 |
| 每 500ms 发保活报文并不能阻止它休眠 | 保活只保证「醒着的时候随时能切」,不保证「一直醒着」 |
| 只有键盘所在那台机器能推它走 | 两端都要实现原生 HID++ 才能双向切换,单端做不到 |
macOS 上那个接收器不回复 getHostInfo(读通道),只回 getFeature(探存在) |
判断「键盘在不在我這」必须用 getFeature;读通道失败 ≠ 键盘不在 |
| 键盘回包本身间歇性(同样 400ms 超时,有时有有时没有) | 发现设备要记住上次成功的槽位,一次全量扫描十几秒会错过「刚醒」的窗口 |
| macOS 的输入监控权限按文件路径 + 二进制哈希绑定 | 每次重新编译 tools/mxhid.c 都要重新授权一次 |
| 接收器会替「已配对但不在本机」的设备应答 | 在 macOS 上「设备是否在我这」会假阳性 → 该端只能靠「切换后复核」判定成败 |
| 设备睡着时写入会被丢弃,而「切换后设备不再应答」和「设备一直没醒」长得一样 | 会造成假成功(两边都以为切过去了,其实没动)→ 必须切换后复核,且切换前先确认设备醒着 |
| 进程不在交互会话里时读不到光标 | Windows 端必须登录后启动(启动文件夹 / 任务计划「仅用户登录时」),否则永远检测不到鼠标活动 |
对半睡的键盘写一次 setCurrentHost 只会把它「叫醒」(设备沉默约 2 秒后重连,但仍停在原通道),要等它醒透再写一次才会真正切换 |
原生后端改成反复写入直到通道真的变了;键盘醒着(刚打过字)时一次就中,约 0.3 秒 |
| Windows 默认允许两个进程绑同一端口(SO_REUSEADDR) | 会出现两个 agent 同时监听、请求被随机分发 → 已改为独占绑定,第二个实例直接退出 |
因此实际使用节奏是:换到某台机器后顺手敲一下键盘,之后那台就能随时把它切走; 如果离开时那台机器上很久没敲过字(键盘已睡),就切不动,需要手动按 F2 / F3。
| 方向 | 实测结果 |
|---|---|
| Windows → Mac(动 Mac 鼠标) | ✅ 1~3 秒,键盘立刻到 Mac |
| Mac → Windows(动 Windows 鼠标) | ✅ 前提:Mac 上刚敲过字;否则失败 |
kbsync.py 协议层 + 状态机 + 平台抽象(只依赖标准库)
hidpp_win.py Windows 原生 HID++ 控制器(ctypes 直连 hid.dll,常驻句柄)
tools/mxhid.c macOS 原生 HID++ 助手(常驻进程,毫秒级,需输入监控授权)
tools/cross_check.py 两台机器之间的连通性自检(不碰硬件)
tools/cursor_watch.py 光标采样,确认活动检测是否生效
tools/mac_probe_check.py macOS/Linux 端手动体检
| 后端 | 平台 | 单次操作 | 说明 |
|---|---|---|---|
hidpp(原生) |
Windows | 16~31 ms | 默认,常驻句柄 + 500ms 保活 + 缓存发现结果 |
mxhid(原生助手) |
macOS | ~0.4 s | 更快,但要给二进制授「输入监控」;重编译后需重新授权 |
mxswitch |
两端 | ~0.8 s | 通用兜底(MIT,Windows/macOS/Linux 都有) |
- Python 3.9+(只用标准库,无需 pip 依赖)
- 一把支持 Easy-Switch 的 MX 键盘(MX Keys / MX Mechanical / MX Keys Mini …)
- 两台机器在同一局域网,能互相访问
git clone https://github.com/<你的账号>/kbsync.git
cd kbsync
copy config.example.json config.json
# 编辑 config.json(见下)Windows 端用仓库自带的原生后端,不需要装任何驱动或 SDK。
git clone https://github.com/<你的账号>/kbsync.git
cd kbsync
cp config.example.json config.json
# 方案 A:直接用 mxswitch(最省事,只需一次授权)
git clone https://github.com/marcocosta97/mxswitch
cd mxswitch && make && cd ..
# 给 mxswitch 授「输入监控」:系统设置 → 隐私与安全性 → 输入监控
# 方案 B:编译本仓库的原生助手(更快)
cd tools
clang -O2 -Wall -Wextra -o mxhid mxhid.c \
-framework IOKit -framework CoreFoundation -framework CoreGraphics
codesign -s - mxhid
./mxhid --setup # 打开「输入监控」,把 mxhid 加进去并打开开关
cd ..重新编译
mxhid.c后必须重新授权:macOS 的输入监控绑定「路径 + 二进制哈希」, 换了文件就失效(表现是助手能打开设备却收不到任何回包)。
config.json(两台各一份,字段对称):
my_channel 对应键盘上的 Easy-Switch 键(F1/F2/F3)。
python kbsync.py --config config.json # 前台运行
python kbsync.py --config config.json --probe # 体检:设备/通道/对端连通性
python kbsync.py --config config.json --self-test # 自检鼠标活动检测
python kbsync.py --config config.json --dry-run # 只记日志,不真的切键盘Windows(任务计划程序,隐藏窗口):
schtasks /Create /TN "kbsync" /SC ONLOGON /RL LIMITED ^
/TR "\"C:\path\pythonw.exe\" \"C:\path\kbsync.py\" --config \"C:\path\config.json\""或者直接双击 run-windows.bat(先改好里面的路径),或者把 run-windows.bat
复制到 shell:startup 目录让它登录自启。
⚠️ 必须跑在交互会话里(登录之后启动)。跑在服务/会话 0 里会读不到光标位置, agent 会一直报「鼠标采样失败」,也就永远检测不到你在用哪台机器。
强烈建议加看门狗:进程可能因为各种原因(系统清理、脚本管理工具、崩溃)悄悄消失,
而「进程没了」和「键盘切不动」在体感上完全一样。tools/kbsync-watchdog.vbs 每 30 秒检查一次,
发现 agent 不在就把它拉起来:
copy tools\kbsync-watchdog.vbs "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\"
wscript "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\kbsync-watchdog.vbs" # 立刻生效看门狗做了单例保护(已有实例在跑就直接退出);agent 自身也有第二道保险: 启动时发现端口已被占用就退出,避免两个实例抢同一个通道。
⚠️ 写.vbs启动脚本的两个坑(都踩过):
sub是 VBScript 的保留字,For Each sub In ...会直接报 「缺少标识符 / Microsoft VBScript 编译器错误 800A03F2」,脚本根本不会运行;cscript/wscript按 ANSI 解析.vbs,文件里混了中文(UTF-8)可能 被当成乱码导致语法错误 →.vbs一律写纯 ASCII。排查方法:
cscript //nologo 你的.vbs,语法错会直接打印错误行号。
macOS(launchd):把 com.kbsync.agent.plist 里的路径改成你自己的,然后
cp com.kbsync.agent.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.kbsync.agent.plist日志默认在 ~/.kbsync/kbsync.log(自动轮转)。
| 现象 | 排查 |
|---|---|
| 完全没反应 | 先跑 --probe:看「判定键盘在本机」和对端连通性 |
--probe 显示键盘不在本机 |
键盘在另一台机器上,或已休眠 → 敲一下键盘唤醒 |
| 切不动(一直 busy) | 键盘在当前这台机器上且已休眠 → 在那台机器敲一下键 |
| 刚切过去就跳回来 | 两边鼠标都在动;调大 switch_cooldown_ms、activity_move_px |
| 一动鼠标就切,太灵敏 | 调大 activity_move_px(24~32)、switch_cooldown_ms |
| macOS 助手收不到任何回包 | 输入监控没授权,或二进制重编译后没重新授权 → ./mxhid --setup |
| Windows 端探测不到 | 键盘休眠;或被对端切走;或接收器未插好 |
跨机连通性自检(不碰硬件):
# A 机
python3 tools/cross_check.py serve --port 52860 --secret s3cret
# B 机
python3 tools/cross_check.py call --peer <A的IP> --port 52860 --secret s3cret --channel 2
# 期望:ping -> ok / active -> {'result': 'switched'} / RESULT: PASSpython -m unittest discover -s tests -v # 29 个用例:检测器 / 状态机 / 回环 TCP
python -m py_compile kbsync.py hidpp_win.py协议(TCP,一行一个 JSON):
// 请求
{"v":1,"type":"active","machine":"win","channel":2,"secret":"..."} // 请求把键盘交到通道 2
{"v":1,"type":"status","machine":"probe","secret":"..."} // 查询对方状态
{"v":1,"type":"ping","machine":"probe","secret":"..."} // 探活
// 响应
{"result":"switched"} // 对端持有键盘,已推过来
{"result":"not_host"} // 对端没有键盘(我这儿也没有)
{"result":"busy"} // 对端正在使用(打字保护)/ 冷却中 / 切不动(可能休眠),稍后重试
{"result":"error"} // 校验失败 / 对端异常
{"result":"pong"} // ping
{"result":"status","machine":"mac","state":"host","channel":3,"device":true}排查小技巧:直接对某一端发 status,能看到它认为的状态、读到的通道、是否看到设备:
printf '{"v":1,"type":"status","machine":"probe","secret":"<你的 secret>"}\n' | nc 192.168.31.122 52845- HID++ 协议细节参考 Logitech 公开的 HID++ 规范;
- mxswitch(MIT,作者 marcocosta97)——跨平台切换键盘通道的实现思路与报文格式,本项目在 macOS 上也直接使用它;
tools/mxhid.c与hidpp_win.py是本项目为「常驻句柄 + 毫秒级响应」重写的实现。
本项目采用 MIT 许可,详见 LICENSE。使用前请确认你的键盘支持 Easy-Switch(多通道)。
{ "machine": "win", // 两端不能重名 "my_channel": 2, // 本机对应的键盘通道(Windows=F2 → 2,Mac=F3 → 3) "peer": "192.168.1.23", // 对端 IP(建议固定 IP 或 DHCP 保留) "port": 52845, // 两端一致 "secret": "换成只有你知道的一串字符", // 两端一致 "backend": "auto", // auto | hidpp | mxhid | mxswitch "mxhid_path": "/Users/you/kbsync/tools/mxhid", // macOS 原生助手路径 "mxswitch": "/Users/you/mxswitch/mxswitch", // mxswitch 后端路径 "keepalive_ms": 2000, // 定期探测/保活的间隔 "release_idle_ms": 0, // >0 启用打字保护(会被鼠标抖动误触发,默认关) "switch_cooldown_ms": 2500, // 一次切换后的冷却,防两边来回抢 "activity_move_px": 16, // 350ms 内位移超过多少像素算「在用这只鼠标」 "host_recheck_ms": 15000, // HOST 状态多久复核一次 "log_level": "INFO" }