前言:
以前写过一篇《Mac版程序员喜欢的键位(使用 IDEA)》文章,写了关于在macos Sierra 版本之前,用Karabiner进行键盘映射的方法。但在macos Sierra之后,Karabiner就无法使用了,代替品Karabiner-elements功能也不是很强大。
没有办法,只能使用Hammerspoon来做键盘映射。使用Hammerspoon时,用一些简单的方法,做不到我们想要的结果。因为Hammerspoon在模拟按键时,hs.hotkey.bind方法不支持用 fn 来做修饰键的(原来的文章就是用 fn 做修饰键的。有的方法使用 ctrl + cmd + alt +shift 做 hyper键,但这样就无法使用 hyper + shift/alt/cmd 这样的组合键了)。
最后在网上找了一个脚本,在修改后之后,终于达到我们想的结果了,哈哈哈。
正文
1,使用MacBook自带键盘的设置
1,安装Karabiner-Elements
。下载地址:Karabiner-Elements
2,修改Karabiner-Elements
的Json文件,路径如下:~/.config/karabiner/karabiner.json
。
如果你没有修改过的话,可以使用下面的脚本,把capslock
键,修改成fn
键。
{
"global": {
"check_for_updates_on_startup": true,
"show_in_menu_bar": true,
"show_profile_name_in_menu_bar": false
},
"profiles": [
{
"complex_modifications": {
"rules": [
{
"manipulators": [
{
"description": "Change caps_lock to command+control+option+shift. Escape if no other key used.",
"from": {
"key_code": "caps_lock",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "fn"
}
],
"to_if_alone": [
{
"key_code": "caps_lock",
"modifiers": {
"optional": [
"any"
]
}
}
],
"type": "basic"
}
]
}
]
},
"devices": [],
"fn_function_keys": {
"f1": "display_brightness_decrement",
"f10": "mute",
"f11": "volume_decrement",
"f12": "volume_increment",
"f2": "display_brightness_increment",
"f3": "mission_control",
"f4": "launchpad",
"f5": "illumination_decrement",
"f6": "illumination_increment",
"f7": "rewind",
"f8": "play_or_pause",
"f9": "fastforward"
},
"name": "Default profile",
"selected": true,
"simple_modifications": {},
"virtual_hid_keyboard": {
"caps_lock_delay_milliseconds": 0,
"keyboard_type": "ansi"
}
},
{
"devices": [],
"fn_function_keys": {
"f1": "f1",
"f10": "mute",
"f11": "volume_decrement",
"f12": "volume_increment",
"f2": "display_brightness_increment",
"f3": "mission_control",
"f4": "launchpad",
"f5": "illumination_decrement",
"f6": "illumination_increment",
"f7": "rewind",
"f8": "play_or_pause",
"f9": "fastforward"
},
"name": "New profile",
"selected": false,
"simple_modifications": {},
"virtual_hid_keyboard": {
"caps_lock_delay_milliseconds": 0,
"keyboard_type": "ansi"
}
}
]
}
3,首先要安装Hammerspoon
。下载地址:Hammerspoon
4,安装完后,在屏幕上方,有一个小锤子图标,点击图标,选择open config
。点完后,就出现一个init.lua
编辑画面。
5,把下面的脚本粘贴进去,再点击小锤子图标,点击Reload Config
,脚本就生效了。
脚本如下:(脚本在原来脚本基础之上,修改了3个地方,在下面都有注释)
原来脚本地址:viKeys.lua
local module = {}
module.debugging = false -- whether to print status updates
local eventtap = require "hs.eventtap"
local event = eventtap.event
local inspect = require "hs.inspect"
local watchable = require "hs.watchable"
local keyHandler = function(e)
--+++++++++++++++ 重要:这里是写 “按下的键” 和 “要映射的键” ++++++++++++++++
local watchFor = { j = "left", k = "down", i = "up", l = "right",
u = "alt_left", o = "alt_right",
y = "pageup", h = "pagedown"
}
--+++++++++++++++ 重要:这里也是写 “按下的键” 和 “要映射的键” ++++++++++++++++
--+++++++++++++++ 因为符号(例如 ; )无法像上面那么写,所以用下面的方式设置 ++++++++++++++++
watchFor["p"] = "gohome"
watchFor[":"] = "goend"
watchFor[";"] = "go_and_select_end" -- 在按 fn + shift + ; 时,就变成 fn + :
watchFor["m"] = "delete"
watchFor[","] = "forwarddelete"
watchFor["."] = "return"
watchFor["c"] = "copy"
watchFor["v"] = "paste"
watchFor["x"] = "cut"
watchFor["z"] = "undo"
watchFor["b"] = "gotoDef" -- idea setting
watchFor["d"] = "delLine" -- idea setting
local actualKey = e:getCharacters(true)
local actlKyNoCs = actualKey:lower()
local replacement = watchFor[actualKey:lower()]
--hs.alert(replacement)
if replacement then
-- hs.alert("okokokok")
local isDown = e:getType() == event.types.keyDown
local flags = {}
-- add start
local isShiftPressed = nil
local isAltPressed = nil
local isCmdPressed = nil
local isCtrlPressed = nil
-- add end
for k, v in pairs(e:getFlags()) do
if v and k ~= "fn" then -- fn will be down because that's our "wrapper", so ignore it
table.insert(flags, k)
end
-- add start
if k == "shift" then
isShiftPressed = true
end
if k == "alt" then
isAltPressed = true
end
if k == "cmd" then
isCmdPressed = true
end
if k == "ctrl" then
isCtrlPressed = true
end
-- add end
end
--[[
重要:下面是定义了,什么样的键组合可以进行映射,之外的键组合,都不进行映射,使用原来的键组合
]]
local isPass = false
-- fn + 单个按键的组合
if (not isShiftPressed) and (not isCtrlPressed) and (not isCmdPressed) and (not isAltPressed)
and
(actlKyNoCs == 'i' or actlKyNoCs == 'j' or actlKyNoCs == 'k' or actlKyNoCs == 'l'
or actlKyNoCs == 'u' or actlKyNoCs == 'o' or actlKyNoCs == 'p' or actlKyNoCs == ';'
or actlKyNoCs == 'y' or actlKyNoCs == 'h'
or actlKyNoCs == 'm' or actlKyNoCs == ',' or actlKyNoCs == '.'
or actlKyNoCs == 'v' or actlKyNoCs == 'c' or actlKyNoCs == 'x' or actlKyNoCs == 'z'
or actlKyNoCs == 'b' or actlKyNoCs == 'd') then
--hs.alert("if 1")
isPass = true
-- fn + shift + 单个按键的组合
elseif (isShiftPressed) and (not isCtrlPressed) and (not isCmdPressed) and (not isAltPressed)
and
(actlKyNoCs == 'i' or actlKyNoCs == 'j' or actlKyNoCs == 'k' or actlKyNoCs == 'l'
or actlKyNoCs == 'u' or actlKyNoCs == 'o' or actlKyNoCs == 'p' or actlKyNoCs == ':') then
--hs.alert("if 2")
isPass = true
-- fn + cmd + 单个按键的组合
elseif (not isShiftPressed) and (not isCtrlPressed) and (isCmdPressed) and (not isAltPressed)
and
(actlKyNoCs == 'u' or actlKyNoCs == 'o' or actlKyNoCs == 'i' or actlKyNoCs == 'k') then
--hs.alert("if 2")
isPass = true
-- fn + ctrl + shift + 单个按键的组合 (Idea中,屏幕上下移动)
elseif (not isShiftPressed) and (not isCtrlPressed) and (isCmdPressed) and (isAltPressed)
and
(actlKyNoCs == 'i' or actlKyNoCs == 'j' or actlKyNoCs == 'k' or actlKyNoCs == 'l') then
--hs.alert("if 2")
isPass = true
-- fn + ctrl + alt + 单个按键的组合 (Idea中,复制行或块)
elseif (not isShiftPressed) and (isCtrlPressed) and (not isCmdPressed) and (isAltPressed)
and
(actlKyNoCs == 'i' or actlKyNoCs == 'j' or actlKyNoCs == 'k' or actlKyNoCs == 'l') then
--hs.alert("if 2")
isPass = true
-- fn + ctrl + cmd + 单个按键的组合 (Idea中,行或块上下移动)
elseif (not isShiftPressed) and (isCtrlPressed) and (isCmdPressed) and (not isAltPressed)
and
(actlKyNoCs == 'i' or actlKyNoCs == 'j' or actlKyNoCs == 'k' or actlKyNoCs == 'l') then
--hs.alert("if 2")
isPass = true
end
if not isPass then
return false
end
-- add end
if module.debugging then print("viKeys: " .. replacement, inspect(flags), isDown) end
--+++++++++++++++ 重要 ++++++++++++++++
--[[
上面定义了“按下的键” 和 “要映射的键”,但无法定义组合键(例如:alt + j 想要映射成 alt + righ),
所以在上面定义了一些符号,在这里进行解析
]]
if replacement == 'alt_left' then
table.insert(flags, 'alt')
replacement = 'left'
elseif replacement == 'alt_right' then
table.insert(flags, 'alt')
replacement = 'right'
elseif replacement == 'gohome' then
table.insert(flags, 'cmd')
replacement = 'left'
elseif replacement == 'goend' then
table.insert(flags, 'cmd')
replacement = 'right'
elseif replacement == 'go_and_select_end' then
table.insert(flags, 'cmd')
replacement = 'right'
elseif replacement == 'copy' then
table.insert(flags, 'cmd')
replacement = 'c'
elseif replacement == 'paste' then
table.insert(flags, 'cmd')
replacement = 'v'
elseif replacement == 'cut' then
table.insert(flags, 'cmd')
replacement = 'x'
elseif replacement == 'undo' then
table.insert(flags, 'cmd')
replacement = 'z'
elseif replacement == 'gotoDef' then
table.insert(flags, 'cmd')
replacement = 'b'
elseif replacement == 'delLine' then
table.insert(flags, 'cmd')
replacement = 'd'
end
local replacementEvent = event.newKeyEvent(flags, replacement, isDown)
if isDown then
-- allow for auto-repeat
replacementEvent:setProperty(event.properties.keyboardEventAutorepeat, e:getProperty(event.properties.keyboardEventAutorepeat))
end
return true, { replacementEvent }
else
return false -- do nothing to the event, just pass it along
end
end
local modifierHandler = function(e)
local flags = e:getFlags()
local onlyFNPressed = false
-- add start
local isShiftPressed = false
local isAltPressed = false
local isCmdPressed = false
local isCtrlPressed = false
local modifierCount = 0
-- add end
for k, v in pairs(flags) do
--+++++++++++++++++ 重要 +++++++++++++++++++++--
--[[ 原来是下面被注释掉的语句,把 k == "fn" 变成了 k ~= "shift"。
原因是,这个语句是判断只有 fn 被按下后,才被处理。如果有其它修饰键,同时被按下的话,不一定成功。
最开始把这个语句删除了,就好用了,但出现另外一个问题,按住 shfit + 字母键后,无法打出大写字母,
于是把这个变成了 k ~= "shift",也就是说,这个事件不会响应 修饰键只是Shift的按键组合 ]]
--onlyFNPressed = v and k == "fn"
onlyFNPressed = v
--[[ 原代码修改后有个问题:如果按Shift + key的话,就无法打出大写字符。
修改方法:在循环里统计修饰键的个数,如果 修饰键 = 1 并且 修饰键= 单个Shift/cmd/ctrl/alt 的话,
就使用原来的按键组合,不进行映射。
]]
-- add start
if v and k == "shift" then
isShiftPressed = true
elseif v and k == "alt" then
isAltPressed = true
elseif v and k == "cmd" then
isCmdPressed = true
elseif v and k == "ctrl" then
isCtrlPressed = true
end
modifierCount = modifierCount + 1
-- add end
if not onlyFNPressed then break end
end
--[[
如果 修饰键 = 1 并且 修饰键=Shift 的话,在下面就跳事件。
]]
-- add start
if (isShiftPressed or isAltPressed or isCmdPressed or isCtrlPressed)
and modifierCount == 1 then
if isAltPressed then
-- hs.alert("alt is pressed")
--hs.hotkey.bind({ 'alt' }, 'b', function() smartLaunchOrFocus({ 'Safari' }) end)
end
return
end
-- add end
-- you must tap and hold fn by itself to turn this on
if onlyFNPressed and not module.keyListener then
if module.debugging then print("viKeys: keyhandler on") end
module.keyListener = eventtap.new({ event.types.keyDown, event.types.keyUp }, keyHandler):start()
-- however, adding additional modifiers afterwards is ok... its only when fn isn't down that we switch back off
elseif not flags.fn and module.keyListener then
if module.debugging then print("viKeys: keyhandler off") end
module.keyListener:stop()
module.keyListener = nil
end
return false
end
module.watchables = watchable.new("viKeys", true)
module.modifierListener = eventtap.new({ event.types.flagsChanged }, modifierHandler)
module.start = function()
module.watchables.enabled = true
module.modifierListener:start()
end
module.stop = function()
if module.keyListener then
module.keyListener:stop()
module.keyListener = nil
end
module.modifierListener:stop()
module.watchables.enabled = false
end
module.toggle = function()
if module.watchable.enabled then
module.stop()
else
module.start()
end
end
module.watchExternalToggle = watchable.watch("viKeys.enabled", function(w, p, k, o, n)
if not o and n then
module.start()
elseif o and not n then
module.stop()
end
end)
module.start() -- autostart
return module
2,使用外接键盘
1,打开Karabiner-Elements
的Preferences...
,然后选择Profiles
选项卡,再点击下面的Add Profile
新建一个Profile
,我们命名为PC-87
。
2,修改Command
和Option
键盘的映射。选择新建的PC-87
的Profile
,然后选择第一个选项卡Simple Modifications
,然后分别映射左右的Command
和Option
键。修改方式如下:
3,修改karabiner.json
文件(路径看上面),修改后内容如下:
(下面的修改内容是包括了上面的Default Profile
的内容。再有,如果新的Profile
命名不一样的话,注意修改一下。修改方法是,找到下面文件中的PC-87
,然后修改。)
{
"global": {
"check_for_updates_on_startup": true,
"show_in_menu_bar": true,
"show_profile_name_in_menu_bar": false
},
"profiles": [
{
"complex_modifications": {
"rules": [
{
"manipulators": [
{
"description": "Change caps_lock to command+control+option+shift. Escape if no other key used.",
"from": {
"key_code": "caps_lock",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "fn"
}
],
"to_if_alone": [
{
"key_code": "caps_lock",
"modifiers": {
"optional": [
"any"
]
}
}
],
"type": "basic"
}
]
}
]
},
"devices": [],
"fn_function_keys": {
"f1": "display_brightness_decrement",
"f10": "mute",
"f11": "volume_decrement",
"f12": "volume_increment",
"f2": "display_brightness_increment",
"f3": "mission_control",
"f4": "launchpad",
"f5": "illumination_decrement",
"f6": "illumination_increment",
"f7": "rewind",
"f8": "play_or_pause",
"f9": "fastforward"
},
"name": "Default profile",
"selected": false,
"simple_modifications": {},
"virtual_hid_keyboard": {
"caps_lock_delay_milliseconds": 0,
"keyboard_type": "ansi"
}
},
{
"complex_modifications": {
"rules": [
{
"manipulators": [
{
"description": "Change caps_lock to command+control+option+shift. Escape if no other key used.",
"from": {
"key_code": "caps_lock",
"modifiers": {
"optional": [
"any"
]
}
},
"to": [
{
"key_code": "fn"
}
],
"to_if_alone": [
{
"key_code": "caps_lock",
"modifiers": {
"optional": [
"any"
]
}
}
],
"type": "basic"
}
]
}
]
},
"devices": [],
"fn_function_keys": {
"f1": "f1",
"f10": "mute",
"f11": "volume_decrement",
"f12": "volume_increment",
"f2": "display_brightness_increment",
"f3": "mission_control",
"f4": "launchpad",
"f5": "illumination_decrement",
"f6": "illumination_increment",
"f7": "rewind",
"f8": "play_or_pause",
"f9": "fastforward"
},
"name": "PC-87",
"selected": true,
"simple_modifications": {
"left_command": "left_option",
"left_option": "left_command",
"right_command": "right_option",
"right_option": "right_command"
},
"virtual_hid_keyboard": {
"caps_lock_delay_milliseconds": 0,
"keyboard_type": "ansi"
}
}
]
}
##问题点
1,在使用内置键盘时,使用 shift + F1~F12 的组合键时,不起作用。但使用外接键盘没有问题。
参考资料:
- 简单的把 hjkl 变成 上下左右的方法:这个方法是用 ctrl 键做修饰键,但如果改成 Fn 键的话,就不行了。Fn 键的修改比较复杂。
- 把FN变成Hyper键的方法:可以看看,使用Karabiner-element
- Hammerspoon的一些例子