github.com/Seikaijyu/gio@v0.0.1/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  	"github.com/Seikaijyu/gio/io/event"
    19  	"github.com/Seikaijyu/gio/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) (string, bool) {
   242  	if 'a' <= s && s <= 'z' {
   243  		return string(rune(s - 'a' + 'A')), true
   244  	}
   245  	if C.XKB_KEY_KP_0 <= s && s <= C.XKB_KEY_KP_9 {
   246  		return string(rune(s - C.XKB_KEY_KP_0 + '0')), true
   247  	}
   248  	if ' ' < s && s <= '~' {
   249  		return string(rune(s)), true
   250  	}
   251  	var n string
   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  }