1. 程式人生 > >Mac:使用大寫鎖定鍵切換輸入法

Mac:使用大寫鎖定鍵切換輸入法

狀態改變 令行 pan -s 表示 callback app esc 命令行

Mac:使用大寫鎖定鍵切換輸入法

動機

大寫鎖定鍵是我的鍵盤上用的最少的鍵之一。說是之一,一是因為我的鍵盤上還有一個關機鍵使用頻率和它有的一拼,二是由於其地理位置優越經常會被誤按。

實際上,在Chromebook上,大寫鎖定鍵就被Google換成了更為常用的“搜索鍵”;另外,也有vimer把大寫鎖定鍵用作ESC鍵,效果拔群。

技術分享

根據個人習慣,我最終決定將大寫鎖定鍵更改為輸入法切換鍵,一是因為作為一個中國人輸入法切換是使用最多的一個快捷鍵之一;另一個原因是因為如此一來鍵上的指示燈還可作為輸入法指示燈,簡直完美。

更改鍵綁定

  • 打開系統偏好設置-鍵盤-鍵盤-修飾鍵,將Caps Lock鍵設為“無操作”
  • 下載Seil(良心軟件,良心作者),打開後將Caps Lock鍵映射為Key Code 80(或者其他一個不存在的鍵,80代表F19
  • 打開系統偏好設置-鍵盤-快捷鍵-輸入源,將切換輸入法的快捷鍵設置為F19(通過選擇後按一下Caps Lock)

Done! 但是現在問題來了:大寫鎖定的燈永遠不亮,這不優美!我們希望指示燈也更改為能夠指示輸入法狀態,即在英文狀態下不亮,在拼音/五筆等輸入法狀態下亮。經過Google發現,OS X提供了控制鍵盤燈的底層API,可以手動控制其狀態,詳見MacLight。這就好辦了,於是我依次嘗試了以下幾種解決方案:

  • 寫一段Shell腳本來切換輸入法(通過Applescript模擬Keystroke)+切換指示燈狀態,通過Automator新建一個“服務”然後將大寫鎖定鍵綁定為運行該腳本。但是經過測試發現延時太大(~200ms
    ),放棄。
  • 用Objective-C寫一個調用底層API的程序來切換輸入法(通過TISSelectInputSource系列API)+切換指示燈狀態,發現調用API切換輸入法後需要切換到下一個輸入窗口才會生效,並且延時依然很大,放棄。
  • 用Objective-C寫一個後臺應用,通過NSDistributedNotificationCenter接收輸入法變更事件,根據狀態改變指示燈。科學!

最終采用了最後這種科學的方法。當然,這個後臺應用只需要是命令行應用就可以了,通過launchctl等方式開機自動啟動即可。不過由於強迫癥什麽的(方便啟動、退出,方便加為登錄啟動項)還是寫成了占領在狀態欄的應用,並取名為IMLight

,如下圖:

技術分享

下載鏈接:點我 or Fork me at GitHub

Update for macOS Sierra

升級macOS Sierra後,Seil無法正常使用了(IMLight不影響),詳見Github上的這個issue,並且由於是系統接口的大改動,一時半會兒可能不會有修復更新。

Issue中也有人提到,可以使用作者正在開發的另一個針對Sierra的項目Karabiner-Elements,但是這個項目對我來說有幾個問題:

  • 與IMLight沖突(雖然不一定是他的問題,但是我暫時也不知道怎麽修復…)
  • 會使得系統偏好設置中的針對多個鍵盤的修飾鍵設置失效(比如無法把外接鍵盤的alt和cmd互換),作者表示無法修復

另外的解決方案是在系統偏好設置中把Caps Lock設置為Ctrl(或者其他),然後用其他軟件重映射,比如Keyboard Maestro(更改Caps Lock這件事情比較底層,需要內核級別的修改,而監聽Ctrl等鍵這件事情就很簡單了)。我使用免費的hammerspoon來實現:

local M = {}

local events = hs.eventtap.event.types
M.log = hs.logger.new(‘caps_remap‘, ‘info‘)

M.last_flags_1 = {}
M.last_flags_0 = {}
M.last_time_1 = 0
M.last_time_0 = 0

M.timeout = 0.15
M.key = "ctrl"
M.action = function() hs.eventtap.keyStroke({}, "f19") end

local function _dict_has_no_other_key(dic)
    for k,v in pairs(dic) do
        if k ~= M.key then
            return false
        end
    end
    return true
end

function M.event_callback(e)
    local typ = e:getType()
    local code = e:getKeyCode()
    local flags = e:getFlags()
    local now = hs.timer.secondsSinceEpoch()

    if _dict_has_no_other_key(flags) and not flags[M.key]
        and _dict_has_no_other_key(M.last_flags_0) and M.last_flags_0[M.key]
        and _dict_has_no_other_key(M.last_flags_1) and not M.last_flags_1[M.key]
        and now - M.last_time_0 < M.timeout
        then
        M.log.i("Fire caps action")
        if M.action then
            M.action()
        end
    end

    M.last_flags_1 = M.last_flags_0
    M.last_flags_0 = flags

    M.last_time_1 = M.last_time_0
    M.last_time_0 = now

    return false
end


function M.init(options)
    if options.key then
        M.key = options.key
    end
    if options.timeout then
        M.timeout = options.timeout
    end
    if options.action then
        M.action = options.action
    end
    M.watcher = hs.eventtap.new({events.flagsChanged}, M.event_callback)
    M.watcher:start()
end

return M

即快速按一下ctrl(即Caps Lock)會觸發F19,而其他包含ctrl的組合鍵並不會,可以滿足要求。

Mac:使用大寫鎖定鍵切換輸入法