mac下自動切換輸入法

長久以來,輸入法一直是困擾mac用戶的一個問題;不過隨著國內(nèi)廠商的跟進,這種狀況得到了極大的改善。不用自己去折騰什么鼠須管了,狼廠和企鵝都做的不錯。

不過依然有一個問題沒有完全解決:不同程序之間輸入的自動切換。

相信大家一定有切換到item2輸入兩個命令發(fā)現(xiàn)是中文然后按cmd + space切換的尷尬;另外如果你如果使用vi或者emacs,那么就更蛋疼了。造成這種狀況的根本原因在于:輸入法的狀態(tài)是混亂的,我無法明白現(xiàn)在自己處于哪一種輸入環(huán)境,除非我開始打字或者看右上角輸入法的圖標(biāo)。我希望item2,Intellij IDEA,Alfred2永遠(yuǎn)是英文輸入狀態(tài),除非手動切換;其他的程序比如chrome瀏覽器,郵件客戶端保持正常。

打個比方,使用sublime寫代碼,大多數(shù)情況下肯定是英文輸入狀態(tài),寫注釋的時候可能手動切換到中文;但是這里有個問題,這時候如果我切換到其他程序,然后改變了輸入的狀態(tài),再次回到sublime,fuck!怎么又成了中文!

目前解決方案有如下方式:

  1. mac系統(tǒng)自帶的設(shè)置-> 鍵盤 -> 輸入源 -> 自動使用文稿的輸入源
  2. 一些輸入法的類似安靜模式的功能

第一種方式,意思就是不同的程序保持獨立的輸入狀態(tài),不會出現(xiàn)你在另外一個程序切換了輸入法的時候再次回來輸入法狀態(tài)就變了。這個開關(guān)很有用,我使用了一段時間,發(fā)現(xiàn)還是怪怪的,有時候并不符合預(yù)期,但是具體場景也搞不明白,反正是一頭霧水,有時候依然會陷入困惑的狀態(tài)。

第二種方式很有意思,應(yīng)該可以滿足很多非程序員的需求。這個安靜模式,打個比方,鼠須管輸入法;這種輸入法其實有幾種輸入模式,如果對于sublime開啟安靜模式,那么在進入sublime程序的時候,會自動切換到英文輸入模式;nice!不過問題就是:如果要切換到中文模式,需要按ctrl或者shift。如果使用一些IDE的話,肯定各種快捷鍵用的飛起,怎么少的了按ctrlshift,這時候問題就來了,如果我們一不小心在使用某些快捷鍵的時候觸發(fā)了這個輸入法的模式切換功能,那么就蛋疼了:我們需要不停滴按shift切換確保自己處于正確的狀態(tài)。更糟糕的是,如果你發(fā)現(xiàn)自己處于鼠須管的英文輸入模式,想使用中文,然后按了cmd + space 切換,你有可能會切換到系統(tǒng)的英文輸入法,打個字發(fā)現(xiàn)依然是英文!fuck!你不信邪,以為是沒有按到,再猛敲幾次cmd + space,最后你自己處于那個狀態(tài)就暈了。

怎么正確配置輸入法

經(jīng)過這些折騰之后,可以得到輸入法的這么幾條最佳實踐:

  1. 最基本的原則是要很方便滴知道自己處于哪一種輸入狀態(tài)。如果任何時候清楚這個,那么就是簡單的切換問題了。
  2. 最好不要使用一個輸入的兩種模式,并使用shift或者ctrl切換;如上文,某些情況會陷入極度混亂,最好在輸入法之間切換,模式簡單。
  3. 所有程序輸入法狀態(tài)應(yīng)該有一個恒定的初始態(tài),每次你重新進入這個程序,就會回到初始狀態(tài)。

為什么需要一個恒定的初始狀態(tài)呢?為了明確自己處于哪一種輸入狀態(tài),只需要在每次進入這個程序的時候,不管之前做過什么,它的狀態(tài)是確定的,姑且叫它初始態(tài);然后基于原則2,每次你希望切換的時候cmd + space一下,需要的時候換回來,如果你去了別的程序再回來,狀態(tài)重置為初始態(tài)。

好了分析了這么多,其實要解決的問題就是3一個,我們寫一段小程序。

切換輸入法實現(xiàn)

mac下如果使用objc或者swift切換輸入法很簡單,Apple提供了很詳細(xì)的Text Input Service文檔(現(xiàn)在這個文檔403了,可以使用google的cache訪問);我希望使用python來調(diào)用這些接口,很遺憾的是,pyobjc沒有封裝TIS系列函數(shù),手動使用ctypes模塊來wrap一下:

import ctypes
import ctypes.util
import objc
import CoreFoundation

_objc = ctypes.PyDLL(objc._objc.__file__)

# PyObject *PyObjCObject_New(id objc_object, int flags, int retain)
_objc.PyObjCObject_New.restype = ctypes.py_object
_objc.PyObjCObject_New.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]

def objc_object(id):
    return _objc.PyObjCObject_New(id, 0, 1)

# kTISPropertyLocalizedName
kTISPropertyUnicodeKeyLayoutData_p = ctypes.c_void_p.in_dll(carbon, 'kTISPropertyInputSourceIsEnabled')
kTISPropertyInputSourceLanguages_p = ctypes.c_void_p.in_dll(carbon, 'kTISPropertyInputSourceLanguages')
kTISPropertyInputSourceType_p = ctypes.c_void_p.in_dll(carbon, 'kTISPropertyInputSourceType')
kTISPropertyLocalizedName_p = ctypes.c_void_p.in_dll(carbon, 'kTISPropertyLocalizedName')
# kTISPropertyInputSourceLanguages_p = ctypes.c_void_p.in_dll(carbon, 'kTISPropertyInputSourceLanguages')

