shiny: handle numpad keys correctly in x11driver

Figure out which modifier is the numlock modifier, use the correct
keysym when the numlock modifier is set and map numpad keysyms to their
runes and key.Codes.

Change-Id: If8bb28cc10564aa0ecaf4a45753993496f288d6e
Reviewed-on: https://go-review.googlesource.com/c/exp/+/175084
Run-TryBot: Alessandro Arzilli <alessandro.arzilli@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/shiny/driver/gldriver/x11.c b/shiny/driver/gldriver/x11.c
index afb38dc..dff6d2d 100644
--- a/shiny/driver/gldriver/x11.c
+++ b/shiny/driver/gldriver/x11.c
@@ -151,6 +151,7 @@
 			keysyms[(k-key_lo)*keysyms_per_keycode + 0],
 			keysyms[(k-key_lo)*keysyms_per_keycode + 1]);
 	}
+	//TODO: use GetModifierMapping to figure out which modifier is the numlock modifier.
 }
 
 void
diff --git a/shiny/driver/gldriver/x11.go b/shiny/driver/gldriver/x11.go
index 61bb7ed..f73098d 100644
--- a/shiny/driver/gldriver/x11.go
+++ b/shiny/driver/gldriver/x11.go
@@ -208,7 +208,7 @@
 		return
 	}
 
