From 51cfb4e5c299f5c35994f1c907f62e24eaa95457 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Thu, 26 Sep 2019 13:01:51 +0200 Subject: [PATCH] ui/app: (linux) separate xkb logic from wayland backend Makes the Wayland backend more clean, and X11 might be able to reuse the xkb code. Signed-off-by: Elias Naur --- ui/app/os_wayland.go | 244 ++++++++----------------------------------- ui/app/xkb_linux.go | 219 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 263 insertions(+), 200 deletions(-) create mode 100644 ui/app/xkb_linux.go diff --git a/ui/app/os_wayland.go b/ui/app/os_wayland.go index 55a56c08..bf557851 100644 --- a/ui/app/os_wayland.go +++ b/ui/app/os_wayland.go @@ -10,13 +10,10 @@ import ( "fmt" "image" "math" - "os" "os/exec" "strconv" "sync" "time" - "unicode" - "unicode/utf8" "unsafe" "gioui.org/ui/f32" @@ -41,13 +38,11 @@ import ( //go:generate sed -i "1s;^;// +build linux,!android\\n\\n;" wayland_text_input.c /* -#cgo LDFLAGS: -lwayland-client -lwayland-cursor -lxkbcommon +#cgo LDFLAGS: -lwayland-client -lwayland-cursor #include #include #include -#include -#include #include "wayland_text_input.h" #include "wayland_xdg_shell.h" #include "wayland_xdg_decoration.h" @@ -56,27 +51,22 @@ import ( import "C" type wlConn struct { - disp *C.struct_wl_display - compositor *C.struct_wl_compositor - wm *C.struct_xdg_wm_base - imm *C.struct_zwp_text_input_manager_v3 - im *C.struct_zwp_text_input_v3 - shm *C.struct_wl_shm - cursorTheme *C.struct_wl_cursor_theme - cursor *C.struct_wl_cursor - cursorSurf *C.struct_wl_surface - decor *C.struct_zxdg_decoration_manager_v1 - seat *C.struct_wl_seat - seatName C.uint32_t - pointer *C.struct_wl_pointer - touch *C.struct_wl_touch - keyboard *C.struct_wl_keyboard - xkb *C.struct_xkb_context - xkbMap *C.struct_xkb_keymap - xkbState *C.struct_xkb_state - xkbCompTable *C.struct_xkb_compose_table - xkbCompState *C.struct_xkb_compose_state - utf8Buf []byte + disp *C.struct_wl_display + compositor *C.struct_wl_compositor + wm *C.struct_xdg_wm_base + imm *C.struct_zwp_text_input_manager_v3 + im *C.struct_zwp_text_input_v3 + shm *C.struct_wl_shm + cursorTheme *C.struct_wl_cursor_theme + cursor *C.struct_wl_cursor + cursorSurf *C.struct_wl_surface + decor *C.struct_zxdg_decoration_manager_v1 + seat *C.struct_wl_seat + seatName C.uint32_t + pointer *C.struct_wl_pointer + touch *C.struct_wl_touch + keyboard *C.struct_wl_keyboard + xkb *xkb repeat repeatState } @@ -86,7 +76,7 @@ type repeatState struct { delay time.Duration key C.uint32_t - win *window + win *Window stopC chan struct{} start time.Duration @@ -121,6 +111,7 @@ type window struct { stage Stage dead bool + pendingErr error lastFrameCallback *C.struct_wl_callback mu sync.Mutex @@ -154,11 +145,6 @@ var ( outputConfig = make(map[*C.struct_wl_output]*wlOutput) ) -var ( - _XKB_MOD_NAME_CTRL = []byte("Control\x00") - _XKB_MOD_NAME_SHIFT = []byte("Shift\x00") -) - func main() { <-mainDone } @@ -631,61 +617,20 @@ func (w *window) resetFling() { //export gio_onKeyboardKeymap func gio_onKeyboardKeymap(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, format C.uint32_t, fd C.int32_t, size C.uint32_t) { - conn.repeat.Stop(0) defer syscall.Close(int(fd)) - if conn.xkbCompState != nil { - C.xkb_compose_state_unref(conn.xkbCompState) - conn.xkbCompState = nil - } - if conn.xkbCompTable != nil { - C.xkb_compose_table_unref(conn.xkbCompTable) - conn.xkbCompTable = nil - } - if conn.xkbState != nil { - C.xkb_state_unref(conn.xkbState) - conn.xkbState = nil - } - if conn.xkbMap != nil { - C.xkb_keymap_unref(conn.xkbMap) - conn.xkbMap = nil + conn.repeat.Stop(0) + if conn.xkb != nil { + conn.xkb.Destroy() } if format != C.WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 { return } - if conn.xkb == nil { - conn.xkb = C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS) - } - if conn.xkb == nil { - return - } - if conn.xkbCompTable == nil { - 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)) - conn.xkbCompTable = C.xkb_compose_table_new_from_locale(conn.xkb, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS) - if conn.xkbCompTable != nil { - conn.xkbCompState = C.xkb_compose_state_new(conn.xkbCompTable, C.XKB_COMPOSE_STATE_NO_FLAGS) - } - } - mapData, err := syscall.Mmap(int(fd), 0, int(size), syscall.PROT_READ, syscall.MAP_SHARED) + xkb, err := newXKB(format, fd, size) if err != nil { - return + // TODO: Do better. + panic(err) } - defer syscall.Munmap(mapData) - conn.xkbMap = C.xkb_keymap_new_from_buffer(conn.xkb, (*C.char)(unsafe.Pointer(&mapData[0])), C.size_t(size-1), C.XKB_KEYMAP_FORMAT_TEXT_V1, C.XKB_KEYMAP_COMPILE_NO_FLAGS) - if conn.xkbMap == nil { - return - } - conn.xkbState = C.xkb_state_new(conn.xkbMap) + conn.xkb = xkb } //export gio_onKeyboardEnter @@ -706,16 +651,14 @@ func gio_onKeyboardLeave(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, se //export gio_onKeyboardKey func gio_onKeyboardKey(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, timestamp, keyCode, state C.uint32_t) { t := time.Duration(timestamp) * time.Millisecond - conn.repeat.Stop(t) w := winMap[keyboard] w.resetFling() - if state != C.WL_KEYBOARD_KEY_STATE_PRESSED || conn.xkbMap == nil || conn.xkbState == nil || conn.xkbCompState == nil { + conn.repeat.Stop(t) + if state != C.WL_KEYBOARD_KEY_STATE_PRESSED || conn.xkb == nil { return } - // According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode." - keyCode += 8 - w.dispatchKey(keyCode) - if C.xkb_keymap_key_repeats(conn.xkbMap, C.xkb_keycode_t(keyCode)) == 1 { + conn.xkb.dispatchKey(w.w, keyCode) + if conn.xkb.isRepeatKey(keyCode) { conn.repeat.Start(w, keyCode, t) } } @@ -730,7 +673,7 @@ func (r *repeatState) Start(w *window, keyCode C.uint32_t, t time.Duration) { r.now = 0 r.stopC = stopC r.key = keyCode - r.win = w + r.win = w.w rate, delay := r.rate, r.delay go func() { timer := time.NewTimer(delay) @@ -785,7 +728,7 @@ func (r *repeatState) Repeat() { if r.last+delay > now { break } - r.win.dispatchKey(r.key) + conn.xkb.dispatchKey(r.win, r.key) r.last += delay } } @@ -818,7 +761,7 @@ loop: break } if w.dead { - w.w.event(DestroyEvent{}) + w.w.event(DestroyEvent{Err: w.pendingErr}) break } // Clear poll events. @@ -898,65 +841,13 @@ func (w *window) destroy() { } } -func (w *window) dispatchKey(keyCode C.uint32_t) { - if len(conn.utf8Buf) == 0 { - conn.utf8Buf = make([]byte, 1) - } - sym := C.xkb_state_key_get_one_sym(conn.xkbState, C.xkb_keycode_t(keyCode)) - if n, ok := convertKeysym(sym); ok { - cmd := key.Event{Name: n} - if C.xkb_state_mod_name_is_active(conn.xkbState, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_CTRL[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 { - cmd.Modifiers |= key.ModCommand - } - if C.xkb_state_mod_name_is_active(conn.xkbState, (*C.char)(unsafe.Pointer(&_XKB_MOD_NAME_SHIFT[0])), C.XKB_STATE_MODS_EFFECTIVE) == 1 { - cmd.Modifiers |= key.ModShift - } - w.w.event(cmd) - } - C.xkb_compose_state_feed(conn.xkbCompState, sym) - var size C.int - switch C.xkb_compose_state_get_status(conn.xkbCompState) { - case C.XKB_COMPOSE_CANCELLED, C.XKB_COMPOSE_COMPOSING: - return - case C.XKB_COMPOSE_COMPOSED: - size = C.xkb_compose_state_get_utf8(conn.xkbCompState, (*C.char)(unsafe.Pointer(&conn.utf8Buf[0])), C.size_t(len(conn.utf8Buf))) - if int(size) >= len(conn.utf8Buf) { - conn.utf8Buf = make([]byte, size+1) - size = C.xkb_compose_state_get_utf8(conn.xkbCompState, (*C.char)(unsafe.Pointer(&conn.utf8Buf[0])), C.size_t(len(conn.utf8Buf))) - } - C.xkb_compose_state_reset(conn.xkbCompState) - case C.XKB_COMPOSE_NOTHING: - size = C.xkb_state_key_get_utf8(conn.xkbState, C.xkb_keycode_t(keyCode), (*C.char)(unsafe.Pointer(&conn.utf8Buf[0])), C.size_t(len(conn.utf8Buf))) - if int(size) >= len(conn.utf8Buf) { - conn.utf8Buf = make([]byte, size+1) - size = C.xkb_state_key_get_utf8(conn.xkbState, C.xkb_keycode_t(keyCode), (*C.char)(unsafe.Pointer(&conn.utf8Buf[0])), C.size_t(len(conn.utf8Buf))) - } - } - // Report only printable runes. - str := conn.utf8Buf[:size] - 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 { - w.w.event(key.EditEvent{Text: string(str)}) - } -} - //export gio_onKeyboardModifiers func gio_onKeyboardModifiers(data unsafe.Pointer, keyboard *C.struct_wl_keyboard, serial, depressed, latched, locked, group C.uint32_t) { conn.repeat.Stop(0) - if conn.xkbState == nil { + if conn.xkb == nil { return } - xkbGrp := C.xkb_layout_index_t(group) - C.xkb_state_update_mask(conn.xkbState, C.xkb_mod_mask_t(depressed), C.xkb_mod_mask_t(latched), C.xkb_mod_mask_t(locked), xkbGrp, xkbGrp, xkbGrp) + conn.xkb.updateMask(depressed, latched, locked, group) } //export gio_onKeyboardRepeatInfo @@ -1096,6 +987,13 @@ func (w *window) isAnimating() bool { return w.animating || w.flinger.Active() } +func (w *window) kill(err error) { + if w.pendingErr == nil { + w.pendingErr = err + } + w.dead = true +} + func (w *window) draw(sync bool) { w.flushScroll() w.mu.Lock() @@ -1228,22 +1126,9 @@ func waylandConnect() error { func (c *wlConn) destroy() { c.repeat.Stop(0) - if c.xkbCompState != nil { - C.xkb_compose_state_unref(c.xkbCompState) - c.xkbCompState = nil - } - if c.xkbCompTable != nil { - C.xkb_compose_table_unref(c.xkbCompTable) - c.xkbCompTable = nil - } - if c.xkbState != nil { - C.xkb_state_unref(conn.xkbState) - } - if c.xkbMap != nil { - C.xkb_keymap_unref(c.xkbMap) - } if c.xkb != nil { - C.xkb_context_unref(c.xkb) + c.xkb.Destroy() + c.xkb = nil } if c.cursorSurf != nil { C.wl_surface_destroy(c.cursorSurf) @@ -1297,44 +1182,3 @@ func fromFixed(v C.wl_fixed_t) float32 { f := math.Float64frombits(b) - (3 << 43) return float32(f) } - -func convertKeysym(s C.xkb_keysym_t) (rune, bool) { - if '0' <= s && s <= '9' || 'A' <= s && s <= 'Z' { - return rune(s), true - } - if 'a' <= s && s <= 'z' { - return rune(s - 0x20), true - } - var n rune - 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 - default: - return 0, false - } - return n, true -} diff --git a/ui/app/xkb_linux.go b/ui/app/xkb_linux.go new file mode 100644 index 00000000..a81d5444 --- /dev/null +++ b/ui/app/xkb_linux.go @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: Unlicense OR MIT + +// +build !android + +package app + +/* +#cgo LDFLAGS: -lxkbcommon + +#include +#include +#include +*/ +import "C" + +import ( + "errors" + "fmt" + "os" + "syscall" + "unicode" + "unicode/utf8" + "unsafe" + + "gioui.org/ui/key" +) + +type xkb 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") +) + +func (x *xkb) Destroy() { + if x.state != 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 + } + 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 + } +} + +func newXKB(format C.uint32_t, fd C.int32_t, size C.uint32_t) (*xkb, error) { + xkb := &xkb{ + ctx: C.xkb_context_new(C.XKB_CONTEXT_NO_FLAGS), + } + if xkb.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)) + xkb.compTable = C.xkb_compose_table_new_from_locale(xkb.ctx, cloc, C.XKB_COMPOSE_COMPILE_NO_FLAGS) + if xkb.compTable == nil { + xkb.Destroy() + return nil, errors.New("newXKB: xkb_compose_table_new_from_locale failed") + } + xkb.compState = C.xkb_compose_state_new(xkb.compTable, C.XKB_COMPOSE_STATE_NO_FLAGS) + if xkb.compState == nil { + xkb.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 { + xkb.Destroy() + return nil, fmt.Errorf("newXKB: mmap of keymap failed: %v", err) + } + defer syscall.Munmap(mapData) + xkb.keyMap = C.xkb_keymap_new_from_buffer(xkb.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 xkb.keyMap == nil { + xkb.Destroy() + return nil, errors.New("newXKB: xkb_keymap_new_from_buffer failed") + } + xkb.state = C.xkb_state_new(xkb.keyMap) + if xkb.state == nil { + xkb.Destroy() + return nil, errors.New("newXKB: xkb_state_new failed") + } + return xkb, nil +} + +func (x *xkb) dispatchKey(w *Window, keyCode C.uint32_t) { + keyCode = mapXKBKeyCode(keyCode) + if len(x.utf8Buf) == 0 { + x.utf8Buf = make([]byte, 1) + } + sym := C.xkb_state_key_get_one_sym(x.state, C.xkb_keycode_t(keyCode)) + if n, ok := convertKeysym(sym); ok { + cmd := key.Event{Name: n} + 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 { + cmd.Modifiers |= key.ModCommand + } + 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 { + cmd.Modifiers |= key.ModShift + } + w.event(cmd) + } + C.xkb_compose_state_feed(x.compState, sym) + var size C.int + 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) + case C.XKB_COMPOSE_NOTHING: + size = C.xkb_state_key_get_utf8(x.state, C.xkb_keycode_t(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, C.xkb_keycode_t(keyCode), (*C.char)(unsafe.Pointer(&x.utf8Buf[0])), C.size_t(len(x.utf8Buf))) + } + } + // Report only printable runes. + str := x.utf8Buf[:size] + 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 { + w.event(key.EditEvent{Text: string(str)}) + } +} + +func (x *xkb) isRepeatKey(keyCode C.uint32_t) bool { + keyCode = mapXKBKeyCode(keyCode) + return C.xkb_keymap_key_repeats(conn.xkb.keyMap, C.xkb_keycode_t(keyCode)) == 1 +} + +func (x *xkb) updateMask(depressed, latched, locked, group C.uint32_t) { + xkbGrp := C.xkb_layout_index_t(group) + C.xkb_state_update_mask(conn.xkb.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 C.uint32_t) C.uint32_t { + // According to the xkb_v1 spec: "to determine the xkb keycode, clients must add 8 to the key event keycode." + return keyCode + 8 +} + +func convertKeysym(s C.xkb_keysym_t) (rune, bool) { + if '0' <= s && s <= '9' || 'A' <= s && s <= 'Z' { + return rune(s), true + } + if 'a' <= s && s <= 'z' { + return rune(s - 0x20), true + } + var n rune + 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 + default: + return 0, false + } + return n, true +}