github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/app/internal/xkb/xkb_unix.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  // +build linux,!android freebsd openbsd
     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/cybriq/giocore/io/event"
    18  	"github.com/cybriq/giocore/io/key"
    19  )
    20  
    21  /*
    22  #cgo linux pkg-config: xkbcommon
    23  #cgo freebsd openbsd CFLAGS: -I/usr/local/include
    24  #cgo freebsd openbsd LDFLAGS: -L/usr/local/lib -lxkbcommon
    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) Modifiers() key.Modifiers {
   137  	var mods key.Modifiers
   138  	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 {
   139  		mods |= key.ModCtrl
   140  	}
   141  	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 {
   142  		mods |= key.ModShift
   143  	}
   144  	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 {
   145  		mods |= key.ModAlt
   146  	}
   147  	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 {
   148  		mods |= key.ModSuper
   149  	}
   150  	return mods
   151  }
   152  
   153  func (x *Context) DispatchKey(keyCode uint32, state key.State) (events []event.Event) {
   154  	if x.state == nil {
   155  		return
   156  	}
   157  	kc := C.xkb_keycode_t(keyCode)
   158  	if len(x.utf8Buf) == 0 {
   159  		x.utf8Buf = make([]byte, 1)
   160  	}
   161  	sym := C.xkb_state_key_get_one_sym(x.state, kc)
   162  	if name, ok := convertKeysym(sym); ok {
   163  		cmd := key.Event{
   164  			Name:      name,
   165  			Modifiers: x.Modifiers(),
   166  			State:     state,
   167  		}
   168  		// Ensure that a physical backtab key is translated to
   169  		// Shift-Tab.
   170  		if sym == C.XKB_KEY_ISO_Left_Tab {
   171  			cmd.Modifiers |= key.ModShift
   172  		}
   173  		events = append(events, cmd)
   174  	}
   175  	C.xkb_compose_state_feed(x.compState, sym)
   176  	var str []byte
   177  	switch C.xkb_compose_state_get_status(x.compState) {
   178  	case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING:
   179  		return
   180  	case C.XKB_COMPOSE_COMPOSED:
   181  		size := C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
   182  		if int(size) >= len(x.utf8Buf) {
   183  			x.utf8Buf = make([]byte, size+1)
   184  			size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
   185  		}
   186  		C.xkb_compose_state_reset(x.compState)
   187  		str = x.utf8Buf[:size]
   188  	case C.XKB_COMPOSE_NOTHING:
   189  		mod := x.Modifiers()
   190  		if mod&(key.ModCtrl|key.ModAlt|key.ModSuper) == 0 {
   191  			str = x.charsForKeycode(kc)
   192  		}
   193  	}
   194  	// Report only printable runes.
   195  	var n int
   196  	for n < len(str) {
   197  		r, s := utf8.DecodeRune(str)
   198  		if unicode.IsPrint(r) {
   199  			n += s
   200  		} else {
   201  			copy(str[n:], str[n+s:])
   202  			str = str[:len(str)-s]
   203  		}
   204  	}
   205  	if state == key.Press && len(str) > 0 {
   206  		events = append(events, key.EditEvent{Text: string(str)})
   207  	}
   208  	return
   209  }
   210  
   211  func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
   212  	size := C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
   213  	if int(size) >= len(x.utf8Buf) {
   214  		x.utf8Buf = make([]byte, size+1)
   215  		size = C.xkb_state_key_get_utf8(x.state, keyCode, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
   216  	}
   217  	return x.utf8Buf[:size]
   218  }
   219  
   220  func (x *Context) IsRepeatKey(keyCode uint32) bool {
   221  	kc := C.xkb_keycode_t(keyCode)
   222  	return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
   223  }
   224  
   225  func (x *Context) UpdateMask(depressed, latched, locked, depressedGroup, latchedGroup, lockedGroup uint32) {
   226  	if x.state == nil {
   227  		return
   228  	}
   229  	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),
   230  		C.xkb_layout_index_t(depressedGroup), C.xkb_layout_index_t(latchedGroup), C.xkb_layout_index_t(lockedGroup))
   231  }
   232  
   233  func convertKeysym(s C.xkb_keysym_t) (string, bool) {
   234  	if 'a' <= s && s <= 'z' {
   235  		return string(rune(s - 'a' + 'A')), true
   236  	}
   237  	if ' ' < s && s <= '~' {
   238  		return string(rune(s)), true
   239  	}
   240  	var n string
   241  	switch s {
   242  	case C.XKB_KEY_Escape:
   243  		n = key.NameEscape
   244  	case C.XKB_KEY_Left:
   245  		n = key.NameLeftArrow
   246  	case C.XKB_KEY_Right:
   247  		n = key.NameRightArrow
   248  	case C.XKB_KEY_Return:
   249  		n = key.NameReturn
   250  	case C.XKB_KEY_KP_Enter:
   251  		n = key.NameEnter
   252  	case C.XKB_KEY_Up:
   253  		n = key.NameUpArrow
   254  	case C.XKB_KEY_Down:
   255  		n = key.NameDownArrow
   256  	case C.XKB_KEY_Home:
   257  		n = key.NameHome
   258  	case C.XKB_KEY_End:
   259  		n = key.NameEnd
   260  	case C.XKB_KEY_BackSpace:
   261  		n = key.NameDeleteBackward
   262  	case C.XKB_KEY_Delete:
   263  		n = key.NameDeleteForward
   264  	case C.XKB_KEY_Page_Up:
   265  		n = key.NamePageUp
   266  	case C.XKB_KEY_Page_Down:
   267  		n = key.NamePageDown
   268  	case C.XKB_KEY_F1:
   269  		n = "F1"
   270  	case C.XKB_KEY_F2:
   271  		n = "F2"
   272  	case C.XKB_KEY_F3:
   273  		n = "F3"
   274  	case C.XKB_KEY_F4:
   275  		n = "F4"
   276  	case C.XKB_KEY_F5:
   277  		n = "F5"
   278  	case C.XKB_KEY_F6:
   279  		n = "F6"
   280  	case C.XKB_KEY_F7:
   281  		n = "F7"
   282  	case C.XKB_KEY_F8:
   283  		n = "F8"
   284  	case C.XKB_KEY_F9:
   285  		n = "F9"
   286  	case C.XKB_KEY_F10:
   287  		n = "F10"
   288  	case C.XKB_KEY_F11:
   289  		n = "F11"
   290  	case C.XKB_KEY_F12:
   291  		n = "F12"
   292  	case C.XKB_KEY_Tab, C.XKB_KEY_KP_Tab, C.XKB_KEY_ISO_Left_Tab:
   293  		n = key.NameTab
   294  	case 0x20, C.XKB_KEY_KP_Space:
   295  		n = key.NameSpace
   296  	default:
   297  		return "", false
   298  	}
   299  	return n, true
   300  }