NIME: 使用 nodejs 快快樂樂開發輸入法 === Lee @ JSDC 2016 https://hackmd.io/p/BJ0xsY5A#/ --- ## 自我介紹 - Lee - ~~一位上班被 Pointer 荼毒、下班陷入 Callback 地獄的工程師。~~ - 新手前端工程師 @ [新芽網路 25sprout](http://www.25sprout.com/) - jessy1092 @ [Github](https://github.com/jessy1092) - 閒暇之餘在 g0v 填坑。 --- ## 大綱 - 緣起 - 原理 - Live Demo - 結語 --- ## 緣起 --- - [PIME - 用 Python 快速開發 Windows 的中文輸入法](http://www.slideshare.net/pcmantw/pime-python-windows-coscup-2015) (COSCUP 2015) - 史上首次用 python 開發 TSF 輸入法 - 不需要了解 Windwos TSF 或 IMM32 - 不需要寫 C++ - Server/Client 架構 - 支援 Windows Vista - 10(32 & 64 bit) --- ## Why NodeJS? --- ~~PCMan: 我不會寫 node.js~~ --- ![](https://app.kxg.io/images/jiade/fb.jpg) --- - 開發簡單快速 - PIME IPC 使用 json 做溝通 - 可以使用眾多 node modules - 吸引 JS 工程師寫輸入法 --- ![](https://pbs.twimg.com/profile_images/378800000822867536/3f5a00acf72df93528b6bb7cd0a4fd0c.jpeg) JS 離統一世界又邁進了一步 --- ## 原理 --- ```flow st=>start: Application e=>end: IME Server op=>operation: Windwos TSF op2=>operation: PIMETextService.dll op3=>operation: libpipe.dll op4=>operation: nodejs named pipe server st->op->op2->op3->op4->e ``` --- - [libpipe.dll](https://github.com/EasyIME/PIME/blob/master/libpipe/libpipe.cpp) - C named pipe server - 隱藏 named pipe server 細節,讓 js/python 可以簡單啟動 named pipe server - [lib/pipe.js](https://github.com/EasyIME/NIME/blob/master/lib/pipe.js) - 使用 node-ffi binding dll --- ```sequence Application -> IME Server: filterKeyDown: "A" Note right of IME Server: 決定要不要處理 IME Server -->Application: return : true Application -> IME Server: onKeyDown: "A" Note right of IME Server: 處理 A -> B IME Server -->Application: CommitString: "B" Note left of Application: Application 顯示 B Application -> IME Server: 下一次 key event ``` --- ## 輸入法狀態 ```flow st=>start: init e=>end: onDeactivate op=>operation: onActivate st->op->e ``` 每個應用程式切換到輸入法,即建立連線( ->init->onActivate ),切離輸入法,即斷掉連線(->onDeactivate)。 --- 第一個 request ```js { "method": "init", "id": "{f80736aa-28db-423a-92c9-5540f501c939}", "isWindows8Above": True, "isUiLess": False, "isConsole": False, "isMetroApp": True, "seqNum": 1 } ``` response 範例 ```js {"success": True, "seqNum": 1} ``` --- 第二個 request ```js { "method": "onActivate", "seqNum": 0, "isKeyboardOpen": True } ``` response 範例 ```js { 'customizeUI': { 'candPerRow': 3, 'candFontSize': 16, 'candFontName': 'MingLiu', 'candUseCursor': True }, 'seqNum': 0, 'setSelKeys': '1234567890', 'addPreservedKey': [{ 'modifiers': 4, 'keyCode': 32, 'guid': '{f1dae0fb-8091-44a7-8a0c-3082a1515447}' }], 'success': True, 'addButton': [{ 'id': 'switch-lang', 'icon': 'icon file path', 'commandId': 1, 'tooltip': '中英文切換' }, { 'id': 'windows-mode-icon', 'icon': 'icon file path', 'commandId': 4, 'tooltip': '中英文切換' }, { 'id': 'switch-shape', 'icon': 'icon file path', 'commandId': 2, 'tooltip': '全形/半形切換' }, { 'id': 'settings', 'icon': 'icon file path', 'type': 'menu', 'tooltip': '設定' }] } ``` --- ## 按鍵狀態 ```flow st=>start: filterKeyDown e=>end: onKeyUp op=>operation: onKeyDown op1=>operation: filterKeyUp st->op->op1->e ``` --- 按下 a 按鍵 ```js { "seqNum": 2, "charCode": 97, "keyCode": 65, "method": "filterKeyDown", "isExtended": False, "keyStates": [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0], "scanCode": 0, "repeatCount": 1 } ``` --- ## NIME 初始化設置 ime.json ```js { "name":"emojime", "version": "0.3.0", "guid": "{A381D463-9338-4FBD-B83D-66FFB03523B3}", "locale": "zh-TW", "icon": "icon.ico", "win8_icon": "", "moduleName": "index", "serviceName": "" } ``` - GUID 可以使用 GUID Generator 產生 - moduleName 為 NIME 讀取的 js 檔案名稱 --- index.js ```js module.exports = { textReducer(request, preState) { return preState; }, response(request, state) { return {success: true, seqNum: request['seqNum']}; } } ``` --- The case convertor. Part 1. ```js if (request['method'] === 'filterKeyDown') { let {charCode, seqNum} = request; let char = String.fromCharCode(charCode); let response = {return: false, success: true, seqNum}; if ((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')) { response['return'] = true; } return response; } ``` https://github.com/jessy1092/nime-demo --- The case convertor. Part 2. ```js if (request['method'] === 'onKeyDown') { let {charCode, seqNum} = request; let char = String.fromCharCode(charCode); let commitString = ''; if (char >= 'a' && char <= 'z') { commitString = char.toUpperCase(); } if (char >= 'A' && char <= 'Z') { commitString = char.toLowerCase(); } return {success: true, commitString, seqNum}; } ``` https://github.com/jessy1092/nime-demo --- server.js ```js let nime = require('nime'); let service = require('./index'); let config = require('./ime.json'); config['textService'] = service; let server = nime.createServer(undefined, [config]); server.listen(); ``` --- ## Live Demo - 大小寫轉換輸入法 --- ## 輸入法呈現區塊 - **Commit**: 輸出字 - **Composition**: 組字框 - **Candidate**: 選字框 --- ## Commit - `commitString`: 決定要輸出的字樣 --- ![](http://farm9.staticflickr.com/8626/30373831062_b74576d994_b.jpg) ```js { "success":true, "seqNum":50, "commitString":"❤️" } ``` --- ## Composition - `compositionString`: 決定組字框的字樣 - `compositionCursor`: 決定組字框的游標位置 --- ![](http://farm6.staticflickr.com/5546/30403577571_9d547e3244_b.jpg) ```js { "success":true, "seqNum":92, "compositionString":":rock", "compositionCursor":5 } ``` --- ## Candidate - `showCandidates`: 決定是否顯示選字框 - `candidateList`: 決定選字框的選單 - `candidateCursor`: 決定選字框的游標位置 --- ![](http://farm6.staticflickr.com/5497/30373669972_20b6d8f7f5_b.jpg) ```js { "success":true, "seqNum":50, "candidateList":[ "☘️ :shamrock:","☘ :shamrock:","� :rocket:" ], "showCandidates":true } ``` --- ## Live Demo - emoji 輸入法 - JS 注音 --- ## 結語 --- ## JS 開發輸入法元年!!! --- - 撰寫文件 - 更好的 IPC 架構 ([issue #11](https://github.com/EasyIME/forum/issues/11)) - NIME 支援更多輸入法功能。語言列、偏好設定等等。 - 台語輸入法 - NIME web 模擬器 - 跨平台 --- ## Reference - [PIME - 用 Python 快速開發 Windows 的中文輸入法](http://www.slideshare.net/pcmantw/pime-python-windows-coscup-2015) - https://github.com/EasyIME/PIME - https://github.com/EasyIME/NIME - https://github.com/EasyIME/emojime - https://github.com/jessy1092/nime-demo - https://github.com/jessy1092/nime-jszhuyin - [libIME 架構說明](https://github.com/chewing/windows-chewing-tsf/wiki/libIME-%E6%9E%B6%E6%A7%8B%E8%AA%AA%E6%98%8E) - https://github.com/chewing/libchewing - [KeyCode](https://github.com/EasyIME/NIME/blob/master/lib/keyCodes.js)