(local { : Gdk } (require :lgi)) (local { : view } (require :fennel)) (local lume (require :lume)) (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 compact [xs] (icollect [_ v (ipairs xs)] v)) (fn index->string [index] (let [Mod Gdk.ModifierType [keyval modmask] (lume.map (lume.split index ":") tonumber) chars []] (if (> (band modmask Mod.CONTROL_MASK) 0) (table.insert chars "C")) (if (> (band modmask Mod.MOD1_MASK) 0) (table.insert chars "M")) (if (> (band modmask Mod.MOD4_MASK) 0) (table.insert chars "S")) (table.insert chars (Gdk.keyval_name keyval)) (table.concat chars "-"))) (let [v (index->string "103:0")] (assert (= v "g") v)) (let [v (index->string "65:0")] (assert (= v "A") v)) (let [v (index->string "120:4")] (assert (= v "C-x") v)) (let [v (index->string "100:8")] (assert (= v "M-d") v)) (let [v (index->string "100:12")] (assert (= v "C-M-d") v)) (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) (where nil (= c "103:4")) (do (set m keymap) (values nil "cancelled")) _ (do (set m keymap) (values nil (.. "No binding for " (index->string c) " "))))))) })) { : recogniser :keyval { :Escape (Gdk.keyval_from_name "Escape") } :_ { ;; symbols in _ are exported only for testing : keychord->spec } }