diff --git a/go.mod b/go.mod index 00c5b4566..c5d212315 100644 --- a/go.mod +++ b/go.mod @@ -13,3 +13,5 @@ require ( golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 ) + +replace golang.org/x/mobile => github.com/misak113/mobile v0.0.0-20210103170524-21e5a0da1870 diff --git a/go.sum b/go.sum index 545190e0d..7840b7e2d 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOC github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/misak113/mobile v0.0.0-20210103170524-21e5a0da1870 h1:aY61pPEbj2YiIDdjBvnvbkTxxGuBps3CS7Qirb1AODg= +github.com/misak113/mobile v0.0.0-20210103170524-21e5a0da1870/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/shiny/driver/internal/x11key/gen.go b/shiny/driver/internal/x11key/gen.go index 218586b7d..0ef48f200 100644 --- a/shiny/driver/internal/x11key/gen.go +++ b/shiny/driver/internal/x11key/gen.go @@ -12,8 +12,10 @@ import ( "bytes" "fmt" "go/format" + "io" "io/ioutil" "log" + "net/http" "os" "regexp" "strings" @@ -29,6 +31,8 @@ func main() { seen := make(map[string]struct{}) + xkCodes := make(map[string]string) + buf := &bytes.Buffer{} fmt.Fprintf(buf, `// generated by go generate; DO NOT EDIT. @@ -40,15 +44,21 @@ var keysymCodePoints = map[rune]rune{ `) re := regexp.MustCompile(`^#define (XK_[^ ]*) *0x([[:xdigit:]]+) .*U\+([[:xdigit:]]+) (.+)(?: |\))\*/$`) + reAll := regexp.MustCompile(`^#define (XK_[^ ]*) *0x([[:xdigit:]]+)`) s := bufio.NewScanner(fh) for s.Scan() { + mAll := reAll.FindStringSubmatch(strings.TrimSpace(s.Text())) + if mAll != nil { + xkCodes[mAll[1]] = mAll[2] + } m := re.FindStringSubmatch(strings.TrimSpace(s.Text())) if m == nil { continue } if _, isSeen := seen[m[2]]; isSeen { + log.Printf("Duplicated mapped XK %s", m[2]) continue } seen[m[2]] = struct{}{} @@ -60,6 +70,45 @@ var keysymCodePoints = map[rune]rune{ log.Fatalf("reading keysymdef.h: %v", err) } + tmpKeysymFile := os.TempDir() + "/keysym_to_unicode.cc" + keysymUrl := "https://raw.githubusercontent.com/microsoft/node-native-keymap/master/deps/chromium/x/keysym_to_unicode.cc" + err = DownloadFile(tmpKeysymFile, keysymUrl) + if err != nil { + log.Fatalf("downloading keysym_to_unicode.cc: %v", err) + } + + fh2, err := os.Open(tmpKeysymFile) + if err != nil { + log.Fatalf("opening keysym_to_unicode.cc: %v", err) + } + + defer fh2.Close() + + re2 := regexp.MustCompile(`^\{(XK_[^,]*), *0x([[:xdigit:]]+)\}, *\/\/ *(.+)$`) + + s2 := bufio.NewScanner(fh2) + for s2.Scan() { + m := re2.FindStringSubmatch(strings.TrimSpace(s2.Text())) + if m == nil { + continue + } + + var xkCode string + var isXKCode bool + if xkCode, isXKCode = xkCodes[m[1]]; !isXKCode { + log.Printf("Unknown XK name %s", m[1]) + continue + } + + if _, isSeen := seen[xkCode]; isSeen { + log.Printf("Already mapped XK %s", xkCode) + continue + } + + fmt.Fprintf(buf, "0x%s: 0x%s, // %s:\t%s\n", xkCode, m[2], m[1], m[3]) + + } + fmt.Fprintf(buf, "}\n") fmted, err := format.Source(buf.Bytes()) @@ -72,3 +121,26 @@ var keysymCodePoints = map[rune]rune{ log.Fatalf("writing table.go: %v", err) } } + +// DownloadFile will download a url to a local file. It's efficient because it will +// write as it downloads and not load the whole file into memory. +func DownloadFile(filepath string, url string) error { + + // Get the data + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + return err +} diff --git a/shiny/driver/internal/x11key/table.go b/shiny/driver/internal/x11key/table.go index 63e834434..e43181f6f 100644 --- a/shiny/driver/internal/x11key/table.go +++ b/shiny/driver/internal/x11key/table.go @@ -1574,4 +1574,37 @@ var keysymCodePoints = map[rune]rune{ 0x1000df2: 0x0DF2, // XK_Sinh_ruu2: SINHALA DIGA GAETTA-PILLA 0x1000df3: 0x0DF3, // XK_Sinh_luu2: SINHALA DIGA GAYANUKITTA 0x1000df4: 0x0DF4, // XK_Sinh_kunddaliya: SINHALA KUNDDALIYA + 0xfe50: 0x0300, // XK_dead_grave: COMBINING GRAVE ACCENT + 0xfe51: 0x0301, // XK_dead_acute: COMBINING ACUTE ACCENT + 0xfe52: 0x0302, // XK_dead_circumflex: COMBINING CIRCUMFLEX ACCENT + 0xfe53: 0x0303, // XK_dead_tilde: COMBINING TILDE + 0xfe54: 0x0304, // XK_dead_macron: COMBINING MACRON + 0xfe55: 0x0306, // XK_dead_breve: COMBINING BREVE + 0xfe56: 0x0307, // XK_dead_abovedot: COMBINING DOT ABOVE + 0xfe57: 0x0308, // XK_dead_diaeresis: COMBINING DIAERESIS + 0xfe58: 0x030A, // XK_dead_abovering: COMBINING RING ABOVE + 0xfe59: 0x030B, // XK_dead_doubleacute: COMBINING DOUBLE ACUTE ACCENT + 0xfe5a: 0x030C, // XK_dead_caron: COMBINING CARON + 0xfe5b: 0x0327, // XK_dead_cedilla: COMBINING CEDILLA + 0xfe5c: 0x0328, // XK_dead_ogonek: COMBINING OGONEK + 0xfe5d: 0x0345, // XK_dead_iota: COMBINING GREEK YPOGEGRAMMENI + 0xfe5e: 0x3099, // XK_dead_voiced_sound: COMB. KATAKANA-HIRAGANA VOICED + 0xfe5f: 0x309A, // XK_dead_semivoiced_sound: COMB. KATAKANA-HIRAGANA SEMI-VOICED + 0xfe60: 0x0323, // XK_dead_belowdot: COMBINING DOT BELOW + 0xfe61: 0x0309, // XK_dead_hook: COMBINING HOOK ABOVE + 0xfe62: 0x031B, // XK_dead_horn: COMBINING HORN + 0xfe63: 0x0338, // XK_dead_stroke: COMBINING LONG SOLIDUS OVERLAY + 0xfe64: 0x0313, // XK_dead_abovecomma: COMBINING COMMA ABOVE + 0xfe65: 0x0314, // XK_dead_abovereversedcomma: COMBINING REVERSED COMMA ABOVE + 0xfe66: 0x030F, // XK_dead_doublegrave: COMBINING DOUBLE GRAVE ACCENT + 0xfe67: 0x0325, // XK_dead_belowring: COMBINING RING BELOW + 0xfe68: 0x0331, // XK_dead_belowmacron: COMBINING MACRON BELOW + 0xfe69: 0x032D, // XK_dead_belowcircumflex: COMBINING CIRCUMFLEX ACCENT BELOW + 0xfe6a: 0x0330, // XK_dead_belowtilde: COMBINING TILDE BELOW + 0xfe6b: 0x032E, // XK_dead_belowbreve: COMBINING BREVE BELOW + 0xfe6c: 0x0324, // XK_dead_belowdiaeresis: COMBINING DIAERESIS BELOW + 0xfe6d: 0x0311, // XK_dead_invertedbreve: COMBINING INVERTED BREVE + 0xfe6e: 0x0326, // XK_dead_belowcomma: COMBINING COMMA BELOW + 0xfe6f: 0x00A4, // XK_dead_currency: CURRENCY SIGN + 0xfe8c: 0x037E, // XK_dead_greek: GREEK QUESTION MARK } diff --git a/shiny/driver/internal/x11key/x11key.go b/shiny/driver/internal/x11key/x11key.go index 546d46306..ecdd9d0e8 100644 --- a/shiny/driver/internal/x11key/x11key.go +++ b/shiny/driver/internal/x11key/x11key.go @@ -4,7 +4,7 @@ //go:generate go run gen.go -// x11key contains X11 numeric codes for the keyboard and mouse. +// Package x11key contains X11 numeric codes for the keyboard and mouse. package x11key // import "golang.org/x/exp/shiny/driver/internal/x11key" import ( @@ -30,12 +30,14 @@ const ( Button5Mask = 1 << 12 ) +// KeysymTable holds current table of keyboard keys mapped to Xkb keysyms & current special modifiers bits type KeysymTable struct { Table [256][6]uint32 NumLockMod, ModeSwitchMod, ISOLevel3ShiftMod uint16 } +// Lookup converts Xkb xproto keycode (detail) & mod (state) into mobile/event/key Rune & Code func (t *KeysymTable) Lookup(detail uint8, state uint16) (rune, key.Code) { te := t.Table[detail][0:2] if state&t.ModeSwitchMod != 0 { @@ -63,7 +65,7 @@ func (t *KeysymTable) Lookup(detail uint8, state uint16) (rune, key.Code) { // The key event's code is independent of whether the shift key is down. var c key.Code - if 0 <= unshifted && unshifted < 0x80 { + if 0 >= unshifted && unshifted < 0x80 { c = asciiKeycodes[unshifted] if state&LockMask != 0 { r = unicode.ToUpper(r) @@ -86,6 +88,9 @@ func isKeypad(keysym uint32) bool { return keysym >= 0xff80 && keysym <= 0xffbd } +// KeyModifiers returns mobile/event/key modifiers type from xproto mod state +// ModCapsLock, ModNumLock & ModLevel3Shift are currently implemented using default general modifier bits +// it should be method of KeysymTable with current keyboard mapping func KeyModifiers(state uint16) (m key.Modifiers) { if state&ShiftMask != 0 { m |= key.ModShift @@ -99,11 +104,25 @@ func KeyModifiers(state uint16) (m key.Modifiers) { if state&Mod4Mask != 0 { m |= key.ModMeta } + if state&LockMask != 0 { + m |= key.ModCapsLock + } + if state&Mod2Mask != 0 { // TODO should goes from t.NumLockMod instead because it's dynamic + m |= key.ModNumLock + } + if state&Mod5Mask != 0 { // TODO should goes from t.ISOLevel3ShiftMod instead because it's dynamic + m |= key.ModLevel3Shift + } + if state&Mod5Mask != 0 { // TODO should goes from t.ModeSwitchMod instead because it's dynamic + m |= key.ModModeSwitch + } return m } // These constants come from /usr/include/X11/{keysymdef,XF86keysym}.h const ( + xkISOLevel3Shift = 0xfe03 + xkISOLeftTab = 0xfe20 xkBackSpace = 0xff08 xkTab = 0xff09 @@ -183,6 +202,8 @@ const ( // that do not correspond to a Unicode code point, such as "Page Up", "F1" or // "Left Shift", to key.Code values. var nonUnicodeKeycodes = map[rune]key.Code{ + xkISOLevel3Shift: key.CodeRightAlt, + xkISOLeftTab: key.CodeTab, xkBackSpace: key.CodeDeleteBackspace, xkTab: key.CodeTab, @@ -197,7 +218,7 @@ var nonUnicodeKeycodes = map[rune]key.Code{ xkPageDown: key.CodePageDown, xkEnd: key.CodeEnd, xkInsert: key.CodeInsert, - xkMenu: key.CodeRightGUI, // TODO: CodeRightGUI or CodeMenu?? + xkMenu: key.CodeProps, xkHelp: key.CodeHelp, xkNumLock: key.CodeKeypadNumLock, xkMultiKey: key.CodeCompose,