@@ 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 <stdlib.h>
#include <wayland-client.h>
#include <wayland-cursor.h>
-#include <xkbcommon/xkbcommon.h>
-#include <xkbcommon/xkbcommon-compose.h>
#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
- }
- 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
+ // TODO: Do better.
+ panic(err)
}
- 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
-}
@@ 0,0 1,219 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+// +build !android
+
+package app
+
+/*
+#cgo LDFLAGS: -lxkbcommon
+
+#include <stdlib.h>
+#include <xkbcommon/xkbcommon.h>
+#include <xkbcommon/xkbcommon-compose.h>
+*/
+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
+}