github.com/gop9/olt@v0.0.0-20200202132135-d956aad50b08/gio/app/internal/xkb/xkb_unix.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 // +build linux,!android freebsd 4 5 // Package xkb implements a Go interface for the X Keyboard Extension library. 6 package xkb 7 8 import ( 9 "errors" 10 "fmt" 11 "os" 12 "syscall" 13 "unicode" 14 "unicode/utf8" 15 "unsafe" 16 17 "github.com/gop9/olt/gio/io/event" 18 "github.com/gop9/olt/gio/io/key" 19 ) 20 21 /* 22 #cgo LDFLAGS: -lxkbcommon 23 #cgo freebsd CFLAGS: -I/usr/local/include 24 #cgo freebsd LDFLAGS: -L/usr/local/lib 25 26 #include <stdlib.h> 27 #include <xkbcommon/xkbcommon.h> 28 #include <xkbcommon/xkbcommon-compose.h> 29 */ 30 import "C" 31 32 type Context struct { 33 Ctx *C.struct_xkb_context 34 keyMap *C.struct_xkb_keymap 35 state *C.struct_xkb_state 36 compTable *C.struct_xkb_compose_table 37 compState *C.struct_xkb_compose_state 38 utf8Buf []byte 39 } 40 41 var ( 42 _XKB_MOD_NAME_CTRL = []byte("Control\x00") 43 _XKB_MOD_NAME_SHIFT = []byte("Shift\x00") 44 _XKB_MOD_NAME_ALT = []byte("Mod1\x00") 45 _XKB_MOD_NAME_LOGO = []byte("Mod4\x00") 46 ) 47 48 func (x *Context) Destroy() { 49 if x.compState != nil { 50 C.xkb_compose_state_unref(x.compState) 51 x.compState = nil 52 } 53 if x.compTable != nil { 54 C.xkb_compose_table_unref(x.compTable) 55 x.compTable = nil 56 } 57 x.DestroyKeymapState() 58 if x.Ctx != nil { 59 C.xkb_context_unref(x.Ctx) 60 x.Ctx = nil 61 } 62 } 63 64 func New() (*Context, error) { 65 ctx := &Context{ 66 Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS), 67 } 68 if ctx.Ctx == nil { 69 return nil, errors.New("newXKB: xkb_context_new failed") 70 } 71 locale := os.Getenv("LC_ALL") 72 if locale == "" { 73 locale = os.Getenv("LC_CTYPE") 74 } 75 if locale == "" { 76 locale = os.Getenv("LANG") 77 } 78 if locale == "" { 79 locale = "C" 80 } 81 cloc := C.CString(locale) 82 defer C.free(unsafe.Pointer(cloc)) 83 ctx.compTable = C.xkb_compose_table_new_from_locale(ctx.Ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS) 84 if ctx.compTable == nil { 85 ctx.Destroy() 86 return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed") 87 } 88 ctx.compState = C.xkb_compose_state_new(ctx.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS) 89 if ctx.compState == nil { 90 ctx.Destroy() 91 return nil, errors.New("newXKB: xkb_compose_state_new failed") 92 } 93 return ctx, nil 94 } 95 96 func (x *Context) DestroyKeymapState() { 97 if x.state != nil { 98 C.xkb_state_unref(x.state) 99 x.state = nil 100 } 101 if x.keyMap != nil { 102 C.xkb_keymap_unref(x.keyMap) 103 x.keyMap = nil 104 } 105 } 106 107 // SetKeymap sets the keymap and state. The context takes ownership of the 108 // keymap and state and frees them in Destroy. 109 func (x *Context) SetKeymap(xkbKeyMap, xkbState unsafe.Pointer) { 110 x.DestroyKeymapState() 111 x.keyMap = (*C.struct_xkb_keymap)(xkbKeyMap) 112 x.state = (*C.struct_xkb_state)(xkbState) 113 } 114 115 func (x *Context) LoadKeymap(format int, fd int, size int) error { 116 x.DestroyKeymapState() 117 mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED) 118 if err != nil { 119 return fmt.Errorf("newXKB: mmap of keymap failed: %v", err) 120 } 121 defer syscall.Munmap(mapData) 122 keyMap := C.xkb_keymap_new_from_buffer(x.Ctx, (*C.char)(unsafe.Pointer(&mapData[0])), C.size_t(size-1), C.XKB_KEYMAP_FORMAT_TEXT_V1, C.XKB_KEYMAP_COMPILE_NO_FLAGS) 123 if keyMap == nil { 124 return errors.New("newXKB: xkb_keymap_new_from_buffer failed") 125 } 126 state := C.xkb_state_new(keyMap) 127 if state == nil { 128 C.xkb_keymap_unref(keyMap) 129 return errors.New("newXKB: xkb_state_new failed") 130 } 131 x.keyMap = keyMap 132 x.state = state 133 return nil 134 } 135 136 func (x *Context) DispatchKey(keyCode uint32) (events []event.Event) { 137 if x.state == nil { 138 return 139 } 140 kc := C.xkb_keycode_t(keyCode) 141 if len(x.utf8Buf) == 0 { 142 x.utf8Buf = make([]byte, 1) 143 } 144 sym := C.xkb_state_key_get_one_sym(x.state, kc) 145 if name, ok := convertKeysym(sym); ok { 146 cmd := key.Event{Name: name} 147 // Ensure that a physical backtab key is translated to 148 // Shift-Tab. 149 if sym == C.XKB_KEY_ISO_Left_Tab { 150 cmd.Modifiers |= key.ModShift 151 } 152 if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 { 153 cmd.Modifiers |= key.ModCtrl 154 } 155 if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_SHIFT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 { 156 cmd.Modifiers |= key.ModShift 157 } 158 if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_ALT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 { 159 cmd.Modifiers |= key.ModAlt 160 } 161 if C.xkb_state_mod_name_is_active(x.state, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_LOGO[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 { 162 cmd.Modifiers |= key.ModSuper 163 } 164 events = append(events, cmd) 165 } 166 C.xkb_compose_state_feed(x.compState, sym) 167 var str []byte 168 switch C.xkb_compose_state_get_status(x.compState) { 169 case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING: 170 return 171 case C.XKB_COMPOSE_COMPOSED: 172 size := C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) 173 if int(size) >= len(x.utf8Buf) { 174 x.utf8Buf = make([]byte, size+1) 175 size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) 176 } 177 C.xkb_compose_state_reset(x.compState) 178 str = x.utf8Buf[:size] 179 case C.XKB_COMPOSE_NOTHING: 180 str = x.charsForKeycode(kc) 181 } 182 // Report only printable runes. 183 var n int 184 for n < len(str) { 185 r, s := utf8.DecodeRune(str) 186 if unicode.IsPrint(r) { 187 n += s 188 } else { 189 copy(str[n:], str[n+s:]) 190 str = str[:len(str)-s] 191 } 192 } 193 if len(str) > 0 { 194 events = append(events, key.EditEvent{Text: string(str)}) 195 } 196 return 197 } 198 199 func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte { 200 size := C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) 201 if int(size) >= len(x.utf8Buf) { 202 x.utf8Buf = make([]byte, size+1) 203 size = C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) 204 } 205 return x.utf8Buf[:size] 206 } 207 208 func (x *Context) IsRepeatKey(keyCode uint32) bool { 209 kc := C.xkb_keycode_t(keyCode) 210 return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1 211 } 212 213 func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) { 214 if x.state == nil { 215 return 216 } 217 C.xkb_state_update_mask(x.state, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked), 218 C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup)) 219 } 220 221 func convertKeysym(s C.xkb_keysym_t) (string, bool) { 222 if 'a' <= s && s <= 'z' { 223 return string(s - 'a' + 'A'), true 224 } 225 if ' ' <= s && s <= '~' { 226 return string(s), true 227 } 228 var n string 229 switch s { 230 case C.XKB_KEY_Escape: 231 n = key.NameEscape 232 case C.XKB_KEY_Left: 233 n = key.NameLeftArrow 234 case C.XKB_KEY_Right: 235 n = key.NameRightArrow 236 case C.XKB_KEY_Return: 237 n = key.NameReturn 238 case C.XKB_KEY_KP_Enter: 239 n = key.NameEnter 240 case C.XKB_KEY_Up: 241 n = key.NameUpArrow 242 case C.XKB_KEY_Down: 243 n = key.NameDownArrow 244 case C.XKB_KEY_Home: 245 n = key.NameHome 246 case C.XKB_KEY_End: 247 n = key.NameEnd 248 case C.XKB_KEY_BackSpace: 249 n = key.NameDeleteBackward 250 case C.XKB_KEY_Delete: 251 n = key.NameDeleteForward 252 case C.XKB_KEY_Page_Up: 253 n = key.NamePageUp 254 case C.XKB_KEY_Page_Down: 255 n = key.NamePageDown 256 case C.XKB_KEY_F1: 257 n = "F1" 258 case C.XKB_KEY_F2: 259 n = "F2" 260 case C.XKB_KEY_F3: 261 n = "F3" 262 case C.XKB_KEY_F4: 263 n = "F4" 264 case C.XKB_KEY_F5: 265 n = "F5" 266 case C.XKB_KEY_F6: 267 n = "F6" 268 case C.XKB_KEY_F7: 269 n = "F7" 270 case C.XKB_KEY_F8: 271 n = "F8" 272 case C.XKB_KEY_F9: 273 n = "F9" 274 case C.XKB_KEY_F10: 275 n = "F10" 276 case C.XKB_KEY_F11: 277 n = "F11" 278 case C.XKB_KEY_F12: 279 n = "F12" 280 case C.XKB_KEY_Tab, C.XKB_KEY_KP_Tab, C.XKB_KEY_ISO_Left_Tab: 281 n = key.NameTab 282 case 0x20, C.XKB_KEY_KP_Space: 283 n = "Space" 284 default: 285 return "", false 286 } 287 return n, true 288 }