gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/app/internal/xkb/xkb_unix.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 //go:build (linux && !android) || freebsd || openbsd 4 // +build linux,!android freebsd openbsd 5 6 // Package xkb implements a Go interface for the X Keyboard Extension library. 7 package xkb 8 9 import ( 10 "errors" 11 "fmt" 12 "os" 13 "syscall" 14 "unicode" 15 "unicode/utf8" 16 "unsafe" 17 18 "gioui.org/io/event" 19 "gioui.org/io/key" 20 ) 21 22 /* 23 #cgo linux pkg-config: xkbcommon 24 #cgo freebsd openbsd CFLAGS: -I/usr/local/include 25 #cgo freebsd openbsd LDFLAGS: -L/usr/local/lib -lxkbcommon 26 27 #include <stdlib.h> 28 #include <xkbcommon/xkbcommon.h> 29 #include <xkbcommon/xkbcommon-compose.h> 30 */ 31 import "C" 32 33 type Context struct { 34 Ctx *C.struct_xkb_context 35 keyMap *C.struct_xkb_keymap 36 state *C.struct_xkb_state 37 compTable *C.struct_xkb_compose_table 38 compState *C.struct_xkb_compose_state 39 utf8Buf []byte 40 } 41 42 var ( 43 _XKB_MOD_NAME_CTRL = []byte("Control\x00") 44 _XKB_MOD_NAME_SHIFT = []byte("Shift\x00") 45 _XKB_MOD_NAME_ALT = []byte("Mod1\x00") 46 _XKB_MOD_NAME_LOGO = []byte("Mod4\x00") 47 ) 48 49 func (x *Context) Destroy() { 50 if x.compState != nil { 51 C.xkb_compose_state_unref(x.compState) 52 x.compState = nil 53 } 54 if x.compTable != nil { 55 C.xkb_compose_table_unref(x.compTable) 56 x.compTable = nil 57 } 58 x.DestroyKeymapState() 59 if x.Ctx != nil { 60 C.xkb_context_unref(x.Ctx) 61 x.Ctx = nil 62 } 63 } 64 65 func New() (*Context, error) { 66 ctx := &Context{ 67 Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS), 68 } 69 if ctx.Ctx == nil { 70 return nil, errors.New("newXKB: xkb_context_new failed") 71 } 72 locale := os.Getenv("LC_ALL") 73 if locale == "" { 74 locale = os.Getenv("LC_CTYPE") 75 } 76 if locale == "" { 77 locale = os.Getenv("LANG") 78 } 79 if locale == "" { 80 locale = "C" 81 } 82 cloc := C.CString(locale) 83 defer C.free(unsafe.Pointer(cloc)) 84 ctx.compTable = C.xkb_compose_table_new_from_locale(ctx.Ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS) 85 if ctx.compTable == nil { 86 ctx.Destroy() 87 return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed") 88 } 89 ctx.compState = C.xkb_compose_state_new(ctx.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS) 90 if ctx.compState == nil { 91 ctx.Destroy() 92 return nil, errors.New("newXKB: xkb_compose_state_new failed") 93 } 94 return ctx, nil 95 } 96 97 func (x *Context) DestroyKeymapState() { 98 if x.state != nil { 99 C.xkb_state_unref(x.state) 100 x.state = nil 101 } 102 if x.keyMap != nil { 103 C.xkb_keymap_unref(x.keyMap) 104 x.keyMap = nil 105 } 106 } 107 108 // SetKeymap sets the keymap and state. The context takes ownership of the 109 // keymap and state and frees them in Destroy. 110 func (x *Context) SetKeymap(xkbKeyMap, xkbState unsafe.Pointer) { 111 x.DestroyKeymapState() 112 x.keyMap = (*C.struct_xkb_keymap)(xkbKeyMap) 113 x.state = (*C.struct_xkb_state)(xkbState) 114 } 115 116 func (x *Context) LoadKeymap(format int, fd int, size int) error { 117 x.DestroyKeymapState() 118 mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED) 119 if err != nil { 120 return fmt.Errorf("newXKB: mmap of keymap failed: %v", err) 121 } 122 defer syscall.Munmap(mapData) 123 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) 124 if keyMap == nil { 125 return errors.New("newXKB: xkb_keymap_new_from_buffer failed") 126 } 127 state := C.xkb_state_new(keyMap) 128 if state == nil { 129 C.xkb_keymap_unref(keyMap) 130 return errors.New("newXKB: xkb_state_new failed") 131 } 132 x.keyMap = keyMap 133 x.state = state 134 return nil 135 } 136 137 func (x *Context) Modifiers() key.Modifiers { 138 var mods key.Modifiers 139 if x.state == nil { 140 return mods 141 } 142 143 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 { 144 mods |= key.ModCtrl 145 } 146 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 { 147 mods |= key.ModShift 148 } 149 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 { 150 mods |= key.ModAlt 151 } 152 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 { 153 mods |= key.ModSuper 154 } 155 return mods 156 } 157 158 func (x *Context) DispatchKey(keyCode uint32, state key.State) (events []event.Event) { 159 if x.state == nil { 160 return 161 } 162 kc := C.xkb_keycode_t(keyCode) 163 if len(x.utf8Buf) == 0 { 164 x.utf8Buf = make([]byte, 1) 165 } 166 sym := C.xkb_state_key_get_one_sym(x.state, kc) 167 if name, ok := convertKeysym(sym); ok { 168 cmd := key.Event{ 169 Name: name, 170 Modifiers: x.Modifiers(), 171 State: state, 172 } 173 // Ensure that a physical backtab key is translated to 174 // Shift-Tab. 175 if sym == C.XKB_KEY_ISO_Left_Tab { 176 cmd.Modifiers |= key.ModShift 177 } 178 events = append(events, cmd) 179 } 180 C.xkb_compose_state_feed(x.compState, sym) 181 var str []byte 182 switch C.xkb_compose_state_get_status(x.compState) { 183 case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING: 184 return 185 case C.XKB_COMPOSE_COMPOSED: 186 size := C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) 187 if int(size) >= len(x.utf8Buf) { 188 x.utf8Buf = make([]byte, size+1) 189 size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) 190 } 191 C.xkb_compose_state_reset(x.compState) 192 str = x.utf8Buf[:size] 193 case C.XKB_COMPOSE_NOTHING: 194 mod := x.Modifiers() 195 if mod&(key.ModCtrl|key.ModAlt|key.ModSuper) == 0 { 196 str = x.charsForKeycode(kc) 197 } 198 } 199 // Report only printable runes. 200 var n int 201 for n < len(str) { 202 r, s := utf8.DecodeRune(str) 203 if unicode.IsPrint(r) { 204 n += s 205 } else { 206 copy(str[n:], str[n+s:]) 207 str = str[:len(str)-s] 208 } 209 } 210 if state == key.Press && len(str) > 0 { 211 events = append(events, key.EditEvent{Text: string(str)}) 212 } 213 return 214 } 215 216 func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte { 217 size := C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) 218 if int(size) >= len(x.utf8Buf) { 219 x.utf8Buf = make([]byte, size+1) 220 size = C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) 221 } 222 return x.utf8Buf[:size] 223 } 224 225 func (x *Context) IsRepeatKey(keyCode uint32) bool { 226 if x.state == nil { 227 return false 228 } 229 kc := C.xkb_keycode_t(keyCode) 230 return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1 231 } 232 233 func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) { 234 if x.state == nil { 235 return 236 } 237 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), 238 C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup)) 239 } 240 241 func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) { 242 if 'a' <= s && s <= 'z' { 243 return key.Name(rune(s - 'a' + 'A')), true 244 } 245 if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 { 246 return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true 247 } 248 if ' ' < s && s <= '~' { 249 return key.Name(rune(s)), true 250 } 251 var n key.Name 252 switch s { 253 case C.XKB_KEY_Escape: 254 n = key.NameEscape 255 case C.XKB_KEY_Left: 256 n = key.NameLeftArrow 257 case C.XKB_KEY_Right: 258 n = key.NameRightArrow 259 case C.XKB_KEY_Return: 260 n = key.NameReturn 261 case C.XKB_KEY_Up: 262 n = key.NameUpArrow 263 case C.XKB_KEY_Down: 264 n = key.NameDownArrow 265 case C.XKB_KEY_Home: 266 n = key.NameHome 267 case C.XKB_KEY_End: 268 n = key.NameEnd 269 case C.XKB_KEY_BackSpace: 270 n = key.NameDeleteBackward 271 case C.XKB_KEY_Delete: 272 n = key.NameDeleteForward 273 case C.XKB_KEY_Page_Up: 274 n = key.NamePageUp 275 case C.XKB_KEY_Page_Down: 276 n = key.NamePageDown 277 case C.XKB_KEY_F1: 278 n = key.NameF1 279 case C.XKB_KEY_F2: 280 n = key.NameF2 281 case C.XKB_KEY_F3: 282 n = key.NameF3 283 case C.XKB_KEY_F4: 284 n = key.NameF4 285 case C.XKB_KEY_F5: 286 n = key.NameF5 287 case C.XKB_KEY_F6: 288 n = key.NameF6 289 case C.XKB_KEY_F7: 290 n = key.NameF7 291 case C.XKB_KEY_F8: 292 n = key.NameF8 293 case C.XKB_KEY_F9: 294 n = key.NameF9 295 case C.XKB_KEY_F10: 296 n = key.NameF10 297 case C.XKB_KEY_F11: 298 n = key.NameF11 299 case C.XKB_KEY_F12: 300 n = key.NameF12 301 case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab: 302 n = key.NameTab 303 case 0x20: 304 n = key.NameSpace 305 case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R: 306 n = key.NameCtrl 307 case C.XKB_KEY_Shift_L, C.XKB_KEY_Shift_R: 308 n = key.NameShift 309 case C.XKB_KEY_Alt_L, C.XKB_KEY_Alt_R: 310 n = key.NameAlt 311 case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R: 312 n = key.NameSuper 313 314 case C.XKB_KEY_KP_Space: 315 n = key.NameSpace 316 case C.XKB_KEY_KP_Tab: 317 n = key.NameTab 318 case C.XKB_KEY_KP_Enter: 319 n = key.NameEnter 320 case C.XKB_KEY_KP_F1: 321 n = key.NameF1 322 case C.XKB_KEY_KP_F2: 323 n = key.NameF2 324 case C.XKB_KEY_KP_F3: 325 n = key.NameF3 326 case C.XKB_KEY_KP_F4: 327 n = key.NameF4 328 case C.XKB_KEY_KP_Home: 329 n = key.NameHome 330 case C.XKB_KEY_KP_Left: 331 n = key.NameLeftArrow 332 case C.XKB_KEY_KP_Up: 333 n = key.NameUpArrow 334 case C.XKB_KEY_KP_Right: 335 n = key.NameRightArrow 336 case C.XKB_KEY_KP_Down: 337 n = key.NameDownArrow 338 case C.XKB_KEY_KP_Prior: 339 // not supported 340 return "", false 341 case C.XKB_KEY_KP_Next: 342 // not supported 343 return "", false 344 case C.XKB_KEY_KP_End: 345 n = key.NameEnd 346 case C.XKB_KEY_KP_Begin: 347 n = key.NameHome 348 case C.XKB_KEY_KP_Insert: 349 // not supported 350 return "", false 351 case C.XKB_KEY_KP_Delete: 352 n = key.NameDeleteForward 353 case C.XKB_KEY_KP_Multiply: 354 n = "*" 355 case C.XKB_KEY_KP_Add: 356 n = "+" 357 case C.XKB_KEY_KP_Separator: 358 // not supported 359 return "", false 360 case C.XKB_KEY_KP_Subtract: 361 n = "-" 362 case C.XKB_KEY_KP_Decimal: 363 // TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in 364 // German, the decimal is a comma, not a period. 365 n = "." 366 case C.XKB_KEY_KP_Divide: 367 n = "/" 368 case C.XKB_KEY_KP_Equal: 369 n = "=" 370 371 default: 372 return "", false 373 } 374 return n, true 375 }