-	r, c := theKeysyms.Lookup(detail, state)
+	r, c := theKeysyms.Lookup(detail, state, 0)
 	w.Send(key.Event{
 		Rune:      r,
 		Code:      c,
diff --git a/shiny/driver/internal/x11key/x11key.go b/shiny/driver/internal/x11key/x11key.go
index 3bad0ed..b916a44 100644
--- a/shiny/driver/internal/x11key/x11key.go
+++ b/shiny/driver/internal/x11key/x11key.go
@@ -32,11 +32,15 @@
 
 type KeysymTable [256][2]uint32
 
-func (t *KeysymTable) Lookup(detail uint8, state uint16) (rune, key.Code) {
+func (t *KeysymTable) Lookup(detail uint8, state uint16, numLockMod uint16) (rune, key.Code) {
 	// The key event's rune depends on whether the shift key is down.
 	unshifted := rune(t[detail][0])
 	r := unshifted
-	if state&ShiftMask != 0 {
+	if state&numLockMod != 0 && isKeypad(t[detail][1]) {
+		if state&ShiftMask == 0 {
+			r = rune(t[detail][1])
+		}
+	} else if state&ShiftMask != 0 {
 		r = rune(t[detail][1])
 		// In X11, a zero keysym when shift is down means to use what the
 		// keysym is when shift is up.
@@ -48,16 +52,16 @@
 	// 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]
 		if state&LockMask != 0 {
 			r = unicode.ToUpper(r)
 		}
+	} else if kk, isKeypad := keypadKeysyms[r]; isKeypad {
+		r, c = kk.rune, kk.code
 	} else if nuk := nonUnicodeKeycodes[unshifted]; nuk != key.CodeUnknown {
 		r, c = -1, nuk
-	} else if uk, isUnicode := keysymCodePoints[r]; isUnicode {
-		r = uk
+	} else {
+		r = keysymCodePoints[r]
 		if state&LockMask != 0 {
 			r = unicode.ToUpper(r)
 		}
@@ -66,6 +70,10 @@
 	return r, c
 }
 
+func isKeypad(keysym uint32) bool {
+	return keysym >= 0xff80 && keysym <= 0xffbd
+}
+
 func KeyModifiers(state uint16) (m key.Modifiers) {
 	if state&ShiftMask != 0 {
 		m |= key.ModShift
@@ -101,29 +109,58 @@
 	xkInsert     = 0xff63
 	xkMenu       = 0xff67
 	xkHelp       = 0xff6a
-	xkNumLock    = 0xff7f
-	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
-	xkCapsLock   = 0xffe5
-	xkAltL       = 0xffe9
-	xkAltR       = 0xffea
-	xkSuperL     = 0xffeb
-	xkSuperR     = 0xffec
-	xkDelete     = 0xffff
+
+	xkNumLock        = 0xff7f
+	xkKeypadEnter    = 0xff8d
+	xkKeypadHome     = 0xff95
+	xkKeypadLeft     = 0xff96
+	xkKeypadUp       = 0xff97
+	xkKeypadRight    = 0xff98
+	xkKeypadDown     = 0xff99
+	xkKeypadPageUp   = 0xff9a
+	xkKeypadPageDown = 0xff9b
+	xkKeypadEnd      = 0xff9c
+	xkKeypadInsert   = 0xff9e
+	xkKeypadDelete   = 0xff9f
+	xkKeypadEqual    = 0xffbd
+	xkKeypadMultiply = 0xffaa
+	xkKeypadAdd      = 0xffab
+	xkKeypadSubtract = 0xffad
+	xkKeypadDecimal  = 0xffae
+	xkKeypadDivide   = 0xffaf
+	xkKeypad0        = 0xffb0
+	xkKeypad1        = 0xffb1
+	xkKeypad2        = 0xffb2
+	xkKeypad3        = 0xffb3
+	xkKeypad4        = 0xffb4
+	xkKeypad5        = 0xffb5
+	xkKeypad6        = 0xffb6
+	xkKeypad7        = 0xffb7
+	xkKeypad8        = 0xffb8
+	xkKeypad9        = 0xffb9
+
+	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
+	xkCapsLock = 0xffe5
+	xkAltL     = 0xffe9
+	xkAltR     = 0xffea
+	xkSuperL   = 0xffeb
+	xkSuperR   = 0xffec
+	xkDelete   = 0xffff
 
 	xf86xkAudioLowerVolume = 0x1008ff11
 	xf86xkAudioMute        = 0x1008ff12
@@ -153,6 +190,18 @@
 	xkNumLock:    key.CodeKeypadNumLock,
 	xkMultiKey:   key.CodeCompose,
 
+	xkKeypadEnter:    key.CodeKeypadEnter,
+	xkKeypadHome:     key.CodeHome,
+	xkKeypadLeft:     key.CodeLeftArrow,
+	xkKeypadUp:       key.CodeUpArrow,
+	xkKeypadRight:    key.CodeRightArrow,
+	xkKeypadDown:     key.CodeDownArrow,
+	xkKeypadPageUp:   key.CodePageUp,
+	xkKeypadPageDown: key.CodePageDown,
+	xkKeypadEnd:      key.CodeEnd,
+	xkKeypadInsert:   key.CodeInsert,
+	xkKeypadDelete:   key.CodeDeleteForward,
+
 	xkF1:  key.CodeF1,
 	xkF2:  key.CodeF2,
 	xkF3:  key.CodeF3,
@@ -235,7 +284,28 @@
 	',':  key.CodeComma,
 	'.':  key.CodeFullStop,
 	'/':  key.CodeSlash,
+}
 
-	// TODO: distinguish CodeKeypadSlash vs CodeSlash, and similarly for other
-	// keypad codes.
+type keypadKeysym struct {
+	rune rune
+	code key.Code
+}
+
+var keypadKeysyms = map[rune]keypadKeysym{
+	xkKeypadEqual:    {'=', key.CodeKeypadEqualSign},
+	xkKeypadMultiply: {'*', key.CodeKeypadAsterisk},
+	xkKeypadAdd:      {'+', key.CodeKeypadPlusSign},
+	xkKeypadSubtract: {'-', key.CodeKeypadHyphenMinus},
+	xkKeypadDecimal:  {'.', key.CodeKeypadFullStop},
+	xkKeypadDivide:   {'/', key.CodeKeypadSlash},
+	xkKeypad0:        {'0', key.CodeKeypad0},
+	xkKeypad1:        {'1', key.CodeKeypad1},
+	xkKeypad2:        {'2', key.CodeKeypad2},
+	xkKeypad3:        {'3', key.CodeKeypad3},
+	xkKeypad4:        {'4', key.CodeKeypad4},
+	xkKeypad5:        {'5', key.CodeKeypad5},
+	xkKeypad6:        {'6', key.CodeKeypad6},
+	xkKeypad7:        {'7', key.CodeKeypad7},
+	xkKeypad8:        {'8', key.CodeKeypad8},
+	xkKeypad9:        {'9', key.CodeKeypad9},
 }
diff --git a/shiny/driver/x11driver/screen.go b/shiny/driver/x11driver/screen.go
index 755c5a6..336d73f 100644
--- a/shiny/driver/x11driver/screen.go
+++ b/shiny/driver/x11driver/screen.go
@@ -33,6 +33,8 @@
 	xsi     *xproto.ScreenInfo
 	keysyms x11key.KeysymTable
 
+	numLockMod uint16
+
 	atomNETWMName      xproto.Atom
 	atomUTF8String     xproto.Atom
 	atomWMDeleteWindow xproto.Atom
@@ -483,6 +485,21 @@
 		s.keysyms[i][0] = uint32(km.Keysyms[(i-keyLo)*n+0])
 		s.keysyms[i][1] = uint32(km.Keysyms[(i-keyLo)*n+1])
 	}
+
+	// Figure out which modifier is the numlock modifier (see chapter 12.7 of the XLib Manual).
+	mm, err := xproto.GetModifierMapping(s.xc).Reply()
+	if err != nil {
+		return err
+	}
+	for modifier := 0; modifier < 8; modifier++ {
+		for i := 0; i < int(mm.KeycodesPerModifier); i++ {
+			const xkNumLock = 0xff7f // XK_Num_Lock from /usr/include/X11/keysymdef.h.
+			if s.keysyms[mm.Keycodes[modifier*int(mm.KeycodesPerModifier)+i]][0] == xkNumLock {
+				s.numLockMod = 1 << uint(modifier)
+				break
+			}
+		}
+	}
 	return nil
 }
 
diff --git a/shiny/driver/x11driver/window.go b/shiny/driver/x11driver/window.go
index 5ebdbcd..cf0c6fa 100644
--- a/shiny/driver/x11driver/window.go
+++ b/shiny/driver/x11driver/window.go
@@ -131,7 +131,7 @@
 }
 
 func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) {
-	r, c := w.s.keysyms.Lookup(uint8(detail), state)
+	r, c := w.s.keysyms.Lookup(uint8(detail), state, w.s.numLockMod)
 	w.Send(key.Event{
 		Rune:      r,
 		Code:      c,