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:
Nigel Tao 2015-08-19 16:10:51 +10:00
Родитель 01fa66802f
Коммит 83e87dc1dc
4 изменённых файлов: 274 добавлений и 39 удалений

Просмотреть файл

@ -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/driver/internal/pump"
"golang.org/x/exp/shiny/screen" "golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/mouse" "golang.org/x/mobile/event/mouse"
) )
@ -30,8 +31,9 @@ type completion struct {
} }
type screenImpl struct { type screenImpl struct {
xc *xgb.Conn xc *xgb.Conn
xsi *xproto.ScreenInfo xsi *xproto.ScreenInfo
keysyms [256][2]xproto.Keysym
atomWMDeleteWindow xproto.Atom atomWMDeleteWindow xproto.Atom
atomWMProtocols xproto.Atom atomWMProtocols xproto.Atom
@ -63,6 +65,9 @@ func newScreenImpl(xc *xgb.Conn) (*screenImpl, error) {
if err := s.initAtoms(); err != nil { if err := s.initAtoms(); err != nil {
return nil, err return nil, err
} }
if err := s.initKeyboardMapping(); err != nil {
return nil, err
}
if err := s.initPictformats(); err != nil { if err := s.initPictformats(); err != nil {
return nil, err return nil, err
} }
@ -115,10 +120,20 @@ func (s *screenImpl) run() {
// TODO: xw = ev.Event // TODO: xw = ev.Event
case xproto.FocusOutEvent: case xproto.FocusOutEvent:
// TODO: xw = ev.Event // TODO: xw = ev.Event
case xproto.KeyPressEvent: 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: 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: case xproto.ButtonPressEvent:
if w := s.findWindow(ev.Event); w != nil { 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 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 { func (s *screenImpl) initPictformats() error {
pformats, err := render.QueryPictFormats(s.xc).Reply() pformats, err := render.QueryPictFormats(s.xc).Reply()
if err != nil { if err != nil {

Просмотреть файл

@ -98,6 +98,39 @@ func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) {
w.Send(paint.Event{}) 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) { 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 // TODO: should a mouse.Event have a separate MouseModifiers field, for
// which buttons are pressed during a mouse move? // 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, 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/driver"
"golang.org/x/exp/shiny/screen" "golang.org/x/exp/shiny/screen"
"golang.org/x/image/math/f64" "golang.org/x/image/math/f64"
"golang.org/x/mobile/event/key"
"golang.org/x/mobile/event/paint" "golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size" "golang.org/x/mobile/event/size"
) )
@ -54,8 +55,15 @@ func main() {
// TODO: be more interesting. // TODO: be more interesting.
fmt.Printf("got event %#v\n", e) fmt.Printf("got event %#v\n", e)
case size.Event: case key.Event:
sz = e 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: case paint.Event:
wBounds := image.Rectangle{Max: image.Point{sz.WidthPx, sz.HeightPx}} wBounds := image.Rectangle{Max: image.Point{sz.WidthPx, sz.HeightPx}}
@ -70,6 +78,9 @@ func main() {
case screen.UploadedEvent: case screen.UploadedEvent:
// No-op. // No-op.
case size.Event:
sz = e
case error: case error:
log.Print(e) log.Print(e)
} }