Toccata in Nowhere.

macOS 键盘键位映射(修饰键替代),蓝牙设备连接自动执行

2022.07.22

对于非为 macOS 设计的蓝牙键盘,需要将 command ⌘ / windows 键与 option ⌥ / Alt 键调换;此外对于 emacs 快捷键,将 caps lock 映射为 control 键则可以极大方便使用。对于蓝牙 4.0 及以上的蓝牙键盘(如 MX Keys / CIY 键盘),在 macOS 中如被识别为 Bluetooth Low Energy 设备,则系统设置中的的修饰键(modifier keys)将无法选择该设备,从而无法进行键位因为修改。本文介绍使用 hidutil 进行键位映射,并使用 launchctl 实现设备连接后自动修改映射表。

hidutil (Human Interface Device) 工具

hidutil 命令行工具可以对 macOS 10.12 以上系统键位进行映射,通过 property 进一步筛选设备后进行修改,例如:

hidutil property --matching '{"Product": "YOUR_DEVICE_NAME"}' \
        --set '{"UserKeyMapping": [{"HIDKeyboardModifierMappingSrc":0x700000039,                                                                  
        "HIDKeyboardModifierMappingDst":0x7000000e0}]}'

0x700000039 (caps lock) 映射为 0x7000000e0 (左 control),同时仅对 YOUR_DEVICE_NAME 设备生效,对其他设备例如 MacBook 内置键盘没有影响。其中 YOUR_DEVICE_NAME 为设备名,可以使用 hidutil list进行查询。

该命令可以多次执行,但效果不会叠加,即每次修改仅对一个按键有效。如果有多个按键需要映射,可以填充多个按键映射的键值,例如:

hidutil property --matching '{"Product": "YOUR_DEVICE_NAME"}' \
--set '{"UserKeyMapping": [{"HIDKeyboardModifierMappingSrc":0x7000000e2,
                "HIDKeyboardModifierMappingDst":0x7000000e3},
                {"HIDKeyboardModifierMappingSrc":0x7000000e6,
                "HIDKeyboardModifierMappingDst":0x7000000e7},
                {"HIDKeyboardModifierMappingSrc":0x7000000e3,                                   
                "HIDKeyboardModifierMappingDst":0x7000000e2}]}'

以上命令可以将 YOUR_DEVICE_NAME 设备的左右 command 和 option 键位的对调。

同时可以对系统偏好设置中修饰键设置不能实现的映射进行修改,例如将 F10、F11 与 F12 映射为静音、音量减与音量加:

{"HIDKeyboardModifierMappingSrc":0x700000044,
"HIDKeyboardModifierMappingDst":0xc000000ea},
{"HIDKeyboardModifierMappingSrc":0x700000045,
"HIDKeyboardModifierMappingDst":0xc000000e9},
{"HIDKeyboardModifierMappingSrc":0x700000043,
"HIDKeyboardModifierMappingDst":0xc000000e2},

另外 Windows 键盘常见的按键 manu 菜单键,在macOS中不能被准确识别,其键位值为 0x700000065,可以通过添加以下键值将 manu 键映射为右 option:

{"HIDKeyboardModifierMappingSrc":0x700000065,
"HIDKeyboardModifierMappingDst":0x7000000e6},

更多键位查询可以参考hidutil官方手册,或者第三方网站 https://hidutil-generator.netlify.app/

可将命令保存为 .sh文件,方便多次执行,或者使用自动操作 Automator 中新建应用程序并添加运行shell脚本方便多次使用。

设备连接自动执行键位映射脚本

以上 hidutil 命令在设备断开连接后重新连接需要重新设置,并且在系统重启后也需要再运行进行设定,并不是非常高雅与方便。因此可以借助 launchctl 服务管理对蓝牙列表进行监看,如果有新增特定的设备即执行脚本进行自动切换。

首先我们将需要切换的设备的命令写入脚本 ~/.keysModifer.sh:

#!/bin/bash

connectedyesorno="$(system_profiler SPBluetoothDataType | awk '/YOUR_DEVICE_NAME/{f=1}/Connected:/ && f{print $2; exit}')"

if [ "$connectedyesorno" == "Yes" ]
then
	hidutil property --matching '{"Product": "YOUR_DEVICE_NAME"}' \
		--set '{"UserKeyMapping": [{"HIDKeyboardModifierMappingSrc":0x700000039,
									"HIDKeyboardModifierMappingDst":0x7000000e0}]}'
									

elif [ "$connectedyesorno" == "No" ]
then
	echo "Not connected..."
else
	echo "ERROR"
fi

以上例子中映射 YOUR_DEVICE_NAME 设备 caps lock 到 control。其中两处的YOUR_DEVICE_NAME设备名称需要使用hidutil list进行查询后修改,同时注意特殊符号的转义。届时可以尝试执行 sh ~/.keysModifer.sh 查看结果,注意要在设备连接时进行尝试。

之后在 ~/Library/LaunchAgents 新建 com.my.keysModifier.plist 文件,加入以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
        <string>com.my.keysModifier</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/sh</string>
        <string>/Users/YOUR_USERNAME/.keyModi.sh</string>
    </array>
        <key>WatchPaths</key>
    <array>
        <string>/Library/Preferences/com.apple.Bluetooth.plist</string>
    </array>
</dict>
</plist>

注意替换 YOUR_USERNAME 为用户名。之后使用

launchctl load ~/Library/LaunchAgents/com.my.keysModifier.plist

启动服务,当/Library/Preferences/com.apple.Bluetooth.plist 文件变化时执行~/.keysModifer.sh 脚本对键位映射进行修改。

停止服务可以使用以下命令:

launchctl unload ~/Library/LaunchAgents/com.my.keysModifier.plist

需要注意的是,对 plish 文件修改后需要关闭服务后重新启动,对 sh 文件修改后则无需特殊修改。

使用以下命令可以进一步删除上文生成的两个文件,恢复原始状态:

rm ~/Library/LaunchAgents/com.my.keysModifier.plist
rm ~/.keysModifer.sh

另外如果有多个设备需要判断并修改,可以在.keysModifer.sh文件中使用多个 connectedyesorno变量判断条件对不同设备的连接状态判断后进行映射修改。

Reference