From 53b6e3d0185f67fcf422f834cd5dc337e8da5b0c Mon Sep 17 00:00:00 2001 From: limerainne Date: Mon, 3 Jun 2019 18:03:28 +0900 Subject: [PATCH] [i18n] Add Korean keyboard (2-beolsik) (#5053) --- .../ui/data/keyboardlayouts/ko_KR_helper.lua | 440 ++++++++++++++++++ .../data/keyboardlayouts/ko_KR_keyboard.lua | 226 +++++++++ frontend/ui/widget/virtualkeyboard.lua | 4 + 3 files changed, 670 insertions(+) create mode 100644 frontend/ui/data/keyboardlayouts/ko_KR_helper.lua create mode 100644 frontend/ui/data/keyboardlayouts/ko_KR_keyboard.lua diff --git a/frontend/ui/data/keyboardlayouts/ko_KR_helper.lua b/frontend/ui/data/keyboardlayouts/ko_KR_helper.lua new file mode 100644 index 000000000..0bd1763c2 --- /dev/null +++ b/frontend/ui/data/keyboardlayouts/ko_KR_helper.lua @@ -0,0 +1,440 @@ +-- require('utf8') +local BaseUtil = require("ffi/util") +local logger = require("logger") + +-------- +-- # Hangul-input-method Kit for Lua/KOReader +-------- +-- ## Input method implemented: 2-beolsik (for simplicity, can retrieve many articles for implementation) +-- ## Classes and their features +-- * HgSylbls (= Hangul Syllables) +-- - Determine if a character is in Hangul consonnant, vowel, initial, medial, or final character +-- - Combine initial, medial[, and final] character into a complete syllables +-- - Determine if a medial (or final) character can be a double one (can combine another medial (or final) one) +-- * HgFSM (= Hangul Finite State Machine) +-- - Process Hangul syllabus combination if the character that user inputs are valid one to be combined +-- * UIHandler +-- - To communicate with the actual UI text input box +-- +-- ## References +-- https://ehclub.co.kr/2482 +-- :: Hangul syllables combination formula, Hangul unicode composition, FSM reference +-- https://en.wikipedia.org/wiki/Hangul_consonant_and_vowel_tables +-------- + +---------------------- +-- Hangul Syllables -- +---------------------- + +local HgSylbls = { + -- Hangul character ranges in Unicode + UNI_HG_BASE = 0xac00, + UNI_HG_UPPER = 0xd7af, + + UNI_HG_CONSONNANT_BASE = 0x1100, + UNI_HG_CONSONNANT_UPPER = 0x1112, + + UNI_HG_VOWEL_BASE = 0x1161, + UNI_HG_VOWEL_UPPER = 0x1175, + + UNI_HG_COMPAT_CONSONNANT_BASE = 0x3131, + UNI_HG_COMPAT_CONSONNANT_UPPER = 0x314e, + UNI_HG_COMPAT_VOWEL_BASE = 0x314f, + UNI_HG_COMPAT_VOWEL_UPPER = 0x3163, + + -- Initial, medial, and final characters to be combined + CHARS_INITIAL = {"ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", + "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"}, + + CHARS_MEDIAL = {"ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅗㅏ", "ㅗㅐ", "ㅗㅣ", "ㅛ", + "ㅜ", "ㅜㅓ", "ㅜㅔ", "ㅜㅣ", "ㅠ", "ㅡ", "ㅡㅣ", "ㅣ"}, + CHARS_MEDIAL_COMBINABLE = {"ㅗ", "ㅜ", "ㅡ"}, + + CHARS_FINAL = {nil, "ㄱ", "ㄲ", "ㄱㅅ", "ㄴ", "ㄴㅈ", "ㄴㅎ", "ㄷ", "ㄹ", "ㄹㄱ", "ㄹㅁ", "ㄹㅂ", "ㄹㅅ", + "ㄹㅌ", "ㄹㅍ", "ㄹㅎ", + "ㅁ", "ㅂ", "ㅂㅅ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"}, + CHARS_FINAL_COMBINABLE = {"ㄴ", "ㄹ", "ㅂ"}, + + -- For faster search, inverse index tables will be constructed in runtime + IDX_INITIAL = nil, + IDX_MEDIAL = nil, + IDX_MEDIAL_COMBINABLE = nil, + IDX_FINAL = nil, + IDX_FINAL_COMBINABLE = nil, +} + +function HgSylbls:create_inverse_tbl() + HgSylbls:_create_inverse_tbl_impl("CHARS", "IDX", "INITIAL") + HgSylbls:_create_inverse_tbl_impl("CHARS", "IDX", "MEDIAL") + HgSylbls:_create_inverse_tbl_impl("CHARS", "IDX", "MEDIAL_COMBINABLE") + HgSylbls:_create_inverse_tbl_impl("CHARS", "IDX", "FINAL") + HgSylbls:_create_inverse_tbl_impl("CHARS", "IDX", "FINAL_COMBINABLE") +end + +function HgSylbls:_create_inverse_tbl_impl(from_prefix, to_prefix, target_tbl) + -- ref: https://stackoverflow.com/questions/38282234/returning-the-index-of-a-value-in-a-lua-table + HgSylbls[to_prefix .. "_" .. target_tbl] = {} + for k, v in pairs(HgSylbls[from_prefix .. "_" .. target_tbl]) do + -- NOTE '-1' for making indices start from '0' + HgSylbls[to_prefix .. "_" .. target_tbl][v] = k - 1 + end +end + + +function HgSylbls:get_combined_char(initial, medial, final) + -- utf8.char() + return BaseUtil.unichar(HgSylbls:_get_combined_charcode(initial, medial, final)) +end +function HgSylbls:_get_combined_charcode(initial, medial, final) + local len_medial = #HgSylbls.CHARS_MEDIAL + local len_final = #HgSylbls.CHARS_FINAL + + local combined_code = HgSylbls.UNI_HG_BASE + + HgSylbls:_initial_idx(initial) * len_medial * len_final + + HgSylbls:_medial_idx(medial) * len_final + + local final_idx = HgSylbls:_final_idx(final) + if final_idx then + combined_code = combined_code + final_idx + end + + return combined_code +end + +function HgSylbls:_initial_idx(char) + -- double initial can be typed directly from 2-beolsik kbd, hence no table of two chars + return HgSylbls.IDX_INITIAL[char] +end +function HgSylbls:_medial_idx(char) + char = HgSylbls:_2elem_tbl_to_str(char) + return HgSylbls.IDX_MEDIAL[char] +end +function HgSylbls:_final_idx(char) + char = HgSylbls:_2elem_tbl_to_str(char) + return HgSylbls.IDX_FINAL[char] +end + + +function HgSylbls:in_intial(char) + -- double initial can be typed directly from 2-beolsik kbd, hence no table of two chars + return HgSylbls.IDX_INITIAL[char] ~= nil +end +function HgSylbls:in_medial(char) + char = HgSylbls:_2elem_tbl_to_str(char) + return HgSylbls.IDX_MEDIAL[char] ~= nil +end +function HgSylbls:in_final(char) + char = HgSylbls:_2elem_tbl_to_str(char) + return HgSylbls.IDX_FINAL[char] ~= nil +end +function HgSylbls:is_medial_comb(char) + return HgSylbls.IDX_MEDIAL_COMBINABLE[char] ~= nil +end +function HgSylbls:is_final_comb(char) + return HgSylbls.IDX_FINAL_COMBINABLE[char] ~= nil +end + +function HgSylbls:in_consonnant_char(char) + return HgSylbls:_in_target_char_group(char, + HgSylbls.UNI_HG_CONSONNANT_BASE, HgSylbls.UNI_HG_CONSONNANT_UPPER, + HgSylbls.UNI_HG_COMPAT_CONSONNANT_BASE, HgSylbls.UNI_HG_COMPAT_CONSONNANT_UPPER) +end +function HgSylbls:in_vowel_char(char) + return HgSylbls:_in_target_char_group(char, + HgSylbls.UNI_HG_VOWEL_BASE, HgSylbls.UNI_HG_VOWEL_UPPER, + HgSylbls.UNI_HG_COMPAT_VOWEL_BASE, HgSylbls.UNI_HG_COMPAT_VOWEL_UPPER) +end +function HgSylbls:_in_target_char_group(char, base, upper, compat_base, compat_upper) + local code = BaseUtil.utf8charcode(char) -- utf8.codepoint() + + if code == nil then + return false + end + + local result = base <= code and code <= upper + + local result_compat = false + if compat_base ~= nil then + result_compat = compat_base <= code and code <= compat_upper + end + + return result or result_compat +end + +function HgSylbls:_2elem_tbl_to_str(str_or_tbl) + -- if the type of argument is a 'table', + -- then it is a double medial/final character + if type(str_or_tbl) == "table" then + local tbl = str_or_tbl + return tbl[1] .. tbl[2] + end + -- otherwise, return an argument as-is + return str_or_tbl +end + +-- initialize HgSylbls inverse index table +HgSylbls:create_inverse_tbl() + + +--------------- +-- UI interface mock; will be implemented +--------------- + +local UIHandler = {} + +function UIHandler:put_char(char) + logger.dbg("UI:put_char()", char) +end +function UIHandler:del_char() + logger.dbg("UI:del_char()") +end +function UIHandler:del_put_char(char) + UIHandler:del_char() + UIHandler:put_char(char) +end + +---------------------- +-- Hangul Automata -- +---------------------- + +local HgFSM = { + STATE = { + IDLE = 0, + GOT_INITIAL = 1, + GOT_MEDIAL = 2, + GOT_FINAL = 3, + GOT_DOUBLE_MEDIAL = 4, + GOT_DOUBLE_FINAL = 5, + }, + + initial = nil, + medial = nil, + final = nil, + + fsm_state = nil, + fsm_prev_states = {}, + + do_not_del_in_medial = false, + + ui_handler = nil, +} + +function HgFSM:init(ui_handler) + HgFSM:clean_state() + + HgFSM.ui_handler = ui_handler +end + +function HgFSM:clean_state() + HgFSM.initial = nil + HgFSM.medial = nil + HgFSM.final = nil + + HgFSM.fsm_prev_states = {HgFSM.STATE.IDLE} + HgFSM.fsm_state = HgFSM.STATE.IDLE + + HgFSM.do_not_del_in_medial = false +end + +function HgFSM:_push_state(state) + HgFSM.fsm_prev_states[#HgFSM.fsm_prev_states+1] = state -- append a state + HgFSM.fsm_state = state +end +function HgFSM:_pop_state() + local prev_state = HgFSM.fsm_prev_states[#HgFSM.fsm_prev_states] + + table.remove(HgFSM.fsm_prev_states) -- pop last item + HgFSM.fsm_state = HgFSM.fsm_prev_states[#HgFSM.fsm_prev_states] + + return prev_state +end + +function HgFSM:process_char(char) + if HgFSM:_should_handle_as_target_char(char) then + HgFSM:_process_hg_char(char) + else + HgFSM:_process_generic_char(char) + end +end + +function HgFSM:process_bsp(char) + if HgFSM.fsm_state == HgFSM.STATE.IDLE or HgFSM.fsm_state == HgFSM.STATE.GOT_INITIAL then + HgFSM:_process_generic_bsp() + else + HgFSM:_process_hg_bsp_except_initial() + HgFSM:_process_hg_char_update_ui(true) -- true: always remove the current character in edit + end +end + +function HgFSM:_should_handle_as_target_char(char) + if HgSylbls:in_consonnant_char(char) then + return true + elseif HgSylbls:in_vowel_char(char) and HgFSM.fsm_state ~= HgFSM.STATE.IDLE then + return true + end + + return false +end + +function HgFSM:_process_generic_char(char) + HgFSM:clean_state() + HgFSM.ui_handler:put_char(char) +end +function HgFSM:_process_generic_bsp(char) + HgFSM:clean_state() + HgFSM.ui_handler:del_char() +end + +function HgFSM:_process_hg_char(char) + local result = HgFSM:_process_hg_char_impl(char) + + if result then + HgFSM:_process_hg_char_update_ui() + else -- e.g. single vowel character + HgFSM:_process_generic_char(char) + end +end + +function HgFSM:_process_hg_bsp_except_initial() + local prev_state = HgFSM:_pop_state() + + if prev_state == HgFSM.STATE.GOT_MEDIAL then + HgFSM.medial = nil + + elseif prev_state == HgFSM.STATE.GOT_DOUBLE_MEDIAL then + HgFSM.medial = HgFSM.medial[1] + + elseif prev_state == HgFSM.STATE.GOT_FINAL then + HgFSM.final = nil + + elseif prev_state == HgFSM.STATE.GOT_DOUBLE_FINAL then + HgFSM.final = HgFSM.final[1] + + end +end + +function HgFSM:_process_hg_char_impl(char) + if HgFSM.fsm_state == HgFSM.STATE.IDLE then + HgFSM:_process_hg_char_new_hg(char) + + elseif HgFSM.fsm_state == HgFSM.STATE.GOT_INITIAL then + if HgSylbls:in_consonnant_char(char) then + HgFSM:_process_hg_char_new_hg(char) + else + HgFSM:_process_hg_char_push_medial(char) + end + + elseif HgFSM.fsm_state == HgFSM.STATE.GOT_MEDIAL then + if HgSylbls:in_vowel_char(char) then + local dbl_medial_cand = {HgFSM.medial, char} + if HgSylbls:is_medial_comb(HgFSM.medial) and HgSylbls:in_medial(dbl_medial_cand) then + HgFSM:_process_hg_char_push_medial(dbl_medial_cand, true) + else + return false + end + else + HgFSM:_process_hg_char_push_final(char) + end + + elseif HgFSM.fsm_state == HgFSM.STATE.GOT_DOUBLE_MEDIAL then + if HgSylbls:in_vowel_char(char) then + return false + else + HgFSM:_process_hg_char_push_final(char) + end + + elseif HgFSM.fsm_state == HgFSM.STATE.GOT_FINAL then + if HgSylbls:in_vowel_char(char) then + HgFSM:_process_hg_char_borrow_initial_push_next_medial( + nil, HgFSM.final, char) + else + local dbl_final_cand = {HgFSM.final, char} + if HgSylbls:is_final_comb(HgFSM.final) and HgSylbls:in_final(dbl_final_cand) then + HgFSM:_process_hg_char_push_final(dbl_final_cand, true) + else + HgFSM:_process_hg_char_new_hg(char) + end + end + + elseif HgFSM.fsm_state == HgFSM.STATE.GOT_DOUBLE_FINAL then + if HgSylbls:in_vowel_char(char) then + HgFSM:_process_hg_char_borrow_initial_push_next_medial( + HgFSM.final[1], HgFSM.final[2], char) + else + HgFSM:_process_hg_char_new_hg(char) + end + + end + + return true +end + +function HgFSM:_process_hg_char_new_hg(char) + HgFSM:clean_state() + + HgFSM:_push_state(HgFSM.STATE.GOT_INITIAL) + HgFSM.initial = char +end + +function HgFSM:_process_hg_char_push_medial(char, is_double) + if is_double then + HgFSM:_push_state(HgFSM.STATE.GOT_DOUBLE_MEDIAL) + else + HgFSM:_push_state(HgFSM.STATE.GOT_MEDIAL) + end + HgFSM.medial = char +end + +function HgFSM:_process_hg_char_push_final(char, is_double) + if is_double then + HgFSM:_push_state(HgFSM.STATE.GOT_DOUBLE_FINAL) + else + HgFSM:_push_state(HgFSM.STATE.GOT_FINAL) + end + HgFSM.final = char +end + +function HgFSM:_process_hg_char_borrow_initial_push_next_medial(curr_final, next_init, next_medial) + local next_init_cand = next_init + HgFSM.final = curr_final + HgFSM:_pop_state() -- go to previous state + HgFSM:_process_hg_char_update_ui() -- apply UI the borrow of final character + + HgFSM:_process_hg_char_new_hg(next_init_cand) + + HgFSM:_push_state(HgFSM.STATE.GOT_MEDIAL) + HgFSM.medial = next_medial + HgFSM.do_not_del_in_medial = true -- previous character in edit has to be maintained +end + + +function HgFSM:_process_hg_char_update_ui(should_undo_in_initial) + should_undo_in_initial = should_undo_in_initial or false + + if HgFSM.fsm_state == HgFSM.STATE.GOT_INITIAL then + if should_undo_in_initial then + HgFSM.ui_handler:del_char() + end + HgFSM.ui_handler:put_char(HgFSM.initial) + + elseif HgFSM.fsm_state == HgFSM.STATE.GOT_MEDIAL or HgFSM.fsm_state == HgFSM.STATE.GOT_DOUBLE_MEDIAL then + local combined_char = HgSylbls:get_combined_char(HgFSM.initial, HgFSM.medial, nil) + if HgFSM.do_not_del_in_medial then + HgFSM.do_not_del_in_medial = false + HgFSM.ui_handler:put_char(combined_char) + else + HgFSM.ui_handler:del_put_char(combined_char) + end + + elseif HgFSM.fsm_state == HgFSM.STATE.GOT_FINAL or HgFSM.fsm_state == HgFSM.STATE.GOT_DOUBLE_FINAL then + local combined_char = HgSylbls:get_combined_char(HgFSM.initial, HgFSM.medial, HgFSM.final) + HgFSM.ui_handler:del_put_char(combined_char) + + end +end + + +return { + UIHandler = UIHandler, + HgFSM = HgFSM, +} diff --git a/frontend/ui/data/keyboardlayouts/ko_KR_keyboard.lua b/frontend/ui/data/keyboardlayouts/ko_KR_keyboard.lua new file mode 100644 index 000000000..04a97a366 --- /dev/null +++ b/frontend/ui/data/keyboardlayouts/ko_KR_keyboard.lua @@ -0,0 +1,226 @@ +local logger = require("logger") + +-------- +-- # Korean 2-beolsik Keyboard layout +-------- + +local HgHelper = require("ui/data/keyboardlayouts/ko_KR_helper") + +-------- +-- UI handler implementation for communicating with text input box widget +-------- +function HgHelper.UIHandler:put_char(char) + HgHelper.UIHandler.inputbox:_addChars(char) +end +function HgHelper.UIHandler:del_char(char) + HgHelper.UIHandler.inputbox:_delChar() +end +HgHelper.HgFSM:init(HgHelper.UIHandler) + +-------- +-- Custom key event handlers with Hangul support +-------- +local wrapInputBox = function(inputbox) + HgHelper.HgFSM.clean_state() -- reset helper + + if inputbox._wrapped == nil then + inputbox._wrapped = true + + -- helper function + local function copy_func_reference(obj, name) + obj["_" .. name] = obj[name] + end + + -- override original implementations with helper object + copy_func_reference(inputbox, "addChars") + copy_func_reference(inputbox, "delChar") + + function inputbox:addChars(key) + logger.dbg("ko_KR_kbd:addChar(", key, ")") + HgHelper.UIHandler.inputbox = self + HgHelper.HgFSM:process_char(key) + end + function inputbox:delChar() + logger.dbg("ko_KR_kbd:delChar()") + HgHelper.UIHandler.inputbox = self + HgHelper.HgFSM:process_bsp() + end + + -- override implementations: reset helper if we have to stop combining current syllable + ---- helper function + local function wrap_func_with_hghelper_reset(obj, name) + copy_func_reference(obj, name) + obj[name] = function(self) + HgHelper.HgFSM.clean_state() + self["_" .. name](self) + end + end + + ---- delete text + wrap_func_with_hghelper_reset(inputbox, "delToStartOfLine") + wrap_func_with_hghelper_reset(inputbox, "clear") + + ---- move cursor + wrap_func_with_hghelper_reset(inputbox, "leftChar") + wrap_func_with_hghelper_reset(inputbox, "rightChar") + wrap_func_with_hghelper_reset(inputbox, "upLine") + wrap_func_with_hghelper_reset(inputbox, "downLine") + + ---- unfocus: move to other inputbox + wrap_func_with_hghelper_reset(inputbox, "unfocus") + + ---- tap/hold/swipe: move cursor + ------ helper function + local function wrap_touch_event_func_with_hghelper_reset(obj, name) + copy_func_reference(obj, name) + obj[name] = function(self, arg, ges) + HgHelper.HgFSM.clean_state() + return self["_" .. name](self, arg, ges) + end + end + + wrap_touch_event_func_with_hghelper_reset(inputbox, "onTapTextBox") + wrap_touch_event_func_with_hghelper_reset(inputbox, "onHoldTextBox") + wrap_touch_event_func_with_hghelper_reset(inputbox, "onSwipeTextBox") + end +end + +-- Belows are just same as the English keyboard popup +local en_popup = require("ui/data/keyboardlayouts/keypopup/en_popup") +local com = en_popup.com -- comma (,) +local prd = en_popup.prd -- period (.) +local _at = en_popup._at +local _eq = en_popup._eq -- equals sign (=) +local _A_ = en_popup._A_ +local _a_ = en_popup._a_ +local _B_ = en_popup._B_ +local _b_ = en_popup._b_ +local _C_ = en_popup._C_ +local _c_ = en_popup._c_ +local _D_ = en_popup._D_ +local _d_ = en_popup._d_ +local _E_ = en_popup._E_ +local _e_ = en_popup._e_ +local _F_ = en_popup._F_ +local _f_ = en_popup._f_ +local _G_ = en_popup._G_ +local _g_ = en_popup._g_ +local _H_ = en_popup._H_ +local _h_ = en_popup._h_ +local _I_ = en_popup._I_ +local _i_ = en_popup._i_ +local _J_ = en_popup._J_ +local _j_ = en_popup._j_ +local _K_ = en_popup._K_ +local _k_ = en_popup._k_ +local _L_ = en_popup._L_ +local _l_ = en_popup._l_ +local _M_ = en_popup._M_ +local _m_ = en_popup._m_ +local _N_ = en_popup._N_ +local _n_ = en_popup._n_ +local _O_ = en_popup._O_ +local _o_ = en_popup._o_ +local _P_ = en_popup._P_ +local _p_ = en_popup._p_ +local _Q_ = en_popup._Q_ +local _q_ = en_popup._q_ +local _R_ = en_popup._R_ +local _r_ = en_popup._r_ +local _S_ = en_popup._S_ +local _s_ = en_popup._s_ +local _T_ = en_popup._T_ +local _t_ = en_popup._t_ +local _U_ = en_popup._U_ +local _u_ = en_popup._u_ +local _V_ = en_popup._V_ +local _v_ = en_popup._v_ +local _W_ = en_popup._W_ +local _w_ = en_popup._w_ +local _X_ = en_popup._X_ +local _x_ = en_popup._x_ +local _Y_ = en_popup._Y_ +local _y_ = en_popup._y_ +local _Z_ = en_popup._Z_ +local _z_ = en_popup._z_ + +-- Based on English keyboard layout, but modifications are made for Korean layout +return { + shiftmode_keys = {["Shift"] = true}, + symbolmode_keys = {["Sym"] = true, ["ABC"] = true}, + utf8mode_keys = {["IM"] = true}, + umlautmode_keys = {["Äéß"] = false}, -- Disabled 'umlaut' keys + keys = { + -- [shift, unshift, symbol-shift, symbol-unshift] + -- 1, 2, 3, 4: default + -- 5, 6, 7, 8: 'IM' (globe) + -- 9, 10, 11, 12: 'umlaut' (UNUSED) + -- + -- first row + { -- 1 2 3 4 5 6 7 8 9 10 11 12 + { _Q_, _q_, "„", "0", "ㅃ", "ㅂ", "₩", "0", "Å", "å", "1", "ª", }, + { _W_, _w_, "!", "1", "ㅉ", "ㅈ", "!", "1", "Ä", "ä", "2", "º", }, + { _E_, _e_, _at, "2", "ㄸ", "ㄷ", _at, "2", "Ö", "ö", "3", "¡", }, + { _R_, _r_, "#", "3", "ㄲ", "ㄱ", "#", "3", "ß", "ß", "4", "¿", }, + { _T_, _t_, "+", _eq, "ㅆ", "ㅅ", "+", _eq, "À", "à", "5", "¼", }, + { _Y_, _y_, "€", "(", "ㅛ", "ㅛ", "☆", "(", "Â", "â", "6", "½", }, + { _U_, _u_, "‰", ")", "ㅕ", "ㅕ", "★", ")", "Æ", "æ", "7", "¾", }, + { _I_, _i_, "|", "\\", "ㅑ", "ㅑ", "♡", "\\", "Ü", "ü", "8", "©", }, + { _O_, _o_, "?", "/", "ㅒ", "ㅐ", "♥", "/", "È", "è", "9", "®", }, + { _P_, _p_, "~", "`", "ㅖ", "ㅔ", "※", "`", "É", "é", "0", "™", }, + }, + -- second row + { -- 1 2 3 4 5 6 7 8 9 10 11 12 + { _A_, _a_, "…", _at, "ㅁ", "ㅁ", "…", "@", "Ê", "ê", "Ş", "ş", }, + { _S_, _s_, "$", "4", "ㄴ", "ㄴ", "$", "4", "Ë", "ë", "İ", "ı", }, + { _D_, _d_, "%", "5", "ㅇ", "ㅇ", "%", "5", "Î", "î", "Ğ", "ğ", }, + { _F_, _f_, "^", "6", "ㄹ", "ㄹ", "^", "6", "Ï", "ï", "Ć", "ć", }, + { _G_, _g_, ":", ";", "ㅎ", "ㅎ", ":", "'", "Ô", "ô", "Č", "č", }, + { _H_, _h_, '"', "'", "ㅗ", "ㅗ", "♩", "\"", "Œ", "œ", "Đ", "đ", }, + { _J_, _j_, "{", "[", "ㅓ", "ㅓ", "♪", "[", "Ù", "ù", "Š", "š", }, + { _K_, _k_, "}", "]", "ㅏ", "ㅏ", "♬", "]", "Û", "û", "Ž", "ž", }, + { _L_, _l_, "_", "-", "ㅣ", "ㅣ", "™", "-", "Ÿ", "ÿ", "Ő", "ő", }, + }, + -- third row + { -- 1 2 3 4 5 6 7 8 9 10 11 12 + { label = "Shift", + icon = "resources/icons/appbar.arrow.shift.png", + width = 1.5 + }, + { _Z_, _z_, "&", "7", "ㅋ", "ㅋ", "「", "7", "Á", "á", "Ű", "ű", }, + { _X_, _x_, "*", "8", "ㅌ", "ㅌ", "」", "8", "Ø", "ø", "Ã", "ã", }, + { _C_, _c_, "£", "9", "ㅊ", "ㅊ", "*", "9", "Í", "í", "Þ", "þ", }, + { _V_, _v_, "<", com, "ㅍ", "ㅍ", "❤", com, "Ñ", "ñ", "Ý", "ý", }, + { _B_, _b_, ">", prd, "ㅠ", "ㅠ", "&", prd, "Ó", "ó", "†", "‡", }, + { _N_, _n_, "‘", "↑", "ㅜ", "ㅜ", "『", "↑", "Ú", "ú", "–", "—", }, + { _M_, _m_, "’", "↓", "ㅡ", "ㅡ", "』", "↓", "Ç", "ç", "…", "¨", }, + { label = "Backspace", + icon = "resources/icons/appbar.clear.reflect.horizontal.png", + width = 1.5 + }, + }, + -- fourth row + { + { "Sym", "Sym", "ABC", "ABC", "Sym", "Sym", "ABC", "ABC", "Sym", "Sym", "ABC", "ABC", + width = 1.5}, + { label = "IM", + icon = "resources/icons/appbar.globe.wire.png", + width = 2, + }, + -- { "Äéß", "Äéß", "Äéß", "Äéß", "Äéß", "Äéß", "Äéß", "Äéß", "Äéß", "Äéß", "Äéß", "Äéß", }, + { label = "간격", + " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", + width = 3.0}, + { com, com, "“", "←", com, com, com, "←", "Ũ", "ũ", com, com, }, + { prd, prd, "”", "→", prd, prd, prd, "→", "Ĩ", "ĩ", prd, prd, }, + { label = "Enter", + "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", + icon = "resources/icons/appbar.arrow.enter.png", + width = 1.5, + }, + }, + }, + + -- wrap InputBox for hooking events to the helper + wrapInputBox = wrapInputBox, +} diff --git a/frontend/ui/widget/virtualkeyboard.lua b/frontend/ui/widget/virtualkeyboard.lua index 5f4b18895..1b3b5e372 100644 --- a/frontend/ui/widget/virtualkeyboard.lua +++ b/frontend/ui/widget/virtualkeyboard.lua @@ -527,6 +527,7 @@ local lang_to_keyboard_layout = { ja = "ja_keyboard", pl = "pl_keyboard", pt_BR = "pt_keyboard", + ko_KR = "ko_KR_keyboard", } function VirtualKeyboard:init() @@ -546,6 +547,9 @@ function VirtualKeyboard:init() if Device:hasKeys() then self.key_events.Close = { {"Back"}, doc = "close keyboard" } end + if keyboard.wrapInputBox then + keyboard.wrapInputBox(self.inputbox) + end end function VirtualKeyboard:onClose()