gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/xkb_linux.go (about)

     1  // SPDX-License-Identifier: Unlicense OR MIT
     2  
     3  // +build !android
     4  
     5  package app
     6  
     7  /*
     8  #cgo LDFLAGS: -lxkbcommon
     9  
    10  #include <stdlib.h>
    11  #include <xkbcommon/xkbcommon.h>
    12  #include <xkbcommon/xkbcommon-compose.h>
    13  */
    14  import "C"
    15  
    16  import (
    17  	"errors"
    18  	"fmt"
    19  	"os"
    20  	"syscall"
    21  	"unicode"
    22  	"unicode/utf8"
    23  	"unsafe"
    24  
    25  	"gioui.org/ui/key"
    26  )
    27  
    28  type xkb struct {
    29  	ctx       *C.struct_xkb_context
    30  	keyMap    *C.struct_xkb_keymap
    31  	state     *C.struct_xkb_state
    32  	compTable *C.struct_xkb_compose_table
    33  	compState *C.struct_xkb_compose_state
    34  	utf8Buf   []byte
    35  }
    36  
    37  var (
    38  	_XKB_MOD_NAME_CTRL  = []byte("Control\x00")
    39  	_XKB_MOD_NAME_SHIFT = []byte("Shift\x00")
    40  )
    41  
    42  func (x *xkb) Destroy() {
    43  	if x.state != nil {
    44  		C.xkb_compose_state_unref(x.compState)
    45  		x.compState = nil
    46  	}
    47  	if x.compTable != nil {
    48  		C.xkb_compose_table_unref(x.compTable)
    49  		x.compTable = nil
    50  	}
    51  	if x.state != nil {
    52  		C.xkb_state_unref(x.state)
    53  		x.state = nil
    54  	}
    55  	if x.keyMap != nil {
    56  		C.xkb_keymap_unref(x.keyMap)
    57  		x.keyMap = nil
    58  	}
    59  	if x.ctx != nil {
    60  		C.xkb_context_unref(x.ctx)
    61  		x.ctx = nil
    62  	}
    63  }
    64  
    65  func newXKB(format C.uint32_t, fd C.int32_t, size C.uint32_t) (*xkb, error) {
    66  	xkb := &xkb{
    67  		ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
    68  	}
    69  	if xkb.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  	xkb.compTable = C.xkb_compose_table_new_from_locale(xkb.ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS)
    85  	if xkb.compTable == nil {
    86  		xkb.Destroy()
    87  		return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed")
    88  	}
    89  	xkb.compState = C.xkb_compose_state_new(xkb.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS)
    90  	if xkb.compState == nil {
    91  		xkb.Destroy()
    92  		return nil, errors.New("newXKB: xkb_compose_state_new failed")
    93  	}
    94  	mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
    95  	if err != nil {
    96  		xkb.Destroy()
    97  		return nil, fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
    98  	}
    99  	defer syscall.Munmap(mapData)
   100  	xkb.keyMap = C.xkb_keymap_new_from_buffer(xkb.ctx, (*C.char)(unsafe.Pointer(&mapData[0])), C.size_t(size-1), C.XKB_KEYMAP_FORMAT_TEXT_V1, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
   101  	if xkb.keyMap == nil {
   102  		xkb.Destroy()
   103  		return nil, errors.New("newXKB: xkb_keymap_new_from_buffer failed")
   104  	}
   105  	xkb.state = C.xkb_state_new(xkb.keyMap)
   106  	if xkb.state == nil {
   107  		xkb.Destroy()
   108  		return nil, errors.New("newXKB: xkb_state_new failed")
   109  	}
   110  	return xkb, nil
   111  }
   112  
   113  func (x *xkb) dispatchKey(w *Window, keyCode C.uint32_t) {
   114  	keyCode = mapXKBKeyCode(keyCode)
   115  	if len(x.utf8Buf) == 0 {
   116  		x.utf8Buf = make([]byte, 1)
   117  	}
   118  	sym := C.xkb_state_key_get_one_sym(x.state, C.xkb_keycode_t(keyCode))
   119  	if n, ok := convertKeysym(sym); ok {
   120  		cmd := key.Event{Name: n}
   121  		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 {
   122  			cmd.Modifiers |= key.ModCommand
   123  		}
   124  		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 {
   125  			cmd.Modifiers |= key.ModShift
   126  		}
   127  		w.event(cmd)
   128  	}
   129  	C.xkb_compose_state_feed(x.compState, sym)
   130  	var size C.int
   131  	switch C.xkb_compose_state_get_status(x.compState) {
   132  	case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING:
   133  		return
   134  	case C.XKB_COMPOSE_COMPOSED:
   135  		size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
   136  		if int(size) >= len(x.utf8Buf) {
   137  			x.utf8Buf = make([]byte, size+1)
   138  			size = C.xkb_compose_state_get_utf8(x.compState, (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
   139  		}
   140  		C.xkb_compose_state_reset(x.compState)
   141  	case C.XKB_COMPOSE_NOTHING:
   142  		size = C.xkb_state_key_get_utf8(x.state, C.xkb_keycode_t(keyCode), (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
   143  		if int(size) >= len(x.utf8Buf) {
   144  			x.utf8Buf = make([]byte, size+1)
   145  			size = C.xkb_state_key_get_utf8(x.state, C.xkb_keycode_t(keyCode), (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf)))
   146  		}
   147  	}
   148  	// Report only printable runes.
   149  	str := x.utf8Buf[:size]
   150  	var n int
   151  	for n < len(str) {
   152  		r, s := utf8.DecodeRune(str)
   153  		if unicode.IsPrint(r) {
   154  			n += s
   155  		} else {
   156  			copy(str[n:], str[n+s:])
   157  			str = str[:len(str)-s]
   158  		}
   159  	}
   160  	if len(str) > 0 {
   161  		w.event(key.EditEvent{Text: string(str)})
   162  	}
   163  }
   164  
   165  func (x *xkb) isRepeatKey(keyCode C.uint32_t) bool {
   166  	keyCode = mapXKBKeyCode(keyCode)
   167  	return C.xkb_keymap_key_repeats(conn.xkb.keyMap, C.xkb_keycode_t(keyCode)) == 1
   168  }
   169  
   170  func (x *xkb) updateMask(depressed, latched, locked, group C.uint32_t) {
   171  	xkbGrp := C.xkb_layout_index_t(group)
   172  	C.xkb_state_update_mask(conn.xkb.state, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked), xkbGrp, xkbGrp, xkbGrp)
   173  }
   174  
   175  func mapXKBKeyCode(keyCode C.uint32_t) C.uint32_t {
   176  	// According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode."
   177  	return keyCode + 8
   178  }
   179  
   180  func convertKeysym(s C.xkb_keysym_t) (rune, bool) {
   181  	if '0' <= s && s <= '9' || 'A' <= s && s <= 'Z' {
   182  		return rune(s), true
   183  	}
   184  	if 'a' <= s && s <= 'z' {
   185  		return rune(s - 0x20), true
   186  	}
   187  	var n rune
   188  	switch s {
   189  	case C.XKB_KEY_Escape:
   190  		n = key.NameEscape
   191  	case C.XKB_KEY_Left:
   192  		n = key.NameLeftArrow
   193  	case C.XKB_KEY_Right:
   194  		n = key.NameRightArrow
   195  	case C.XKB_KEY_Return:
   196  		n = key.NameReturn
   197  	case C.XKB_KEY_KP_Enter:
   198  		n = key.NameEnter
   199  	case C.XKB_KEY_Up:
   200  		n = key.NameUpArrow
   201  	case C.XKB_KEY_Down:
   202  		n = key.NameDownArrow
   203  	case C.XKB_KEY_Home:
   204  		n = key.NameHome
   205  	case C.XKB_KEY_End:
   206  		n = key.NameEnd
   207  	case C.XKB_KEY_BackSpace:
   208  		n = key.NameDeleteBackward
   209  	case C.XKB_KEY_Delete:
   210  		n = key.NameDeleteForward
   211  	case C.XKB_KEY_Page_Up:
   212  		n = key.NamePageUp
   213  	case C.XKB_KEY_Page_Down:
   214  		n = key.NamePageDown
   215  	default:
   216  		return 0, false
   217  	}
   218  	return n, true
   219  }