shiny: support AltGr under X11

Supports key combinations involving AltGr.

Change-Id: Ib431d31dad9493b7ced7192e4b5157b6a2dfcd40
Reviewed-on: https://go-review.googlesource.com/c/exp/+/192778
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/shiny/driver/gldriver/x11.go b/shiny/driver/gldriver/x11.go
index f73098d..7aaeaff 100644
--- a/shiny/driver/gldriver/x11.go
+++ b/shiny/driver/gldriver/x11.go
@@ -194,8 +194,8 @@
 
 //export onKeysym
 func onKeysym(k, unshifted, shifted uint32) {
-	theKeysyms[k][0] = unshifted
-	theKeysyms[k][1] = shifted
+	theKeysyms.Table[k][0] = unshifted
+	theKeysyms.Table[k][1] = shifted
 }
 
 //export onKey
@@ -208,7 +208,7 @@
 		return
 	}
 
-	r, c := theKeysyms.Lookup(detail, state, 0)
+	r, c := theKeysyms.Lookup(detail, state)
 	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 b916a44..546d463 100644
--- a/shiny/driver/internal/x11key/x11key.go
+++ b/shiny/driver/internal/x11key/x11key.go
@@ -30,18 +30,30 @@
 	Button5Mask = 1 << 12
 )
 
-type KeysymTable [256][2]uint32
+type KeysymTable struct {
+	Table [256][6]uint32
 
-func (t *KeysymTable) Lookup(detail uint8, state uint16, numLockMod uint16) (rune, key.Code) {
+	NumLockMod, ModeSwitchMod, ISOLevel3ShiftMod uint16
+}
+
+func (t *KeysymTable) Lookup(detail uint8, state uint16) (rune, key.Code) {
+	te := t.Table[detail][0:2]
+	if state&t.ModeSwitchMod != 0 {
+		te = t.Table[detail][2:4]
+	}
+	if state&t.ISOLevel3ShiftMod != 0 {
+		te = t.Table[detail][4:6]
+	}
+
 	// The key event's rune depends on whether the shift key is down.
-	unshifted := rune(t[detail][0])
+	unshifted := rune(te[0])
 	r := unshifted
-	if state&numLockMod != 0 && isKeypad(t[detail][1]) {
+	if state&t.NumLockMod != 0 && isKeypad(te[1]) {
 		if state&ShiftMask == 0 {
-			r = rune(t[detail][1])
+			r = rune(te[1])
 		}
 	} else if state&ShiftMask != 0 {
-		r = rune(t[detail][1])
+		r = rune(te[1])
 		// In X11, a zero keysym when shift is down means to use what the
 		// keysym is when shift is up.
 		if r == 0 {
diff --git a/shiny/driver/x11driver/screen.go b/shiny/driver/x11driver/screen.go
index c734797..7e0d3bb 100644
--- a/shiny/driver/x11driver/screen.go
+++ b/shiny/driver/x11driver/screen.go
@@ -33,8 +33,6 @@
 	xsi     *xproto.ScreenInfo
 	keysyms x11key.KeysymTable
 
-	numLockMod uint16
-
 	atomNETWMName      xproto.Atom
 	atomUTF8String     xproto.Atom
 	atomWMDeleteWindow xproto.Atom
@@ -496,8 +494,13 @@
 		return fmt.Errorf("x11driver: too few keysyms per keycode: %d", n)
 	}
 	for i := keyLo; i <= keyHi; i++ {
-		s.keysyms[i][0] = uint32(km.Keysyms[(i-keyLo)*n+0])
-		s.keysyms[i][1] = uint32(km.Keysyms[(i-keyLo)*n+1])
+		for j := 0; j < 6; j++ {
+			if j < n {
+				s.keysyms.Table[i][j] = uint32(km.Keysyms[(i-keyLo)*n+j])
+			} else {
+				s.keysyms.Table[i][j] = 0
+			}
+		}
 	}
 
 	// Figure out which modifier is the numlock modifier (see chapter 12.7 of the XLib Manual).
@@ -505,12 +508,30 @@
 	if err != nil {
 		return err
 	}
+	s.keysyms.NumLockMod, s.keysyms.ModeSwitchMod, s.keysyms.ISOLevel3ShiftMod = 0, 0, 0
+	numLockFound, modeSwitchFound, isoLevel3ShiftFound := false, false, false
+modifierSearchLoop:
 	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
+			const (
+				// XK_Num_Lock, XK_Mode_switch and XK_ISO_Level3_Shift from /usr/include/X11/keysymdef.h.
+				xkNumLock        = 0xff7f
+				xkModeSwitch     = 0xff7e
+				xkISOLevel3Shift = 0xfe03
+			)
+			switch s.keysyms.Table[mm.Keycodes[modifier*int(mm.KeycodesPerModifier)+i]][0] {
+			case xkNumLock:
+				s.keysyms.NumLockMod = 1 << uint(modifier)
+				numLockFound = true
+			case xkModeSwitch:
+				s.keysyms.ModeSwitchMod = 1 << uint(modifier)
+				modeSwitchFound = true
+			case xkISOLevel3Shift:
+				s.keysyms.ISOLevel3ShiftMod = 1 << uint(modifier)
+				isoLevel3ShiftFound = true
+			}
+			if numLockFound && modeSwitchFound && isoLevel3ShiftFound {
+				break modifierSearchLoop
 			}
 		}
 	}
diff --git a/shiny/driver/x11driver/window.go b/shiny/driver/x11driver/window.go
index cf0c6fa..5ebdbcd 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, w.s.numLockMod)
+	r, c := w.s.keysyms.Lookup(uint8(detail), state)
 	w.Send(key.Event{
 		Rune:      r,
 		Code:      c,