123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- // SPDX-License-Identifier: Unlicense OR MIT
- //go:build (linux && !android) || freebsd || openbsd
- // +build linux,!android freebsd openbsd
- // Package xkb implements a Go interface for the X Keyboard Extension library.
- package xkb
- import (
- "errors"
- "fmt"
- "os"
- "syscall"
- "unicode"
- "unicode/utf8"
- "unsafe"
- "gioui.org/io/event"
- "gioui.org/io/key"
- )
- /*
- #cgo linux pkg-config: xkbcommon
- #cgo freebsd openbsd CFLAGS: -I/usr/local/include
- #cgo freebsd openbsd LDFLAGS: -L/usr/local/lib -lxkbcommon
- #include <stdlib.h>
- #include <xkbcommon/xkbcommon.h>
- #include <xkbcommon/xkbcommon-compose.h>
- */
- import "C"
- type Context struct {
- Ctx *C.struct_xkb_context
- keyMap *C.struct_xkb_keymap
- state *C.struct_xkb_state
- compTable *C.struct_xkb_compose_table
- compState *C.struct_xkb_compose_state
- utf8Buf []byte
- }
- var (
- _XKB_MOD_NAME_CTRL = []byte("Control\x00")
- _XKB_MOD_NAME_SHIFT = []byte("Shift\x00")
- _XKB_MOD_NAME_ALT = []byte("Mod1\x00")
- _XKB_MOD_NAME_LOGO = []byte("Mod4\x00")
- )
- func (x *Context) Destroy() {
- if x.compState != nil {
- C.xkb_compose_state_unref(x.compState)
- x.compState = nil
- }
- if x.compTable != nil {
- C.xkb_compose_table_unref(x.compTable)
- x.compTable = nil
- }
- x.DestroyKeymapState()
- if x.Ctx != nil {
- C.xkb_context_unref(x.Ctx)
- x.Ctx = nil
- }
- }
- func New() (*Context, error) {
- ctx := &Context{
- Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
- }
- if ctx.Ctx == nil {
- return nil, errors.New("newXKB: xkb_context_new failed")
- }
- locale := os.Getenv("LC_ALL")
- if locale == "" {
- locale = os.Getenv("LC_CTYPE")
- }
- if locale == "" {
- locale = os.Getenv("LANG")
- }
- if locale == "" {
- locale = "C"
- }
- cloc := C.CString(locale)
- defer C.free(unsafe.Pointer(cloc))
- ctx.compTable = C.xkb_compose_table_new_from_locale(ctx.Ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS)
- if ctx.compTable == nil {
- ctx.Destroy()
- return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed")
- }
- ctx.compState = C.xkb_compose_state_new(ctx.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS)
- if ctx.compState == nil {
- ctx.Destroy()
- return nil, errors.New("newXKB: xkb_compose_state_new failed")
- }
- return ctx, nil
- }
- func (x *Context) DestroyKeymapState() {
- if x.state != nil {
- C.xkb_state_unref(x.state)
- x.state = nil
- }
- if x.keyMap != nil {
- C.xkb_keymap_unref(x.keyMap)
- x.keyMap = nil
- }
- }
- // SetKeymap sets the keymap and state. The context takes ownership of the
- // keymap and state and frees them in Destroy.
- func (x *Context) SetKeymap(xkbKeyMap, xkbState unsafe.Pointer) {
- x.DestroyKeymapState()
- x.keyMap = (*C.struct_xkb_keymap)(xkbKeyMap)
- x.state = (*C.struct_xkb_state)(xkbState)
- }
- func (x *Context) LoadKeymap(format int, fd int, size int) error {
- x.DestroyKeymapState()
- mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
- if err != nil {
- return fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
- }
- defer syscall.Munmap(mapData)
- 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)
- if keyMap == nil {
- return errors.New("newXKB: xkb_keymap_new_from_buffer failed")
- }
- state := C.xkb_state_new(keyMap)
- if state == nil {
- C.xkb_keymap_unref(keyMap)
- return errors.New("newXKB: xkb_state_new failed")
- }
- x.keyMap = keyMap
- x.state = state
- return nil
- }
- func (x *Context) Modifiers() key.Modifiers {
- var mods key.Modifiers
- if x.state == nil {
- return mods
- }
- 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 {
- mods |= key.ModCtrl
- }
- 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 {
- mods |= key.ModShift
- }
- 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 {
- mods |= key.ModAlt
- }
- 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 {
- mods |= key.ModSuper
- }
- return mods
- }
- func (x *Context) DispatchKey(keyCode uint32, state key.State) (events []event.Event) {
- if x.state == nil {
- return
- }
- kc := C.xkb_keycode_t(keyCode)
- if len(x.utf8Buf) == 0 {
- x.utf8Buf = make([]byte, 1)
- }
- sym := C.xkb_state_key_get_one_sym(x.state, kc)
- if name, ok := convertKeysym(sym); ok {
- cmd := key.Event{
- Name: name,
- Modifiers: x.Modifiers(),
- State: state,
- }
- // Ensure that a physical backtab key is translated to
- // Shift-Tab.
- if sym == C.XKB_KEY_ISO_Left_Tab {
- cmd.Modifiers |= key.ModShift
- }
- events = append(events, cmd)
- }
- C.xkb_compose_state_feed(x.compState, sym)
- var str []byte
- switch C.xkb_compose_state_get_status(x.compState) {
- case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING:
- return
- case C.XKB_COMPOSE_COMPOSED:
- size := C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
- if int(size) >= len(x.utf8Buf) {
- x.utf8Buf = make([]byte, size+1)
- size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
- }
- C.xkb_compose_state_reset(x.compState)
- str = x.utf8Buf[:size]
- case C.XKB_COMPOSE_NOTHING:
- mod := x.Modifiers()
- if mod&(key.ModCtrl|key.ModAlt|key.ModSuper) == 0 {
- str = x.charsForKeycode(kc)
- }
- }
- // Report only printable runes.
- var n int
- for n < len(str) {
- r, s := utf8.DecodeRune(str)
- if unicode.IsPrint(r) {
- n += s
- } else {
- copy(str[n:], str[n+s:])
- str = str[:len(str)-s]
- }
- }
- if state == key.Press && len(str) > 0 {
- events = append(events, key.EditEvent{Text: string(str)})
- }
- return
- }
- func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
- size := C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
- if int(size) >= len(x.utf8Buf) {
- x.utf8Buf = make([]byte, size+1)
- size = C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
- }
- return x.utf8Buf[:size]
- }
- func (x *Context) IsRepeatKey(keyCode uint32) bool {
- if x.state == nil {
- return false
- }
- kc := C.xkb_keycode_t(keyCode)
- return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
- }
- func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) {
- if x.state == nil {
- return
- }
- 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),
- C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
- }
- func convertKeysym(s C.xkb_keysym_t) (key.Name, bool) {
- if 'a' <= s && s <= 'z' {
- return key.Name(rune(s - 'a' + 'A')), true
- }
- if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
- return key.Name(rune(s - C.XKB_KEY_KP_0 + '0')), true
- }
- if ' ' < s && s <= '~' {
- return key.Name(rune(s)), true
- }
- var n key.Name
- switch s {
- case C.XKB_KEY_Escape:
- n = key.NameEscape
- case C.XKB_KEY_Left:
- n = key.NameLeftArrow
- case C.XKB_KEY_Right:
- n = key.NameRightArrow
- case C.XKB_KEY_Return:
- n = key.NameReturn
- case C.XKB_KEY_Up:
- n = key.NameUpArrow
- case C.XKB_KEY_Down:
- n = key.NameDownArrow
- case C.XKB_KEY_Home:
- n = key.NameHome
- case C.XKB_KEY_End:
- n = key.NameEnd
- case C.XKB_KEY_BackSpace:
- n = key.NameDeleteBackward
- case C.XKB_KEY_Delete:
- n = key.NameDeleteForward
- case C.XKB_KEY_Page_Up:
- n = key.NamePageUp
- case C.XKB_KEY_Page_Down:
- n = key.NamePageDown
- case C.XKB_KEY_F1:
- n = key.NameF1
- case C.XKB_KEY_F2:
- n = key.NameF2
- case C.XKB_KEY_F3:
- n = key.NameF3
- case C.XKB_KEY_F4:
- n = key.NameF4
- case C.XKB_KEY_F5:
- n = key.NameF5
- case C.XKB_KEY_F6:
- n = key.NameF6
- case C.XKB_KEY_F7:
- n = key.NameF7
- case C.XKB_KEY_F8:
- n = key.NameF8
- case C.XKB_KEY_F9:
- n = key.NameF9
- case C.XKB_KEY_F10:
- n = key.NameF10
- case C.XKB_KEY_F11:
- n = key.NameF11
- case C.XKB_KEY_F12:
- n = key.NameF12
- case C.XKB_KEY_Tab, C.XKB_KEY_ISO_Left_Tab:
- n = key.NameTab
- case 0x20:
- n = key.NameSpace
- case C.XKB_KEY_Control_L, C.XKB_KEY_Control_R:
- n = key.NameCtrl
- case C.XKB_KEY_Shift_L, C.XKB_KEY_Shift_R:
- n = key.NameShift
- case C.XKB_KEY_Alt_L, C.XKB_KEY_Alt_R:
- n = key.NameAlt
- case C.XKB_KEY_Super_L, C.XKB_KEY_Super_R:
- n = key.NameSuper
- case C.XKB_KEY_KP_Space:
- n = key.NameSpace
- case C.XKB_KEY_KP_Tab:
- n = key.NameTab
- case C.XKB_KEY_KP_Enter:
- n = key.NameEnter
- case C.XKB_KEY_KP_F1:
- n = key.NameF1
- case C.XKB_KEY_KP_F2:
- n = key.NameF2
- case C.XKB_KEY_KP_F3:
- n = key.NameF3
- case C.XKB_KEY_KP_F4:
- n = key.NameF4
- case C.XKB_KEY_KP_Home:
- n = key.NameHome
- case C.XKB_KEY_KP_Left:
- n = key.NameLeftArrow
- case C.XKB_KEY_KP_Up:
- n = key.NameUpArrow
- case C.XKB_KEY_KP_Right:
- n = key.NameRightArrow
- case C.XKB_KEY_KP_Down:
- n = key.NameDownArrow
- case C.XKB_KEY_KP_Prior:
- // not supported
- return "", false
- case C.XKB_KEY_KP_Next:
- // not supported
- return "", false
- case C.XKB_KEY_KP_End:
- n = key.NameEnd
- case C.XKB_KEY_KP_Begin:
- n = key.NameHome
- case C.XKB_KEY_KP_Insert:
- // not supported
- return "", false
- case C.XKB_KEY_KP_Delete:
- n = key.NameDeleteForward
- case C.XKB_KEY_KP_Multiply:
- n = "*"
- case C.XKB_KEY_KP_Add:
- n = "+"
- case C.XKB_KEY_KP_Separator:
- // not supported
- return "", false
- case C.XKB_KEY_KP_Subtract:
- n = "-"
- case C.XKB_KEY_KP_Decimal:
- // TODO(dh): does a German keyboard layout also translate the numpad key to XKB_KEY_KP_DECIMAL? Because in
- // German, the decimal is a comma, not a period.
- n = "."
- case C.XKB_KEY_KP_Divide:
- n = "/"
- case C.XKB_KEY_KP_Equal:
- n = "="
- default:
- return "", false
- }
- return n, true
- }
|