kTISPropertyInputSourceCategory = objc_object(ctypes.c_void_p.in_dll(carbon, 'kTISPropertyInputSourceCategory'))
kTISCategoryKeyboardInputSource = objc_object(ctypes.c_void_p.in_dll(carbon, 'kTISCategoryKeyboardInputSource'))


# TISCreateInputSourceList
carbon.TISCreateInputSourceList.restype = ctypes.c_void_p
carbon.TISCreateInputSourceList.argtypes = [ctypes.c_void_p, ctypes.c_bool]

carbon.TISSelectInputSource.restype = ctypes.c_void_p
carbon.TISSelectInputSource.argtypes = [ctypes.c_void_p]

carbon.TISGetInputSourceProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
carbon.TISGetInputSourceProperty.restype = ctypes.c_void_p

carbon.TISCopyInputSourceForLanguage.argtypes = [ctypes.c_void_p]
carbon.TISCopyInputSourceForLanguage.restype = ctypes.c_void_p

def get_avaliable_languages():
    single_langs = filter(lambda x: x.count() == 1, \
        map(lambda x: objc_object(carbon.TISGetInputSourceProperty(CoreFoundation.CFArrayGetValueAtIndex(objc_object(s), x).__c_void_p__(), kTISPropertyInputSourceLanguages_p)), \
            range(CoreFoundation.CFArrayGetCount(objc_object(carbon.TISCreateInputSourceList(None, 0))))))
    res = set()
    map(lambda y: res.add(y[0]), single_langs)
    return res

def select_kb(lang):
    cur = carbon.TISCopyInputSourceForLanguage(CoreFoundation.CFSTR(lang).__c_void_p__())
    carbon.TISSelectInputSource(cur)

切換輸入法主要是TISSelectInputSource方法,簡單滴調(diào)用這個方法就可以了。使用ctypes包裝這個方法有兩個地方可以借鑒:

pyobjc 轉(zhuǎn)ctypes兼容類型

pyobjc提供的對象是不能直接傳遞給ctypes要包裝的函數(shù)使用的,需要轉(zhuǎn)換成可以識別的類型。每一個pyobjc提供的對象都有一個__c_void_p__()方法,對它調(diào)用這個方法就可以把這個對象轉(zhuǎn)換成一個c_void_p類型

ctypes指針構(gòu)造出pyobjc對象

簡單包裝一下objcruntime里面的new方法,然后可以直接根據(jù)指針new一個對象出來。正如以上代碼的PyObjCObject_New。(新版的pyobjc模塊貌似已經(jīng)包裝了這個方法)

PS:本人第一次包裝objc接口,對于objc以及pyobjc均不熟悉,可能有更優(yōu)雅的方法,請批評指正。

如何自動切換?

要想實現(xiàn)輸入法自動切換,自然是需要在某程序切換到前臺的時候,幫它更改一下輸入法的狀態(tài);如果知道一個程序是不是在前臺呢?最笨的辦法當(dāng)然就是輪詢,但是不夠優(yōu)雅。幸運的是,新的mac系統(tǒng)提供了這個回調(diào)。

class Observer(NSObject):
    def handle_(self, noti):
        info = noti.userInfo().objectForKey_(NSWorkspaceApplicationKey)
        bundleIdentifier = info.bundleIdentifier()
        if bundleIdentifier in ignore_list:
            print "found: %s active" % bundleIdentifier
            select_kb(u'en')


def main():
    nc = NSWorkspace.sharedWorkspace().notificationCenter()
    observer = Observer.new()
    nc.addObserver_selector_name_object_(
        observer,
        "handle:",
        NSWorkspaceDidActivateApplicationNotification,
        None
    )
    AppHelper.runConsoleEventLoop(installInterrupt=True)

這一段代碼可以拿到最前臺運行的application,而且是回調(diào)通知。有兩個地方需要注意:

  1. Observer對象需要先new出來,(我直接在函數(shù)參數(shù)里面調(diào)用,直接就是segement fault,不知道原因)不能使用python的構(gòu)造對象方式。需要調(diào)用new方法。
  2. 需要使用AppHelper.runConsoleEventLoop 才能接收到事件,至于為什么見參考。

成果

好了,把上面兩段代碼整合起來;就能實現(xiàn)每次在打開某些程序的時候,自動切換到某個輸入法了!

每次我切換到IDEA敲代碼,輸入法狀態(tài)永遠(yuǎn)都是英文;就算我切換到其他回個郵件,發(fā)個消息切換到了中文,再次回來依然是英文;我手動切換到了中文被打斷了去做了別的事情,再次回來,依然是英文狀態(tài)。我永遠(yuǎn)都知道自己處于什么輸入模式,如果不滿足條件,cmd + space 切換即可。

最后,你可以使用supervisor之類的東西把它加入開機自動運行,這樣,困惑已久的輸入法問題終于得到解決。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,639評論 19 139
  • 不知不覺,歲寒輸入法的更新歷史已經(jīng)可以列出這么一長串來了。從中可以看出,歲寒的發(fā)展過程也是一個不斷試錯的過程,其中...
    臨歲之寒閱讀 34,886評論 1 6
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,171評論 25 708
  • 萬物皆有裂痕,那是光照進來的地方”--Leonard Cohen 年初花一百元買了一本名叫”詩光年“的日歷,放在書...
    蔣菱閱讀 445評論 0 1
  • 過耳的短發(fā) 64的腰 灰霾的藍(lán)色 18的? 鐘情各色的衣裳 執(zhí)著各味的奶茶 向往各地的美景 人生尚起步 本該意氣風(fēng)發(fā)
    良晨與景閱讀 124評論 0 0

友情鏈接更多精彩內(nèi)容