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/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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче