You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
NoiseTorch/vendor/gioui.org/app/internal/xkb/xkb_unix.go

297 lines
7.6 KiB
Go

// SPDX-License-Identifier: Unlicense OR MIT
// +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) (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(),
}
// 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:
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 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 = "F1"
case C.XKB_KEY_F2:
n = "F2"
case C.XKB_KEY_F3:
n = "F3"
case C.XKB_KEY_F4:
n = "F4"
case C.XKB_KEY_F5:
n = "F5"
case C.XKB_KEY_F6:
n = "F6"
case C.XKB_KEY_F7:
n = "F7"
case C.XKB_KEY_F8:
n = "F8"
case C.XKB_KEY_F9:
n = "F9"
case C.XKB_KEY_F10:
n = "F10"
case C.XKB_KEY_F11:
n = "F11"
case C.XKB_KEY_F12:
n = "F12"
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 = "Space"
default:
return "", false
}
return n, true
}