namespace eval keymap { try { # C implementation based on libkeymap # libkeymap is part of the kbd package, but most distros don't --enable-libkeymap c loadlibLd libkeymap.so c loadlibLd libkbdfile.so rename [c create] kc kc include kc include kc include kc include kc include kc include kc code { static const char *const dirpath[] = { ".", "/usr/lib/kbd/keymaps/**", "/usr/share/kbd/keymaps/**", NULL }; static const char *const suffixes[] = { "", ".kmap", ".map", NULL }; typedef struct lk_ctx lkctx_t; } kc argtype lkctx_t* { lkctx_t* $argname; sscanf(Tcl_GetString($obj), "(lkctx_t*) 0x%p", &$argname); } kc rtype lkctx_t* { $robj = Tcl_ObjPrintf("(lkctx_t*) 0x%" PRIxPTR, (uintptr_t) $rvalue); } kc proc load {char* name} lkctx_t* { lkctx_t* ctx = lk_init(); // @TODO assert failures struct kbdfile_ctx* fctx = kbdfile_context_new(); struct kbdfile* file = kbdfile_new(fctx); kbdfile_find(name, dirpath, suffixes, file); lk_parse_keymap(ctx, file); lk_add_constants(ctx); kbdfile_close(file); kbdfile_context_free(fctx); return ctx; } kc proc resolve {lkctx_t* ctx int key int mods} Tcl_Obj* { if (!lk_map_exists(ctx, mods)) return Tcl_NewStringObj("", 0); // see https://github.com/legionus/kbd/blob/master/src/libkeymap/dump.c#L361-L395 int code = lk_get_key(ctx, mods, key); char* result; if (KTYP(code) == KT_LETTER) { // this key should be affected by capslock, but do we care? code = K(KT_LATIN, KVAL(code)); result = lk_code_to_ksym(ctx, code); } else if (KTYP(code) == KT_META && KVAL(code) < 128) { code = K(KT_LATIN, KVAL(code)); char* base = lk_code_to_ksym(ctx, code); asprintf(&result, "Meta_%s", base); free(base); } else { result = lk_code_to_ksym(ctx, code); } if (result == NULL) { return Tcl_NewStringObj("", 0); } else { Tcl_Obj* tclres = Tcl_NewStringObj(result, -1); free(result); return tclres; } } kc proc dump {lkctx_t* ctx} void { lk_dump_keymaps(ctx, stdout); lk_dump_keys(ctx, stdout, LK_SHAPE_FULL_TABLE, 0); } kc proc destroy {lkctx_t* ctx} void { lk_free(ctx); } kc compile namespace export * } on error e { # Tcl implementation using loadkeys/dumpkeys proc _fillRange {range} { lassign [split $range -] from to if {$to eq ""} {return $from} set out "" for {set i $from} {$i <= $to} {incr i} { lappend out $i } return $out } proc _parseKey {key} { if {$key eq "nul"} {return ""} if {[string index $key 0] eq "+"} { return [string range $key 1 end] } return $key } proc _unparseKey {key} { if {$key eq ""} {return "nul"} if {[string length $key] == 1} { return "+$key" } return $key } proc load {name} { if {$::isLaptop} { exec sudo loadkeys -C /dev/tty0 $name set table [exec sudo dumpkeys -C /dev/tty0 -kf] set extraArgs {-C /dev/tty0} } else { exec loadkeys $name set table [exec dumpkeys -kf] } set mods [_fillRange 0-15] set codes "" foreach line [split $table "\n"] { switch [lindex $line 0] { keymaps { set map [split [lindex $line 1] ,] set mods [concat {*}[lmap r $map {_fillRange $r}]] } keycode { set code [lindex $line 1] set keys [lrange $line 3 end] set keys [lmap k $keys {_parseKey $k}] dict append codes $code $keys } } } return [list $mods $codes] } proc dump {km} { lassign $km mods codes puts "keymap [join $mods ,]" dict fir {code keys} $codes { set keys [lmap k $keys {_unparseKey $k}] puts "keycode $code = [join $keys \t]" } } proc resolve {km code mod} { # @TODO: error/range handling lassign $km mods codes set mod [lsearch -exact $mods $mod] return [lindex [dict get $codes $code] $mod] } proc destroy {km} {} ;# for compatibility with C impl namespace export load dump resolve destroy } set modWeights { Shift 1 AltGr 2 Control 4 Alt 8 } # @TODO: complete this table or find a better data source # https://wiki.linuxquestions.org/wiki/List_of_keysyms set remapTable { one 1 two 2 three 3 four 4 five 5 six 6 seven 7 eight 8 nine 9 zero 0 apostrophe ' igrave ` space " " exclam ! quotedbl \" sterlin ??? dollar \$ percent % ampersand & slash / parenleft ( parenright ) equal = question ? asciicircum ^ numbersign # plus + minus - underscore _ } proc printable {keysym} { variable remapTable if {![string is lower $keysym]} {return ""} return [dict_getdef $remapTable $keysym $keysym] } namespace export modWeights printable namespace ensemble create } if {$::argv0 eq [info script]} { set tkm [keymap load "it"] # keymap dump $tkm for {set i 0} {$i < 15} {incr i} { puts "30/$i [keymap resolve $tkm 30 $i]" } puts "252/0 [keymap resolve $tkm 252 0]" puts [join [lmap code {5 12 27} {keymap printable [keymap resolve $tkm $code 0]}] ""] keymap destroy $tkm }