shiny/driver/x11driver: implement key events.
Change-Id: I6adba113086ab6436e5f01a1d4b49ca731f22f3f Reviewed-on: https://go-review.googlesource.com/13713 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Родитель
01fa66802f
Коммит
83e87dc1dc
|
@ -0,0 +1,192 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package x11driver
|
||||
|
||||
import (
|
||||
"golang.org/x/mobile/event/key"
|
||||
)
|
||||
|
||||
// These constants come from /usr/include/X11/X.h
|
||||
const (
|
||||
xShiftMask = 1 << 0
|
||||
xLockMask = 1 << 1
|
||||
xControlMask = 1 << 2
|
||||
xMod1Mask = 1 << 3
|
||||
xMod2Mask = 1 << 4
|
||||
xMod3Mask = 1 << 5
|
||||
xMod4Mask = 1 << 6
|
||||
xMod5Mask = 1 << 7
|
||||
xButton1Mask = 1 << 8
|
||||
xButton2Mask = 1 << 9
|
||||
xButton3Mask = 1 << 10
|
||||
xButton4Mask = 1 << 11
|
||||
xButton5Mask = 1 << 12
|
||||
)
|
||||
|
||||
func keyModifiers(state uint16) (m key.Modifiers) {
|
||||
if state&xShiftMask != 0 {
|
||||
m |= key.ModShift
|
||||
}
|
||||
if state&xControlMask != 0 {
|
||||
m |= key.ModControl
|
||||
}
|
||||
if state&xMod1Mask != 0 {
|
||||
m |= key.ModAlt
|
||||
}
|
||||
if state&xMod4Mask != 0 {
|
||||
m |= key.ModMeta
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// These constants come from /usr/include/X11/{keysymdef,XF86keysym}.h
|
||||
const (
|
||||
xkISOLeftTab = 0xfe20
|
||||
xkBackSpace = 0xff08
|
||||
xkTab = 0xff09
|
||||
xkReturn = 0xff0d
|
||||
xkEscape = 0xff1b
|
||||
xkHome = 0xff50
|
||||
xkLeft = 0xff51
|
||||
xkUp = 0xff52
|
||||
xkRight = 0xff53
|
||||
xkDown = 0xff54
|
||||
xkPageUp = 0xff55
|
||||
xkPageDown = 0xff56
|
||||
xkEnd = 0xff57
|
||||
xkInsert = 0xff63
|
||||
xkMenu = 0xff67
|
||||
xkF1 = 0xffbe
|
||||
xkF2 = 0xffbf
|
||||
xkF3 = 0xffc0
|
||||
xkF4 = 0xffc1
|
||||
xkF5 = 0xffc2
|
||||
xkF6 = 0xffc3
|
||||
xkF7 = 0xffc4
|
||||
xkF8 = 0xffc5
|
||||
xkF9 = 0xffc6
|
||||
xkF10 = 0xffc7
|
||||
xkF11 = 0xffc8
|
||||
xkF12 = 0xffc9
|
||||
xkShiftL = 0xffe1
|
||||
xkShiftR = 0xffe2
|
||||
xkControlL = 0xffe3
|
||||
xkControlR = 0xffe4
|
||||
xkAltL = 0xffe9
|
||||
xkAltR = 0xffea
|
||||
xkSuperL = 0xffeb
|
||||
xkSuperR = 0xffec
|
||||
xkDelete = 0xffff
|
||||
|
||||
xf86xkAudioLowerVolume = 0x1008ff11
|
||||
xf86xkAudioMute = 0x1008ff12
|
||||
xf86xkAudioRaiseVolume = 0x1008ff13
|
||||
)
|
||||
|
||||
// nonUnicodeKeycodes maps from those xproto.Keysym values (converted to runes)
|
||||
// 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{
|
||||
xkISOLeftTab: key.CodeTab,
|
||||
xkBackSpace: key.CodeDeleteBackspace,
|
||||
xkTab: key.CodeTab,
|
||||
xkReturn: key.CodeReturnEnter,
|
||||
xkEscape: key.CodeEscape,
|
||||
xkHome: key.CodeHome,
|
||||
xkLeft: key.CodeLeftArrow,
|
||||
xkUp: key.CodeUpArrow,
|
||||
xkRight: key.CodeRightArrow,
|
||||
xkDown: key.CodeDownArrow,
|
||||
xkPageUp: key.CodePageUp,
|
||||
xkPageDown: key.CodePageDown,
|
||||
xkEnd: key.CodeEnd,
|
||||
xkInsert: key.CodeInsert,
|
||||
xkMenu: key.CodeRightGUI, // TODO: CodeRightGUI or CodeMenu??
|
||||
|
||||
xkF1: key.CodeF1,
|
||||
xkF2: key.CodeF2,
|
||||
xkF3: key.CodeF3,
|
||||
xkF4: key.CodeF4,
|
||||
xkF5: key.CodeF5,
|
||||
xkF6: key.CodeF6,
|
||||
xkF7: key.CodeF7,
|
||||
xkF8: key.CodeF8,
|
||||
xkF9: key.CodeF9,
|
||||
xkF10: key.CodeF10,
|
||||
xkF11: key.CodeF11,
|
||||
xkF12: key.CodeF12,
|
||||
|
||||
xkShiftL: key.CodeLeftShift,
|
||||
xkShiftR: key.CodeRightShift,
|
||||
xkControlL: key.CodeLeftControl,
|
||||
xkControlR: key.CodeRightControl,
|
||||
xkAltL: key.CodeLeftAlt,
|
||||
xkAltR: key.CodeRightAlt,
|
||||
xkSuperL: key.CodeLeftGUI,
|
||||
xkSuperR: key.CodeRightGUI,
|
||||
|
||||
xkDelete: key.CodeDeleteForward,
|
||||
|
||||
xf86xkAudioRaiseVolume: key.CodeVolumeUp,
|
||||
xf86xkAudioLowerVolume: key.CodeVolumeDown,
|
||||
xf86xkAudioMute: key.CodeMute,
|
||||
}
|
||||
|
||||
// asciiKeycodes maps lower-case ASCII runes to key.Code values.
|
||||
var asciiKeycodes = [0x80]key.Code{
|
||||
'a': key.CodeA,
|
||||
'b': key.CodeB,
|
||||
'c': key.CodeC,
|
||||
'd': key.CodeD,
|
||||
'e': key.CodeE,
|
||||
'f': key.CodeF,
|
||||
'g': key.CodeG,
|
||||
'h': key.CodeH,
|
||||
'i': key.CodeI,
|
||||
'j': key.CodeJ,
|
||||
'k': key.CodeK,
|
||||
'l': key.CodeL,
|
||||
'm': key.CodeM,
|
||||
'n': key.CodeN,
|
||||
'o': key.CodeO,
|
||||
'p': key.CodeP,
|
||||
'q': key.CodeQ,
|
||||
'r': key.CodeR,
|
||||
's': key.CodeS,
|
||||
't': key.CodeT,
|
||||
'u': key.CodeU,
|
||||
'v': key.CodeV,
|
||||
'w': key.CodeW,
|
||||
'x': key.CodeX,
|
||||
'y': key.CodeY,
|
||||
'z': key.CodeZ,
|
||||
|
||||
'1': key.Code1,
|
||||
'2': key.Code2,
|
||||
'3': key.Code3,
|
||||
'4': key.Code4,
|
||||
'5': key.Code5,
|
||||
'6': key.Code6,
|
||||
'7': key.Code7,
|
||||
'8': key.Code8,
|
||||
'9': key.Code9,
|
||||
'0': key.Code0,
|
||||
|
||||
' ': key.CodeSpacebar,
|
||||
'-': key.CodeHyphenMinus,
|
||||
'=': key.CodeEqualSign,
|
||||
'[': key.CodeLeftSquareBracket,
|
||||
']': key.CodeRightSquareBracket,
|
||||
'\\': key.CodeBackslash,
|
||||
';': key.CodeSemicolon,
|
||||
'\'': key.CodeApostrophe,
|
||||
'`': key.CodeGraveAccent,
|
||||
',': key.CodeComma,
|
||||
'.': key.CodeFullStop,
|
||||
'/': key.CodeSlash,
|
||||
|
||||
// TODO: distinguish CodeKeypadSlash vs CodeSlash, and similarly for other
|
||||
// keypad codes.
|
||||
}
|
|
@ -17,6 +17,7 @@ import (
|
|||
|
||||
"golang.org/x/exp/shiny/driver/internal/pump"
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"golang.org/x/mobile/event/mouse"
|
||||
)
|
||||
|
||||
|
@ -30,8 +31,9 @@ type completion struct {
|
|||
}
|
||||
|
||||
type screenImpl struct {
|
||||
xc *xgb.Conn
|
||||
xsi *xproto.ScreenInfo
|
||||
xc *xgb.Conn
|
||||
xsi *xproto.ScreenInfo
|
||||
keysyms [256][2]xproto.Keysym
|
||||
|
||||
atomWMDeleteWindow xproto.Atom
|
||||
atomWMProtocols xproto.Atom
|
||||
|
@ -63,6 +65,9 @@ func newScreenImpl(xc *xgb.Conn) (*screenImpl, error) {
|
|||
if err := s.initAtoms(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.initKeyboardMapping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.initPictformats(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -115,10 +120,20 @@ func (s *screenImpl) run() {
|
|||
// TODO: xw = ev.Event
|
||||
case xproto.FocusOutEvent:
|
||||
// TODO: xw = ev.Event
|
||||
|
||||
case xproto.KeyPressEvent:
|
||||
// TODO: xw = ev.Event
|
||||
if w := s.findWindow(ev.Event); w != nil {
|
||||
w.handleKey(ev.Detail, ev.State, key.DirPress)
|
||||
} else {
|
||||
noWindowFound = true
|
||||
}
|
||||
|
||||
case xproto.KeyReleaseEvent:
|
||||
// TODO: xw = ev.Event
|
||||
if w := s.findWindow(ev.Event); w != nil {
|
||||
w.handleKey(ev.Detail, ev.State, key.DirRelease)
|
||||
} else {
|
||||
noWindowFound = true
|
||||
}
|
||||
|
||||
case xproto.ButtonPressEvent:
|
||||
if w := s.findWindow(ev.Event); w != nil {
|
||||
|
@ -353,6 +368,23 @@ func (s *screenImpl) internAtom(name string) (xproto.Atom, error) {
|
|||
return r.Atom, nil
|
||||
}
|
||||
|
||||
func (s *screenImpl) initKeyboardMapping() error {
|
||||
const keyLo, keyHi = 8, 255
|
||||
km, err := xproto.GetKeyboardMapping(s.xc, keyLo, keyHi-keyLo+1).Reply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n := int(km.KeysymsPerKeycode)
|
||||
if n < 2 {
|
||||
return fmt.Errorf("x11driver: too few keysyms per keycode: %d", n)
|
||||
}
|
||||
for i := keyLo; i <= keyHi; i++ {
|
||||
s.keysyms[i][0] = km.Keysyms[(i-keyLo)*n+0]
|
||||
s.keysyms[i][1] = km.Keysyms[(i-keyLo)*n+1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *screenImpl) initPictformats() error {
|
||||
pformats, err := render.QueryPictFormats(s.xc).Reply()
|
||||
if err != nil {
|
||||
|
|
|
@ -98,6 +98,39 @@ func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) {
|
|||
w.Send(paint.Event{})
|
||||
}
|
||||
|
||||
func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) {
|
||||
// The key event's rune depends on whether the shift key is down.
|
||||
unshifted := rune(w.s.keysyms[detail][0])
|
||||
r := unshifted
|
||||
if state&xShiftMask != 0 {
|
||||
r = rune(w.s.keysyms[detail][1])
|
||||
// In X11, a zero xproto.Keysym when shift is down means to use what
|
||||
// the xproto.Keysym is when shift is up.
|
||||
if r == 0 {
|
||||
r = unshifted
|
||||
}
|
||||
}
|
||||
|
||||
// The key event's code is independent of whether the shift key is down.
|
||||
var c key.Code
|
||||
if 0 <= unshifted && unshifted < 0x80 {
|
||||
// TODO: distinguish the regular '2' key and number-pad '2' key (with
|
||||
// Num-Lock).
|
||||
c = asciiKeycodes[unshifted]
|
||||
} else {
|
||||
r, c = -1, nonUnicodeKeycodes[unshifted]
|
||||
}
|
||||
|
||||
// TODO: Unicode-but-not-ASCII keysyms like the Swiss keyboard's 'ö'.
|
||||
|
||||
w.Send(key.Event{
|
||||
Rune: r,
|
||||
Code: c,
|
||||
Modifiers: keyModifiers(state),
|
||||
Direction: dir,
|
||||
})
|
||||
}
|
||||
|
||||
func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir mouse.Direction) {
|
||||
// TODO: should a mouse.Event have a separate MouseModifiers field, for
|
||||
// which buttons are pressed during a mouse move?
|
||||
|
@ -109,36 +142,3 @@ func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir
|
|||
Direction: dir,
|
||||
})
|
||||
}
|
||||
|
||||
// These constants come from /usr/include/X11/X.h
|
||||
const (
|
||||
xShiftMask = 1 << 0
|
||||
xLockMask = 1 << 1
|
||||
xControlMask = 1 << 2
|
||||
xMod1Mask = 1 << 3
|
||||
xMod2Mask = 1 << 4
|
||||
xMod3Mask = 1 << 5
|
||||
xMod4Mask = 1 << 6
|
||||
xMod5Mask = 1 << 7
|
||||
xButton1Mask = 1 << 8
|
||||
xButton2Mask = 1 << 9
|
||||
xButton3Mask = 1 << 10
|
||||
xButton4Mask = 1 << 11
|
||||
xButton5Mask = 1 << 12
|
||||
)
|
||||
|
||||
func keyModifiers(state uint16) (m key.Modifiers) {
|
||||
if state&xShiftMask != 0 {
|
||||
m |= key.ModShift
|
||||
}
|
||||
if state&xControlMask != 0 {
|
||||
m |= key.ModControl
|
||||
}
|
||||
if state&xMod1Mask != 0 {
|
||||
m |= key.ModAlt
|
||||
}
|
||||
if state&xMod4Mask != 0 {
|
||||
m |= key.ModMeta
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"golang.org/x/exp/shiny/driver"
|
||||
"golang.org/x/exp/shiny/screen"
|
||||
"golang.org/x/image/math/f64"
|
||||
"golang.org/x/mobile/event/key"
|
||||
"golang.org/x/mobile/event/paint"
|
||||
"golang.org/x/mobile/event/size"
|
||||
)
|
||||
|
@ -54,8 +55,15 @@ func main() {
|
|||
// TODO: be more interesting.
|
||||
fmt.Printf("got event %#v\n", e)
|
||||
|
||||
case size.Event:
|
||||
sz = e
|
||||
case key.Event:
|
||||
if e.Rune >= 0 {
|
||||
fmt.Printf("key.Event: %q (%v), %v, %v\n", e.Rune, e.Code, e.Modifiers, e.Direction)
|
||||
} else {
|
||||
fmt.Printf("key.Event: (%v), %v, %v\n", e.Code, e.Modifiers, e.Direction)
|
||||
}
|
||||
if e.Code == key.CodeEscape {
|
||||
return
|
||||
}
|
||||
|
||||
case paint.Event:
|
||||
wBounds := image.Rectangle{Max: image.Point{sz.WidthPx, sz.HeightPx}}
|
||||
|
@ -70,6 +78,9 @@ func main() {
|
|||
case screen.UploadedEvent:
|
||||
// No-op.
|
||||
|
||||
case size.Event:
|
||||
sz = e
|
||||
|
||||
case error:
|
||||
log.Print(e)
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче