xkb_unix.go raw

   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/p9c/p9/pkg/gel/gio/io/event"
  18  	"github.com/p9c/p9/pkg/gel/gio/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  }
 301