yeet-log

My blog, using org-publish
git clone https://git.git.jamzattack.xyz/yeet-log
Log | Files | Refs

emacs-hangul-input.org (10313B)


      1 #+title: Emacs Hangul Input
      2 #+date: <2020-07-18 Sat>
      3 #+property: header-args :results none
      4 
      5 Note: org-mode help links don't work when exported to html; [[https://git.jamzattack.xyz/yeet-log][best
      6 viewed in Emacs]].
      7 
      8 * Keyboard layout
      9 
     10 The variable [[help:quail-keyboard-layout-alist][quail-keyboard-layout-alist]] by default contains six
     11 layouts: all qwerty.
     12 
     13 First, we need to define and enable a dvorak layout:
     14 #+name: dvorak-layout
     15 #+begin_src emacs-lisp
     16   (with-eval-after-load 'quail
     17     (push
     18      (cons "dvorak"
     19            (concat
     20             "                              "
     21             "`~1!2@3#4$5%6^7&8*9(0)[{]}    "   ; numbers
     22             "  '\",<.>pPyYfFgGcCrRlL/?=+\\|  " ; qwerty
     23             "  aAoOeEuUiIdDhHtTnNsS-_      "   ; asdf
     24             "  ;:qQjJkKxXbBmMwWvVzZ        "   ; zxcv
     25             "                              "))
     26      quail-keyboard-layout-alist)
     27 
     28     (quail-set-keyboard-layout "dvorak"))
     29 #+end_src
     30 
     31 However, the Hangul input methods don't respect this variable, so we
     32 need to hack about a bit.
     33 
     34 * Input methods in Emacs
     35 
     36 The way Emacs changes the input method is by defining a local
     37 variable, [[help:input-method-function][input-method-function]].
     38 
     39 For example, if you change the layout with =C-u C-\ korean-hangul RET=
     40 or ~(set-input-method 'korean-hangul)~, the above variable will be set
     41 to [[help:hangul2-input-method][hangul2-input-method]].
     42 
     43 Because of this structure, one can simply write a function that takes
     44 a single argument (the key sequence) and inserts characters
     45 accordingly.  Technically, an input method doesn't even need to insert
     46 anything; it could just be a bunch of random commands -- such is the
     47 power of Emacs.
     48 
     49 * =hangul.el=
     50 
     51 The Hangul library is very qwerty-based.  I didn't think this would
     52 pose many problems however, as that's what changing the [[*Keyboard layout][keyboard
     53 layout]] should do, right?
     54 
     55 I use the standard "korean-hangul" (AKA Dubeolsik, 두벌식) input
     56 method.  Others are also defined:
     57 
     58 - Hangul 2-Bulsik input method
     59 - Hangul 3-Bulsik final input method
     60 - Hangul 3-Bulsik 390 input method
     61 
     62 Side note: Hangul is known for being a very sensible and logical
     63 alphabet.  The keyboard layout is the same -- vowels are on the right,
     64 and consonants on the left.  [[https://en.wikipedia.org/wiki/Korean_language_and_computers][Wikipedia]]
     65 
     66 ** Messing with the internal function
     67 
     68 The function [[help:hangul2-input-method-internal][hangul2-input-method-internal]] (or its caller, without
     69 =-internal=) are where the translation seemed to be missing:
     70 #+name: old-internal
     71 #+begin_src emacs-lisp
     72   ;; Defined in lisp/leim/quail/hangul.el
     73   (defun hangul2-input-method-internal (key)
     74     (let ((char (+ (aref hangul2-keymap (1- (% key 32)))
     75                    (cond ((or (= key ?O) (= key ?P)) 2)
     76                          ((or (= key ?E) (= key ?Q) (= key ?R)
     77                               (= key ?T) (=  key ?W)) 1)
     78                          (t 0)))))
     79       (if (< char 31)
     80           (hangul2-input-method-jaum char)
     81         (hangul2-input-method-moum char))))
     82 #+end_src
     83 
     84 I figured it might work fine if I just lexically rebound ~KEY~ to the
     85 translation:
     86 #+name: new-function
     87 #+begin_src emacs-lisp
     88   (defun hangul2-input-method-internal (key)
     89     (setq key (quail-keyboard-translate key))
     90     (let ((char (+ (aref hangul2-keymap (1- (% key 32)))
     91                    (cond ((or (= key ?O) (= key ?P)) 2)
     92                          ((or (= key ?E) (= key ?Q) (= key ?R)
     93                               (= key ?T) (= key ?W)) 1)
     94                          (t 0)))))
     95       (if (< char 31)
     96           (hangul2-input-method-jaum char)
     97         (hangul2-input-method-moum char))))
     98 #+end_src
     99 
    100 *** Results
    101 
    102 Unfortunately, this is janky as hell.  There are multiple issues:
    103 
    104 1. Some keys result in "Args out of range: [17 48 26 23 ...]".
    105 2. The translation is only used for some keys.
    106 
    107 This is because the key is checked first in [[help:hangul2-input-method][hangul2-input-method]] to
    108 determine whether it is alphabetic and should be handed off to the
    109 internal version.
    110 
    111 ** hangul2-input-method
    112 
    113 So that first one didn't work...  What if I used [[help:hangul2-input-method][hangul2-input-method]]
    114 instead?
    115 #+name: first-try
    116 #+begin_src emacs-lisp
    117   ;; Defined in lisp/leim/quail/hangul.el
    118   (defun hangul2-input-method (key)
    119     "2-Bulsik input method."
    120     (if (or buffer-read-only (not (alphabetp key)))
    121         (list key)
    122       (quail-setup-overlays nil)
    123       (let ((input-method-function nil)
    124             (echo-keystrokes 0)
    125             (help-char nil))
    126         (setq hangul-queue (make-vector 6 0))
    127         (hangul2-input-method-internal key)
    128         (unwind-protect
    129             (catch 'exit-input-loop
    130               (while t
    131                 (let* ((seq (read-key-sequence nil))
    132                        (cmd (lookup-key hangul-im-keymap seq))
    133                        key)
    134                   (cond ((and (stringp seq)
    135                               (= 1 (length seq))
    136                               (setq key (aref seq 0))
    137                               (alphabetp key))
    138                          (hangul2-input-method-internal key))
    139                         ((commandp cmd)
    140                          (call-interactively cmd))
    141                         (t
    142                          (setq unread-command-events
    143                                (nconc (listify-key-sequence seq)
    144                                       unread-command-events))
    145                          (throw 'exit-input-loop nil))))))
    146           (quail-delete-overlays)))))
    147 #+end_src
    148 
    149 I did the same thing as above, just redefining KEY at the beginning
    150 with: ~(setq key (quail-keyboard-translate key))~
    151 
    152 *Yes!!  It works!*
    153 
    154 아이고...  There is still a major problem: only the first character is
    155 actually translated through [[help:quail-input-method][quail-input-method]].
    156 
    157 *** The problem
    158 
    159 You see the second half of that function?  A loop that reads input
    160 until a complete syllabic block has been entered.
    161 
    162 #+name: while-loop
    163 #+begin_src emacs-lisp
    164   ;; From `hangul2-input-method', paraphrased
    165   (let* ((seq (read-key-sequence nil))
    166          key)
    167     (cond ((and (stringp seq)
    168                 (= 1 (length seq))
    169                 (setq key (aref seq 0))
    170                 (alphabetp key))
    171            (hangul2-input-method-internal key))))
    172 #+end_src
    173 
    174 Well, I replaced the initial key, but the following keys also need to
    175 be translated.  This is simple -- because it's only passed to the
    176 internal function when it's in the latin alphabet, it won't interfere
    177 with any keybindings.
    178 
    179 * Finale
    180 
    181 The only changes needed to allow Hangul input with dvorak are:
    182 
    183 - Create and/or enable a dvorak =quail-input-method=
    184 - Use quail's key translations in =hangul2-input-method=
    185   - For the initial key (argument of the function)
    186   - For the following keys (in the while loop)
    187 
    188 Note: This needs to be redefined after loading =quail/hangul=, so you
    189 might consider wrapping it in =with-eval-after-load "quail/hangul"= in
    190 your init file.
    191 
    192 ** hangul2
    193 
    194 #+name: final
    195 #+begin_src emacs-lisp
    196   (defun hangul2-input-method (key)
    197     "2-Bulsik input method."
    198     (setq key (quail-keyboard-translate key))
    199     (if (or buffer-read-only (not (alphabetp key)))
    200         (list key)
    201       (quail-setup-overlays nil)
    202       (let ((input-method-function nil)
    203             (echo-keystrokes 0)
    204             (help-char nil))
    205         (setq hangul-queue (make-vector 6 0))
    206         (hangul2-input-method-internal key)
    207         (unwind-protect
    208             (catch 'exit-input-loop
    209               (while t
    210                 (let* ((seq (read-key-sequence nil))
    211                        (cmd (lookup-key hangul-im-keymap seq))
    212                        key)
    213                   (cond
    214                    ((and (stringp seq)
    215                          (= 1 (length seq))
    216                          (setq key (quail-keyboard-translate (aref seq 0)))
    217                          (alphabetp key))
    218                     (hangul2-input-method-internal key))
    219                    ((commandp cmd)
    220                     (call-interactively cmd))
    221                    (t
    222                     (setq unread-command-events
    223                           (nconc (listify-key-sequence seq)
    224                                  unread-command-events))
    225                     (throw 'exit-input-loop nil))))))
    226           (quail-delete-overlays)))))
    227 #+end_src
    228 
    229 ** hangul3
    230 
    231 #+begin_src emacs-lisp
    232   (defun hangul3-input-method (key)
    233     "3-Bulsik final input method."
    234     (setq key (quail-keyboard-translate key))
    235     (if (or buffer-read-only (< key 33) (>= key 127))
    236 	(list key)
    237       (quail-setup-overlays nil)
    238       (let ((input-method-function nil)
    239 	    (echo-keystrokes 0)
    240 	    (help-char nil))
    241 	(setq hangul-queue (make-vector 6 0))
    242 	(hangul3-input-method-internal key)
    243 	(unwind-protect
    244 	    (catch 'exit-input-loop
    245 	      (while t
    246 		(let* ((seq (read-key-sequence nil))
    247 		       (cmd (lookup-key hangul-im-keymap seq))
    248 		       key)
    249 		  (cond ((and (stringp seq)
    250 			      (= 1 (length seq))
    251 			      (setq key (quail-keyboard-translate (aref seq 0)))
    252 			      (and (>= key 33) (< key 127)))
    253 			 (hangul3-input-method-internal key))
    254 			((commandp cmd)
    255 			 (call-interactively cmd))
    256 			(t
    257 			 (setq unread-command-events
    258 			       (nconc (listify-key-sequence seq)
    259 				      unread-command-events))
    260 			 (throw 'exit-input-loop nil))))))
    261 	  (quail-delete-overlays)))))
    262 #+end_src
    263 
    264 ** hangul390
    265 
    266 #+begin_src emacs-lisp
    267   (defun hangul390-input-method (key)
    268     "3-Bulsik 390 input method."
    269     (setq key (quail-keyboard-translate key))
    270     (if (or buffer-read-only (< key 33) (>= key 127))
    271 	(list key)
    272       (quail-setup-overlays nil)
    273       (let ((input-method-function nil)
    274 	    (echo-keystrokes 0)
    275 	    (help-char nil))
    276 	(setq hangul-queue (make-vector 6 0))
    277 	(hangul390-input-method-internal key)
    278 	(unwind-protect
    279 	    (catch 'exit-input-loop
    280 	      (while t
    281 		(let* ((seq (read-key-sequence nil))
    282 		       (cmd (lookup-key hangul-im-keymap seq))
    283 		       key)
    284 		  (cond ((and (stringp seq)
    285 			      (= 1 (length seq))
    286 			      (setq key (quail-keyboard-translate (aref seq 0)))
    287 			      (and (>= key 33) (< key 127)))
    288 			 (hangul390-input-method-internal key))
    289 			((commandp cmd)
    290 			 (call-interactively cmd))
    291 			(t
    292 			 (setq unread-command-events
    293 			       (nconc (listify-key-sequence seq)
    294 				      unread-command-events))
    295 			 (throw 'exit-input-loop nil))))))
    296 	  (quail-delete-overlays)))))
    297 #+end_src