Files
gio/app/internal/xkb/xkb_unix.go
T
Jeff Williams 13183522dd app: send keypress events for modifier keys
This change causes modifier keys (Control, Shift, Alt, Super) to be sent
to the application as key.Event events. These will still continue to be
used as modifiers for other key and pointer events as they are today.

This commit also adds a minor cleanup to use constants for function
keys in the OS-specific keypress handling functions.

Signed-off-by: Jeff Williams <kanobe@gmail.com>
2022-01-15 16:56:47 +01:00

310 lines
8.2 KiB
Go

// 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 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 {
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) (string, bool) {
if 'a' <= s && s <= 'z' {
return string(rune(s - 'a' + 'A')), true
}
if ' ' < s && s <= '~' {
return string(rune(s)), true
}
var n string
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_KP_Enter:
n = key.NameEnter
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_KP_Tab, C.XKB_KEY_ISO_Left_Tab:
n = key.NameTab
case 0x20, C.XKB_KEY_KP_Space:
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
default:
return "", false
}
return n, true
}