app/internal/window: replace X11 input handling with xkb

Unifies Wayland and X11 keyboard handling.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-11-22 18:27:29 +01:00
parent 5a34b6f2c3
commit d5424ef7fc
3 changed files with 156 additions and 240 deletions
+16 -9
View File
@@ -628,18 +628,14 @@ func (w *window) resetFling() {
func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, format C.uint32_t, fd C.int32_t, size C.uint32_t) {
defer syscall.Close(int(fd))
conn.repeat.Stop(0)
if conn.xkb != nil {
conn.xkb.Destroy()
}
conn.xkb.DestroyKeymapState()
if format != C.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 {
return
}
xkb, err := xkb.New(int(format), int(fd), int(size))
if err != nil {
if err := conn.xkb.LoadKeymap(int(format), int(fd), int(size)); err != nil {
// TODO: Do better.
panic(err)
}
conn.xkb = xkb
}
//export gio_onKeyboardEnter
@@ -663,10 +659,10 @@ func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, seri
w := winMap[keyboard]
w.resetFling()
conn.repeat.Stop(t)
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED || conn.xkb == nil {
if state != C.WL_KEYBOARD_KEY_STATE_PRESSED {
return
}
kc := uint32(keyCode)
kc := mapXKBKeycode(uint32(keyCode))
for _, e := range conn.xkb.DispatchKey(kc) {
w.w.Event(e)
}
@@ -675,6 +671,11 @@ func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, seri
}
}
func mapXKBKeycode(keyCode uint32) uint32 {
// According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode."
return keyCode + 8
}
func (r *repeatState) Start(w *window, keyCode uint32, t time.Duration) {
if r.rate <= 0 {
return
@@ -867,7 +868,7 @@ func gio_onKeyboardModifiers(data unsafe.Pointer, keyboard *C.struct_wl_keyboard
if conn.xkb == nil {
return
}
conn.xkb.UpdateMask(uint32(depressed), uint32(latched), uint32(locked), uint32(group))
conn.xkb.UpdateMask(uint32(depressed), uint32(latched), uint32(locked), uint32(group), uint32(group), uint32(group))
}
//export gio_onKeyboardRepeatInfo
@@ -1082,6 +1083,12 @@ func detectUIScale() float32 {
func waylandConnect() error {
c := new(wlConn)
conn = c
xkb, err := xkb.New()
if err != nil {
c.destroy()
return fmt.Errorf("wayland: %v", err)
}
c.xkb = xkb
c.disp = C.wl_display_connect(nil)
if c.disp == nil {
c.destroy()
+79 -188
View File
@@ -5,38 +5,17 @@
package window
/*
#cgo LDFLAGS: -lX11
#cgo LDFLAGS: -lX11 -lxkbcommon -lxkbcommon-x11 -lX11-xcb
#include <stdlib.h>
#include <locale.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/XKBlib.h>
#include <X11/Xlib-xcb.h>
#include <xkbcommon/xkbcommon-x11.h>
void gio_x11_init_ime(Display *dpy, Window win, XIM *xim, XIC *xic) {
// adjust locale temporarily for XOpenIM
char *lc = setlocale(LC_CTYPE, NULL);
setlocale(LC_CTYPE, "");
XSetLocaleModifiers("");
*xim = XOpenIM(dpy, 0, 0, 0);
if (!*xim) {
// fallback to internal input method
XSetLocaleModifiers("@im=none");
*xim = XOpenIM(dpy, 0, 0, 0);
}
// revert locale to prevent any unexpected side effects
setlocale(LC_CTYPE, lc);
*xic = XCreateIC(*xim,
XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win,
XNFocusWindow, win,
NULL);
XSetICFocus(*xic);
}
*/
import "C"
import (
@@ -46,29 +25,29 @@ import (
"strconv"
"sync"
"time"
"unicode"
"unicode/utf8"
"unsafe"
"gioui.org/f32"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/app/internal/xkb"
syscall "golang.org/x/sys/unix"
)
type x11Window struct {
w Callbacks
x *C.Display
xw C.Window
w Callbacks
x *C.Display
xkb *xkb.Context
xkbEventBase C.int
xw C.Window
evDelWindow C.Atom
stage system.Stage
cfg config
width int
height int
xim C.XIM
xic C.XIC
notify struct {
read, write int
}
@@ -192,8 +171,10 @@ func (w *x11Window) destroy() {
syscall.Close(w.notify.read)
w.notify.read = 0
}
C.XDestroyIC(w.xic)
C.XCloseIM(w.xim)
if w.xkb != nil {
w.xkb.Destroy()
w.xkb = nil
}
C.XDestroyWindow(w.x, w.xw)
C.XCloseDisplay(w.x)
}
@@ -235,60 +216,22 @@ func (h *x11EventHandler) handleEvents() bool {
continue
}
switch _type := (*C.XAnyEvent)(unsafe.Pointer(xev))._type; _type {
case h.w.xkbEventBase:
xkbEvent := (*C.XkbAnyEvent)(unsafe.Pointer(xev))
switch xkbEvent.xkb_type {
case C.XkbNewKeyboardNotify, C.XkbMapNotify:
if err := h.w.updateXkbKeymap(); err != nil {
panic(err)
}
case C.XkbStateNotify:
state := (*C.XkbStateNotifyEvent)(unsafe.Pointer(xev))
h.w.xkb.UpdateMask(uint32(state.base_mods), uint32(state.latched_mods), uint32(state.locked_mods),
uint32(state.base_group), uint32(state.latched_group), uint32(state.locked_group))
}
case C.KeyPress:
kevt := (*C.XKeyPressedEvent)(unsafe.Pointer(xev))
lookup:
// Save state then clear CTRL & Shift bits in order to have
// Xutf8LookupString return the unmodified key name in text[:l].
// This addresses an issue on some non US keyboard layouts where
// CTRL-[0..9] do not behave consistently.
//
// Note that this enables sending a key.Event for key combinations
// like CTRL-SHIFT-/ on QWERTY layouts, but CTRL-? is completely
// masked. The same applies to AZERTY layouts where CTRL-SHIFT-É is
// available but not CTRL-2.
state := kevt.state
mods := x11KeyStateToModifiers(state)
if mods.Contain(key.ModCtrl) {
kevt.state &^= (C.uint(C.ControlMask) | C.uint(C.ShiftMask))
}
l := int(C.Xutf8LookupString(w.xic, kevt,
(*C.char)(unsafe.Pointer(&h.text[0])), C.int(len(h.text)),
&h.keysym, &h.status))
switch h.status {
case C.XBufferOverflow:
h.text = make([]byte, l)
goto lookup
case C.XLookupChars:
// Synthetic event from XIM.
w.w.Event(key.EditEvent{Text: string(h.text[:l])})
case C.XLookupKeySym:
// Special keys.
if n, m, ok := x11ConvertKeysym(h.keysym, mods); ok {
w.w.Event(key.Event{Name: n, Modifiers: m})
}
case C.XLookupBoth:
if n, m, ok := x11ConvertKeysym(h.keysym, mods); ok {
w.w.Event(key.Event{Name: n, Modifiers: m})
}
// Do not send EditEvent for CTRL key combinations.
if mods.Contain(key.ModCtrl) {
break
}
// Report only printable runes.
str := h.text[:l]
for n := 0; 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 {
w.w.Event(key.EditEvent{Text: string(str)})
}
for _, e := range h.w.xkb.DispatchKey(uint32(kevt.keycode)) {
w.w.Event(e)
}
case C.KeyRelease:
case C.ButtonPress, C.ButtonRelease:
@@ -369,98 +312,6 @@ func (h *x11EventHandler) handleEvents() bool {
return redraw
}
func x11KeyStateToModifiers(s C.uint) key.Modifiers {
var m key.Modifiers
if s&C.Mod1Mask != 0 {
m |= key.ModAlt
}
if s&C.Mod4Mask != 0 {
m |= key.ModSuper
}
if s&C.ControlMask != 0 {
m |= key.ModCtrl
}
if s&C.ShiftMask != 0 {
m |= key.ModShift
}
return m
}
// x11ConvertKeysym returns the Gio special key that matches keysym s. For
// portability reasons, some keysyms might be translated and modifiers changed
// (like BackTab -> ModShift+Tab)
func x11ConvertKeysym(s C.KeySym, mods key.Modifiers) (string, key.Modifiers, bool) {
if '0' <= s && s <= '9' || 'A' <= s && s <= 'Z' {
return string(s), mods, true
}
if 'a' <= s && s <= 'z' {
return string(s - 0x20), mods, true
}
var n string
switch s {
case C.XK_Escape:
n = key.NameEscape
case C.XK_Left, C.XK_KP_Left:
n = key.NameLeftArrow
case C.XK_Right, C.XK_KP_Right:
n = key.NameRightArrow
case C.XK_Return:
n = key.NameReturn
case C.XK_KP_Enter:
n = key.NameEnter
case C.XK_Up, C.XK_KP_Up:
n = key.NameUpArrow
case C.XK_Down, C.XK_KP_Down:
n = key.NameDownArrow
case C.XK_Home, C.XK_KP_Home:
n = key.NameHome
case C.XK_End, C.XK_KP_End:
n = key.NameEnd
case C.XK_BackSpace:
n = key.NameDeleteBackward
case C.XK_Delete, C.XK_KP_Delete:
n = key.NameDeleteForward
case C.XK_Page_Up, C.XK_KP_Prior:
n = key.NamePageUp
case C.XK_Page_Down, C.XK_KP_Next:
n = key.NamePageDown
case C.XK_F1:
n = "F1"
case C.XK_F2:
n = "F2"
case C.XK_F3:
n = "F3"
case C.XK_F4:
n = "F4"
case C.XK_F5:
n = "F5"
case C.XK_F6:
n = "F6"
case C.XK_F7:
n = "F7"
case C.XK_F8:
n = "F8"
case C.XK_F9:
n = "F9"
case C.XK_F10:
n = "F10"
case C.XK_F11:
n = "F11"
case C.XK_F12:
n = "F12"
case C.XK_ISO_Left_Tab:
mods |= key.ModShift
fallthrough
case C.XK_Tab:
n = key.NameTab
case 0x20, C.XK_KP_Space:
n = "Space"
default:
return "", mods, false
}
return n, mods, true
}
var (
x11Threads sync.Once
)
@@ -490,6 +341,22 @@ func newX11Window(gioWin Callbacks, opts *Options) error {
if dpy == nil {
return errors.New("x11: cannot connect to the X server")
}
var major, minor C.int = C.XkbMajorVersion, C.XkbMinorVersion
var xkbEventBase C.int
if C.XkbQueryExtension(dpy, nil, &xkbEventBase, nil, &major, &minor) != C.True {
C.XCloseDisplay(dpy)
return errors.New("x11: XkbQueryExtension failed")
}
const bits = C.uint(C.XkbNewKeyboardNotifyMask | C.XkbMapNotifyMask | C.XkbStateNotifyMask)
if C.XkbSelectEvents(dpy, C.XkbUseCoreKbd, bits, bits) != C.True {
C.XCloseDisplay(dpy)
return errors.New("x11: XkbSelectEvents failed")
}
xkb, err := xkb.New()
if err != nil {
C.XCloseDisplay(dpy)
return fmt.Errorf("x11: %v", err)
}
ppsp := x11DetectUIScale(dpy)
cfg := config{pxPerDp: ppsp, pxPerSp: ppsp}
@@ -506,23 +373,23 @@ func newX11Window(gioWin Callbacks, opts *Options) error {
0, 0, C.uint(cfg.Px(opts.Width)), C.uint(cfg.Px(opts.Height)),
0, C.CopyFromParent, C.InputOutput, nil,
C.CWEventMask|C.CWBackPixmap|C.CWOverrideRedirect, &swa)
var (
xim C.XIM
xic C.XIC
)
C.gio_x11_init_ime(dpy, win, &xim, &xic)
w := &x11Window{
w: gioWin, x: dpy, xw: win,
width: cfg.Px(opts.Width),
height: cfg.Px(opts.Height),
cfg: cfg,
xim: xim,
xic: xic,
width: cfg.Px(opts.Width),
height: cfg.Px(opts.Height),
cfg: cfg,
xkb: xkb,
xkbEventBase: xkbEventBase,
}
w.notify.read = pipe[0]
w.notify.write = pipe[1]
if err := w.updateXkbKeymap(); err != nil {
w.destroy()
return err
}
var hints C.XWMHints
hints.input = C.True
hints.flags = C.InputHint
@@ -591,3 +458,27 @@ func x11DetectUIScale(dpy *C.Display) float32 {
return scale
}
func (w *x11Window) updateXkbKeymap() error {
w.xkb.DestroyKeymapState()
ctx := (*C.struct_xkb_context)(unsafe.Pointer(w.xkb.Ctx))
xcb := C.XGetXCBConnection(w.x)
if xcb == nil {
return errors.New("x11: XGetXCBConnection failed")
}
xkbDevID := C.xkb_x11_get_core_keyboard_device_id(xcb)
if xkbDevID == -1 {
return errors.New("x11: xkb_x11_get_core_keyboard_device_id failed")
}
keymap := C.xkb_x11_keymap_new_from_device(ctx, xcb, xkbDevID, C.XKB_KEYMAP_COMPILE_NO_FLAGS)
if keymap == nil {
return errors.New("x11: xkb_x11_keymap_new_from_device failed")
}
state := C.xkb_x11_state_new_from_device(keymap, xcb, xkbDevID)
if state == nil {
C.xkb_keymap_unref(keymap)
return errors.New("x11: xkb_x11_keymap_new_from_device failed")
}
w.xkb.SetKeymap(unsafe.Pointer(keymap), unsafe.Pointer(state))
return nil
}
+61 -43
View File
@@ -9,8 +9,8 @@ import (
"errors"
"fmt"
"os"
"unicode"
"syscall"
"unicode"
"unicode/utf8"
"unsafe"
@@ -30,7 +30,7 @@ import (
import "C"
type Context struct {
ctx *C.struct_xkb_context
Ctx *C.struct_xkb_context
keyMap *C.struct_xkb_keymap
state *C.struct_xkb_state
compTable *C.struct_xkb_compose_table
@@ -54,25 +54,18 @@ func (x *Context) Destroy() {
C.xkb_compose_table_unref(x.compTable)
x.compTable = nil
}
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
}
if x.ctx != nil {
C.xkb_context_unref(x.ctx)
x.ctx = nil
x.DestroyKeymapState()
if x.Ctx != nil {
C.xkb_context_unref(x.Ctx)
x.Ctx = nil
}
}
func New(format int, fd int, size int) (*Context, error) {
func New() (*Context, error) {
ctx := &Context{
ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
Ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS),
}
if ctx.ctx == nil {
if ctx.Ctx == nil {
return nil, errors.New("newXKB: xkb_context_new failed")
}
locale := os.Getenv("LC_ALL")
@@ -87,7 +80,7 @@ func New(format int, fd int, size int) (*Context, error) {
}
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)
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")
@@ -97,27 +90,54 @@ func New(format int, fd int, size int) (*Context, error) {
ctx.Destroy()
return nil, errors.New("newXKB: xkb_compose_state_new failed")
}
mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
ctx.Destroy()
return nil, fmt.Errorf("newXKB: mmap of keymap failed: %v", err)
}
defer syscall.Munmap(mapData)
ctx.keyMap = C.xkb_keymap_new_from_buffer(ctx.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 ctx.keyMap == nil {
ctx.Destroy()
return nil, errors.New("newXKB: xkb_keymap_new_from_buffer failed")
}
ctx.state = C.xkb_state_new(ctx.keyMap)
if ctx.state == nil {
ctx.Destroy()
return nil, errors.New("newXKB: xkb_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) DispatchKey(keyCode uint32) (events []event.Event) {
kc := mapXKBKeyCode(keyCode)
if x.state == nil {
return
}
kc := C.xkb_keycode_t(keyCode)
if len(x.utf8Buf) == 0 {
x.utf8Buf = make([]byte, 1)
}
@@ -186,18 +206,16 @@ func (x *Context) charsForKeycode(keyCode C.xkb_keycode_t) []byte {
}
func (x *Context) IsRepeatKey(keyCode uint32) bool {
kc := mapXKBKeyCode(keyCode)
kc := C.xkb_keycode_t(keyCode)
return C.xkb_keymap_key_repeats(x.keyMap, kc) == 1
}
func (x *Context) UpdateMask(depressed, latched, locked, group uint32) {
xkbGrp := C.xkb_layout_index_t(group)
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), xkbGrp, xkbGrp, xkbGrp)
}
func mapXKBKeyCode(keyCode uint32) C.xkb_keycode_t {
// According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode."
return C.xkb_keycode_t(keyCode + 8)
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) {