(local { : Gdk } (require :lgi)) (local { : view } (require :fennel)) (local modifier-keyvals ;; we need to detect and discard modifier-only key events when ;; looking for the next key in a key sequence. Gtk/Gdk ;; allegedly has KeyEvent.is_modifier to do this but it's always ;; 0 because GI doesn't expose it. (let [names [ :Control_L :Control_R :Control :Shift_L :Shift_R :WakeUp ; labelled "Fn" :Super_L ; labelled with Windows logo :Super_R ; menu key? not on my keyboard :Alt_L :Alt_R :ISO_Level3_Shift ; AltGr ]] (collect [_ n (ipairs names)] (values (Gdk.keyval_from_name n) n)))) (fn modifier? [keyval] (. modifier-keyvals keyval)) (fn keychord->spec [keychord] (let [Mod Gdk.ModifierType symbol (keychord:match "(%w+)$") upper? (and (symbol:match "%u") true) modmask (accumulate [m (if upper? Mod.SHIFT_MASK 0) v (keychord:gmatch "(%w+)-")] (match (v:lower) "m" (bor m Mod.MOD1_MASK) "c" (bor m Mod.CONTROL_MASK) "s" (bor m Mod.MOD4_MASK)))] { :keyval (string.byte (symbol:lower)) : modmask })) (fn spec->index [spec] (string.format "%d:%d" spec.keyval spec.modmask)) (fn event->index [event] (let [modmask (accumulate [m 0 k _ (pairs event.state)] (bor m (. Gdk.ModifierType k)))] (spec->index {:keyval event.keyval : modmask}))) (fn command? [tbl] ;; a keymap entry has a string as key, a command ;; definition is a numerically-indexed array (if (. tbl 1) true)) (fn keymap? [tbl] (not (. tbl 1))) (fn compile-keymap [input] (collect [k v (pairs input)] (let [f (-> k keychord->spec spec->index)] (if (keymap? v) (values f (compile-keymap v)) (values f v))))) (fn recogniser [source-keymap] (let [keymap (compile-keymap source-keymap)] (var m keymap) { :accept-event (fn [_ e] (when (not (modifier? e.keyval)) (let [c (event->index e)] (match (. m c) (where v (keymap? v)) (do (set m v) (values nil (.. c " "))) (where v (command? v)) (do (set m keymap) v) _ (do (set m keymap) (values nil (.. "No binding for " (view e) " "))))))) })) { : recogniser :keyval { :Escape (Gdk.keyval_from_name "Escape") } :_ { ;; symbols in _ are exported only for testing : keychord->spec } }