aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p@janouch.name>2018-08-24 20:45:16 +0200
committerPřemysl Janouch <p@janouch.name>2018-09-02 18:25:37 +0200
commitff7518c74dd5ae14e41910f713ecb194965b3f17 (patch)
tree22411671e17d2a206dec3416867d8cd9d7ecbb8f
parent9e070e964851544d0c2840e8d123a71c80772e80 (diff)
downloadhaven-ff7518c74dd5ae14e41910f713ecb194965b3f17.tar.gz
haven-ff7518c74dd5ae14e41910f713ecb194965b3f17.tar.xz
haven-ff7518c74dd5ae14e41910f713ecb194965b3f17.zip
xgb-keys: minimal example of reading keys
-rw-r--r--prototypes/xgb-keys.go213
1 files changed, 213 insertions, 0 deletions
diff --git a/prototypes/xgb-keys.go b/prototypes/xgb-keys.go
new file mode 100644
index 0000000..578ab39
--- /dev/null
+++ b/prototypes/xgb-keys.go
@@ -0,0 +1,213 @@
+package main
+
+import (
+ "github.com/BurntSushi/xgb"
+ //"github.com/BurntSushi/xgb/xkb"
+ "github.com/BurntSushi/xgb/xproto"
+ "log"
+)
+
+func main() {
+ X, err := xgb.NewConn()
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ /*
+ // Use the extension if available, makes better use of state bits.
+ if err := xkb.Init(X); err == nil {
+ if _, err := xkb.UseExtension(X, 1, 0).Reply(); err != nil {
+ log.Fatalln(err)
+ }
+ }
+ */
+
+ setup := xproto.Setup(X)
+ screen := setup.DefaultScreen(X)
+
+ visual, depth := screen.RootVisual, screen.RootDepth
+ // TODO: We should check that we find it, though we don't /need/ alpha here,
+ // it's just a minor improvement--affects the backpixel value.
+ for _, i := range screen.AllowedDepths {
+ for _, v := range i.Visuals {
+ // TODO: Could/should check other parameters.
+ if i.Depth == 32 && v.Class == xproto.VisualClassTrueColor {
+ visual, depth = v.VisualId, i.Depth
+ break
+ }
+ }
+ }
+
+ mid, err := xproto.NewColormapId(X)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ _ = xproto.CreateColormap(
+ X, xproto.ColormapAllocNone, mid, screen.Root, visual)
+
+ wid, err := xproto.NewWindowId(X)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // Border pixel and colormap are required when depth differs from parent.
+ _ = xproto.CreateWindow(X, depth, wid, screen.Root,
+ 0, 0, 500, 500, 0, xproto.WindowClassInputOutput,
+ visual, xproto.CwBackPixel|xproto.CwBorderPixel|xproto.CwEventMask|
+ xproto.CwColormap, []uint32{0x80808080, 0,
+ xproto.EventMaskStructureNotify | xproto.EventMaskKeyPress |
+ /* KeymapNotify */ xproto.EventMaskKeymapState, uint32(mid)})
+
+ title := []byte("Keys")
+ _ = xproto.ChangeProperty(X, xproto.PropModeReplace, wid, xproto.AtomWmName,
+ xproto.AtomString, 8, uint32(len(title)), title)
+
+ _ = xproto.MapWindow(X, wid)
+
+ mapping, err := xproto.GetKeyboardMapping(X, setup.MinKeycode,
+ byte(setup.MaxKeycode-setup.MinKeycode+1)).Reply()
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // The order is "Shift, Lock, Control, Mod1, Mod2, Mod3, Mod4, and Mod5."
+ mm, err := xproto.GetModifierMapping(X).Reply()
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // XXX: This seems pointless, the key will just end up switching groups
+ // instead of levels without full XKB handling. Though perhaps it might
+ // at least work as intended when there's only one XKB group.
+ const MODE_SWITCH = 0xff7e
+
+ var modeSwitchMask uint16
+ for mod := 0; mod < 8; mod++ {
+ perMod := int(mm.KeycodesPerModifier)
+ for _, kc := range mm.Keycodes[mod*perMod : (mod+1)*perMod] {
+ if kc == 0 {
+ continue
+ }
+
+ perKc := int(mapping.KeysymsPerKeycode)
+ k := int(kc - setup.MinKeycode)
+ for _, ks := range mapping.Keysyms[k*perKc : (k+1)*perKc] {
+ if ks == MODE_SWITCH {
+ modeSwitchMask |= 1 << uint(mod)
+ }
+ }
+ }
+ }
+
+ for {
+ ev, xerr := X.WaitForEvent()
+ if xerr != nil {
+ log.Printf("Error: %s\n", xerr)
+ return
+ }
+ if ev == nil {
+ return
+ }
+
+ log.Printf("Event: %s\n", ev)
+ switch e := ev.(type) {
+ case xproto.UnmapNotifyEvent:
+ return
+
+ case xproto.KeymapNotifyEvent:
+ // e.Keys is a 32 * 8 large bitmap indicating which keys are
+ // currently pressed down. This is sent "after every EnterNotify
+ // and FocusIn" but it also seems to fire when the keyboard layout
+ // changes. aixterm manual even speaks of that explicitly.
+ //
+ // But since changing the effective group involves no changes to
+ // the compatibility mapping, there's nothing for us to do.
+
+ case xproto.MappingNotifyEvent:
+ // e.FirstKeyCode .. e.Count changes have happened but rereading
+ // everything is the simpler thing to do.
+ mapping, err = xproto.GetKeyboardMapping(X, setup.MinKeycode,
+ byte(setup.MaxKeycode-setup.MinKeycode+1)).Reply()
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // TODO: We should also repeat the search for MODE SWITCH.
+
+ case xproto.KeyPressEvent:
+ step := int(mapping.KeysymsPerKeycode)
+ from := int(e.Detail-setup.MinKeycode) * step
+ ks := mapping.Keysyms[from : from+step]
+
+ // Strip trailing NoSymbol entries.
+ for len(ks) > 0 && ks[len(ks)-1] == 0 {
+ ks = ks[:len(ks)-1]
+ }
+
+ // Expand back to at least 4.
+ switch {
+ case len(ks) == 1:
+ ks = append(ks, 0, ks[0], 0)
+ case len(ks) == 2:
+ ks = append(ks, ks[0], ks[1])
+ case len(ks) == 3:
+ ks = append(ks, 0)
+ }
+
+ // Other silly expansion rules, only applied to basic ASCII.
+ if ks[1] == 0 {
+ ks[1] = ks[0]
+ if ks[0] >= 'A' && ks[0] <= 'Z' ||
+ ks[0] >= 'a' && ks[0] <= 'z' {
+ ks[0] = ks[0] | 32
+ ks[1] = ks[0] &^ 32
+ }
+ }
+
+ if ks[3] == 0 {
+ ks[3] = ks[2]
+ if ks[2] >= 'A' && ks[2] <= 'Z' ||
+ ks[2] >= 'a' && ks[2] <= 'z' {
+ ks[2] = ks[2] | 32
+ ks[3] = ks[2] &^ 32
+ }
+ }
+
+ // We only have enough information to switch between two groups.
+ offset := 0
+ if e.State&modeSwitchMask != 0 {
+ offset += 2
+ }
+
+ var result xproto.Keysym
+
+ shift := e.State&xproto.ModMaskShift != 0
+ lock := e.State&xproto.ModMaskLock != 0
+ switch {
+ case !shift && !lock:
+ result = ks[offset+0]
+ case !shift && lock:
+ if ks[offset+0] >= 'a' && ks[offset+0] <= 'z' {
+ result = ks[offset+1]
+ } else {
+ result = ks[offset+0]
+ }
+ case shift && lock:
+ if ks[offset+1] >= 'a' && ks[offset+1] <= 'z' {
+ result = ks[offset+1] &^ 32
+ } else {
+ result = ks[offset+1]
+ }
+ case shift:
+ result = ks[offset+1]
+ }
+
+ if result <= 0xff {
+ log.Printf("%c (Latin-1)\n", rune(result))
+ } else {
+ log.Println(result)
+ }
+ }
+ }
+}