给带 QMK 功能的 Keychron 键盘增加 Apple 的 Globe 键
为什么又发神经?
一个全屏快捷键引发的故事
事情是这样的,某天我在用倒霉的 Microsoft Teams 开会的时候,我习惯性地使用 ^ + ⌘ + F
快捷键尝试全屏显示。
结果反复按了几次快捷键都没有反应,我就奇了怪了,为什么不行呢?以前都可以的。
结果发现 Teams 的快捷键变了,跟原来的不一样了。如下图:
是不是只有 Teams 在作妖,其它的应用呢?是不是快捷键也变了?
于是我打开了万年不用的 Safari 和 Chrome,hmm……
buer,苹果你这么玩儿是吧?!没事儿就瞎改快捷键是吧?!
行行行~~~你赢了,我改习惯还不行吗?不就是 🌐︎ + F
吗?
盐鹅,一顿操作猛如虎,发现我这「高级」的 Keychron 货,并不是尊贵的苹果键盘,根本没有 🌐︎
键啊……
自信满满的 QMK + VIA 之旅
那怎么办?简单,咱这可是带 QMK 固件,支持 VIA 的高级货。
理论上来说,简单地修改一下配置,就可以实现 🌐︎
键的映射。
又天真了不是?!人家苹果的 🌐︎
键,那可是仅属于苹果的存在,「杂牌货」的 Keychron 不配。
就算在 Keychron 里面配置了 fn
键,也无法在 macOS 上成功激活 🌐︎
键。
探索之旅
不信邪的我,开始了漫长的寻找解决方案之路……
历代的苹果 Magic Keyboard
寻找的过程中,发现了一些苹果键盘变化的蛛丝马迹。
比如说,第一代的苹果 Magic Keyboard 是没有 🌐︎
键的,只有 fn
键。如下图:
然后到了第二代的时候,苹果在键盘的右上角增加了一个 🌐︎
键和 fn
键共用的按键。如下图:
到了最新的第三代嘛,你懂的,苹果直接把 🌐︎
和 fn
键布置到了键盘的左下角,成为了新的「修饰键」(Modifier Keys)。如下图:
至此,恭喜 fn
键成功晋级为 🌐︎
键,并成为了 macOS 中的「修饰键」(Modifier Keys)。
从此在 macOS 中,和 Control
、Option
、Command
一样,拥有了「一席之地」。
开始找解题思路
看一下 QMK 官方的 Issue 里面就有一个 Add support for globe key (🌐︎︎) for macOS/iPadOS/iOS 的讨论。
发现并没有什么好的解决方案,然后就顺藤摸瓜,找到了 QMK Apple Fn Key 这篇文章。
文中提到的方法,需要修改 USB 设备的 VID 和 PID,伪装成苹果的键盘,才能在 macOS 中正常使用。
个人使用来看似乎没什么问题,但是 VID 和 PID 也是要通过 USB 联盟认证的,直接修改 VID 和 PID 似乎有点不太优雅。
不过在搜索的过程中,发现了一个苹果的 Accessory Design Guidelines 文档,里面提到了苹果的 🌐︎
键。哎嗨,这不就是我想要的吗?
又到了快乐编程时间
经过了 N 个小时漫长的代码阅读,终于掌握了 Keychron QMK 实现的精髓。
这里省略一万字的心酸历程,直接上干货。
增加相对应的 Keycode
data/constants/keycodes/keycodes_0.0.2_basic.hjson
原始的 QMK 中,并没有 KC_GLOBE
这个 Keycode,所以需要我们自己增加。
先修改 data/constants/keycodes/keycodes_0.0.2_basic.hjson
文件,增加相对应的 Keycode。
1 | "0x00C3": { |
在原文件中,0x00C2
是 KC_LAUNCHPAD
的 Keycode,所以这里我们使用 0x00C3
作为 KC_GLOBE
的 Keycode。
并把 KC_GLOB
作为 KC_GLOBE
的别名。
quantum/keycodes.h
接下来修改 quantum/keycodes.h
文件,在 enum qk_keycode_defines
中增加相对应的 Keycode。
1 | enum qk_keycode_defines { |
还记得苹果的 Accessory Design Guidelines 文档吗?
其中 KC_GLOBE
键对应的 Usage ID 是 0x029D
,名称是 AC Keyboard Layout Select
,属于 HID Consumer Page
。
所以我们还需要对应的修改 IS_CONSUMER_KEYCODE
和 CONSUMER_KEYCODE_RANGE
的宏定义。
把原来的 KC_LAUNCHPAD
(0x00C2) 宏定义替换为数值更高的 KC_GLOBE
(0x00C3)。
1 |
tests/test_common/keycode_table.cpp
修改 tests/test_common/keycode_table.cpp
文件,增加相对应的 Keycode。
1 | std::map<uint16_t, std::string> KEYCODE_ID_TABLE = { |
至此,Keycode 部分的添加就算修改完成了。
修改按键触发逻辑
按键 Keycode 的定义有了,接下来就是修改按键触发逻辑了。
HID Consumer 上报
文件:tmk_core/protocol/report.h
要使得新增的 Keycode 可用,我们需要修改 HID Consumer 相关的代码。
本来我还想根据苹果的规范,自己添加映射的,但是发现已经有好心人做好了映射,如下:
1 | enum consumer_usages { |
那我们就直接使用这个映射,来修改 KEYCODE2CONSUMER
函数,增加对 KC_GLOBE
按键的支持。
1 | static inline uint16_t KEYCODE2CONSUMER(uint8_t key) { |
Keychron 部分的实现
因为我使用的是 Keychron 的键盘,所以修改的方式跟直接用 QMK 的不大一样。
需要修改 keychron_common.h
和 keychron_common.c
这两个文件,来增加对 KC_GLOBE
按键的支持。
文件:keyboards/keychron/common/keychron_common.h
这个很简单,只需要在 enum
的 NEW_SAFE_RANGE
之前增加一个新的按键定义即可。
1 | enum { |
需要注意的是 KC_APFN
是我根据 Keychron 的固件代码,自己定义的一个按键。
这个名字不能与之前定义的 KC_GLOBE
相同,因为 KC_GLOBE
是 QMK 的 Keycode,而 KC_APFN
是 Keychron 的 Keycode。
这里一定要区分清楚,不要混淆了,后面在配置键盘 Keymap 的时候,还会用到这个定义。
文件:keyboards/keychron/common/keychron_common.c
这个文件的修改也很简单,只需要在 process_record_user
函数中,增加对 KC_APFN
按键的判断即可。
1 | bool process_record_keychron_common(uint16_t keycode, keyrecord_t *record) { |
这里就是把 Keychron 的 KC_APFN
转换为 QMK 的 KC_GLOBE
并通过 HID Consumer 协议发给系统。
当按键按下的时候,通过 register_code(KC_GLOBE);
注册 KC_GLOBE
按键。
当按键释放的时候,通过 unregister_code(KC_GLOBE);
注销 KC_GLOBE
按键。
特定键盘的配置
经过上面的修改,Keychron 的键盘就可以正常使用 KC_GLOBE
按键了。
但是对于特定的键盘,比如我现在正在使用的 K7 Max,我们还需要修改 Keymap 才能正常使用。
Keymap 配置
Keymap 的配置,根据不同的键盘是不一样的。
我这里以我自己修改的 K7 Max 为例,只需把你想作为 🌐︎
的按键定义为 KC_APFN
即可。
1 | const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { |
像上面这样,在 KC_RCMMD
后面增加一个 KC_APFN
按键。
在键盘上的位置,就是空格右边 ⌘
键再右边的 fn1
键,按下去就相当于按下了 🌐︎
键。
对于只使用 🌐︎
键切换输入法的朋友,看到这里就可以结束了。
但是对于想要使用 🌐︎
键作为修饰键的朋友,请继续往下看。
Makefile 配置
文件:keyboards/keychron/k7_max/ansi/rgb/keymaps/tommy/rules.mk
默认情况下,经过上述的修改,我们就可以正常使用 🌐︎
键了。
但这里有一个小问题,就是 QMK 默认情况下会使用不同的 USB Endpoint 来发送数据。
而苹果会把不同 Endpoint 的数据认为是独立的键盘输入,从而导致 🌐︎ + 其他按键
无法正常工作。
比如说,单独按 🌐︎
键,可以正常切换输入法。
但是当我按下 🌐︎ + F
键的时候,系统会认为我分别按下了 🌐︎
键和 F
键,而不是 🌐︎ + F
键。
所以,我们需要增加 KEYBOARD_SHARED_EP = yes
这个配置,让设备以 Shared Endpoint 的方式来发送数据。
这样,苹果会把不同 Endpoint 的数据认为是同一个键盘输入,从而使得 🌐︎ + 其他按键
可以正常工作。
1 | KEYBOARD_SHARED_EP = yes |
如果不使用 VIA 功能的话,到这里就可以结束了。
但是要想临时改个按键什么的,也不能每次都编译固件吧?
既然来都来了,那么……
VIA 支持
文件:keyboards/keychron/k7_max/via_json/k7_max_ansi_rgb_v1.0.json
其实也很简单,只需要在上述文件的 customKeycodes
字段中,增加 APFn
的定义即可。如下:
1 | "customKeycodes": [ |
这样我们就可以在 VIA 中看到我们自定义的 APFn
,也就是 🌐︎
按键了。
需要特别注意的是,我们之前编辑过的 Makefile 文件中,必须要有 VIA_ENABLE = yes
这个配置,否则 VIA 功能无法正常工作。
不过一般默认情况下,新的 Keychron 键盘都是默认开启 VIA 功能的。
综合上面的配置,Makefile 文件内容类似如下:
1 | VIA_ENABLE = yes |
编译固件
我这里选择编译和烧录固件同时进行:
1 | make keychron/k7_max/ansi/rgb:tommy:flash |
编译成功后,会输出类似如下的信息:
1 | Linking: .build/keychron_k7_max_ansi_rgb_tommy.elf [OK] |
表示成功生成了可烧录的二进制文件 keychron_k7_max_ansi_rgb_tommy.bin
。
接下来程序会进入刷机模式,并出现类似的提示:
1 | Flashing for bootloader: stm32-dfu |
这个时候我们需要把键盘上的开关调整到 Cable
模式,然后按住 Esc
键不放,再重新插入 USB 数据线。
找到 DFU
模式的设备后,程序会自动进入烧录阶段,出现类似如下的信息:
1 | dfu-util 0.11 |
当 Erase
和 Download
都完成后,程序会自动退出烧录模式,并提示固件烧录成功。
键盘会自动重启,并进入正常工作模式。
验证 VIA 模式
使用 USB 数据线连接键盘和电脑,并把键盘开关调整到 Cable
模式。
打开 Chrome
浏览器,访问 https://usevia.app/
,会看到类似如下的界面:
点击 Authorize
按钮,选择 Keychron K7 Max
键盘并 Connect
。
对于第一次使用的用户,我们需要开启 Show Design tab
选项,否则无法看到键盘的配置界面。
不管以前是否有加载过配置的 json 文件,我们都需要重新加载一次,因为我们刚才修改了 json 配置文件。
点击 Load
按钮,选择我们刚才修改的 keyboards/keychron/k7_max/via_json/k7_max_ansi_rgb_v1.0.json
文件。
注意:第一次使用的用户,需要重新
Authorize
并Connect
一次键盘才能正常使用。
然后我们切换到首页,就可以看到我们刚才定义的 APFn
按键,并可以正常配置键位了。
彩蛋
最后,给大家分享一个 🌐︎
键的小彩蛋。
实际上,虽然 🌐︎
键与 fn
键共用一个按键,但是它与 fn
键并不相同。
🌐︎
键仅用于系统快捷键,以下是目前我在新版 macOS 系统上会用到的一些快捷方式:
🌐︎ + A
:把焦点转移到 Dock 栏,然后按应用的首字母,并输入↩︎
可以快速切换到该应用。🌐︎ + ⇧ + A
:打开应用程序库(Launchpad),按字母可以快速启动应用。🌐︎ + D
:启动听写🌐︎ + E
:选择 Emoji 表情🌐︎ + H
:显示桌面🌐︎ + C
:显示控制中心🌐︎ + N
:显示通知中心🌐︎ + Q
:启动快速笔记🌐︎︎ + F
:进入全屏模式
源代码
不会吧,不会吧,不会吧,真的有人能坚持看到这里?
好吧,既然你都看到这里了,那么……
你不会真的以为我会跟你分享源代码吧?
恭喜你,答对了!
相关源代码已上传到 GitHub 仓库,感兴趣的朋友可以参考下面的 Commit:
https://github.com/TommyLau/qmk_firmware/commit/f403c031640d618dceeeb73d140ab4bf085ac251
Changelog 如下:
1 | feat: Add support for Apple Globe/FN key (KC_GLOBE) |