Skip to content

Commit ae71ac9

Browse files
committed
feat(keyboard): keyboard state/layout from events
1 parent 217e18f commit ae71ac9

File tree

7 files changed

+291
-4
lines changed

7 files changed

+291
-4
lines changed

cmd/evtest/main.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package main
22

33
import (
4+
"flag"
45
"fmt"
56
"os"
67

78
"github.com/holoplot/go-evdev"
9+
"github.com/holoplot/go-evdev/kbext"
810
)
911

1012
func listDevices() {
@@ -18,18 +20,36 @@ func listDevices() {
1820
}
1921
}
2022

23+
func listLayouts() {
24+
ids := kbext.LayoutKeys()
25+
for _, d := range ids {
26+
fmt.Println(d)
27+
}
28+
}
29+
30+
var (
31+
kbLayoutStr string
32+
)
33+
2134
func main() {
22-
if len(os.Args) < 2 {
35+
flag.StringVar(&kbLayoutStr, "kblayout", string(kbext.LayoutQuertyEnUs), "Keyboard layout")
36+
flag.Parse()
37+
38+
args := flag.Args()
39+
if len(args) < 1 {
2340
fmt.Printf("Usage: %s <input device>\n\n", os.Args[0])
2441
fmt.Printf("Available devices:\n")
2542

2643
listDevices()
44+
45+
fmt.Printf("\nAvailable keyboard layouts:\n")
46+
listLayouts()
2747
return
2848
}
2949

30-
d, err := evdev.Open(os.Args[1])
50+
d, err := evdev.Open(args[0])
3151
if err != nil {
32-
fmt.Printf("Cannot read %s: %v\n", os.Args[1], err)
52+
fmt.Printf("Cannot read %s: %v\n", args[0], err)
3353
return
3454
}
3555

@@ -104,7 +124,8 @@ func main() {
104124
fmt.Printf(" Property type %d (%s)\n", p, evdev.PropName(p))
105125
}
106126

107-
fmt.Printf("Testing ... (interrupt to exit)\n")
127+
fmt.Printf("Testing (kb layout=%s)... (interrupt to exit)\n", kbLayoutStr)
128+
kbState := kbext.NewKbState(kbext.LayoutID(kbLayoutStr))
108129

109130
for {
110131
e, err := d.ReadOne()
@@ -125,6 +146,14 @@ func main() {
125146
default:
126147
fmt.Printf("%s, -------------- %s ------------\n", ts, e.CodeName())
127148
}
149+
case evdev.EV_KEY:
150+
kbevt := evdev.NewKeyEvent(e)
151+
fmt.Printf("%s, %s\n", ts, kbevt.String())
152+
153+
pressed, err := kbState.KeyEvent(kbevt)
154+
if err == nil {
155+
fmt.Printf(" kb char: %s\n", pressed)
156+
}
128157
default:
129158
fmt.Printf("%s, %s\n", ts, e.String())
130159
}

kbext/azerty.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package kbext

kbext/keymap.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package kbext
2+
3+
type keymap struct {
4+
plain string
5+
altgr string
6+
shift string
7+
}

kbext/layouts.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package kbext
2+
3+
import evdev "github.com/holoplot/go-evdev"
4+
5+
type LayoutID string
6+
7+
var (
8+
LayoutQuertyEnUs LayoutID = "querty-en-US"
9+
)
10+
11+
type layout map[evdev.EvCode]keymap
12+
13+
var layouts = map[LayoutID]layout{
14+
LayoutQuertyEnUs: quertyEnUs,
15+
}
16+
17+
func LayoutKeys() []LayoutID {
18+
r := make([]LayoutID, 0, len(layouts))
19+
for k := range layouts {
20+
r = append(r, k)
21+
}
22+
return r
23+
}

kbext/querty.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package kbext
2+
3+
import evdev "github.com/holoplot/go-evdev"
4+
5+
var (
6+
// LayoutQuertyEnUs LayoutID = "querty-en-US"
7+
8+
quertyEnUs = map[evdev.EvCode]keymap{
9+
evdev.KEY_NUMERIC_STAR: {plain: "*", altgr: "*", shift: "*"},
10+
evdev.KEY_NUMERIC_3: {plain: "3", altgr: "3", shift: "3"},
11+
evdev.KEY_NUMERIC_2: {plain: "2", altgr: "2", shift: "2"},
12+
evdev.KEY_NUMERIC_5: {plain: "5", altgr: "5", shift: "5"},
13+
evdev.KEY_NUMERIC_4: {plain: "4", altgr: "4", shift: "4"},
14+
evdev.KEY_NUMERIC_7: {plain: "7", altgr: "7", shift: "7"},
15+
evdev.KEY_NUMERIC_6: {plain: "6", altgr: "6", shift: "6"},
16+
evdev.KEY_NUMERIC_9: {plain: "9", altgr: "9", shift: "9"},
17+
evdev.KEY_NUMERIC_8: {plain: "8", altgr: "8", shift: "8"},
18+
evdev.KEY_NUMERIC_1: {plain: "1", altgr: "1", shift: "1"},
19+
evdev.KEY_NUMERIC_0: {plain: "0", altgr: "0", shift: "0"},
20+
21+
evdev.KEY_KP4: {plain: "4", altgr: "4", shift: "4"},
22+
evdev.KEY_KP5: {plain: "5", altgr: "5", shift: "5"},
23+
evdev.KEY_KP6: {plain: "6", altgr: "6", shift: "6"},
24+
evdev.KEY_KP7: {plain: "7", altgr: "7", shift: "7"},
25+
evdev.KEY_KP0: {plain: "0", altgr: "0", shift: "0"},
26+
evdev.KEY_KP1: {plain: "1", altgr: "1", shift: "1"},
27+
evdev.KEY_KP2: {plain: "2", altgr: "2", shift: "2"},
28+
evdev.KEY_KP3: {plain: "3", altgr: "3", shift: "3"},
29+
evdev.KEY_KP8: {plain: "8", altgr: "8", shift: "8"},
30+
evdev.KEY_KP9: {plain: "9", altgr: "9", shift: "9"},
31+
32+
evdev.KEY_U: {plain: "u", altgr: "u", shift: "U"},
33+
evdev.KEY_W: {plain: "w", altgr: "w", shift: "W"},
34+
evdev.KEY_E: {plain: "e", altgr: "e", shift: "E"},
35+
evdev.KEY_D: {plain: "d", altgr: "d", shift: "D"},
36+
evdev.KEY_G: {plain: "g", altgr: "g", shift: "G"},
37+
evdev.KEY_F: {plain: "f", altgr: "f", shift: "F"},
38+
evdev.KEY_A: {plain: "a", altgr: "a", shift: "A"},
39+
evdev.KEY_C: {plain: "c", altgr: "c", shift: "C"},
40+
evdev.KEY_B: {plain: "b", altgr: "b", shift: "B"},
41+
evdev.KEY_M: {plain: "m", altgr: "m", shift: "M"},
42+
evdev.KEY_L: {plain: "l", altgr: "l", shift: "L"},
43+
evdev.KEY_O: {plain: "o", altgr: "o", shift: "O"},
44+
evdev.KEY_N: {plain: "n", altgr: "n", shift: "N"},
45+
evdev.KEY_I: {plain: "i", altgr: "i", shift: "I"},
46+
evdev.KEY_H: {plain: "h", altgr: "h", shift: "H"},
47+
evdev.KEY_K: {plain: "k", altgr: "k", shift: "K"},
48+
evdev.KEY_J: {plain: "j", altgr: "j", shift: "J"},
49+
evdev.KEY_Q: {plain: "q", altgr: "q", shift: "Q"},
50+
evdev.KEY_P: {plain: "p", altgr: "p", shift: "P"},
51+
evdev.KEY_S: {plain: "s", altgr: "s", shift: "S"},
52+
evdev.KEY_X: {plain: "x", altgr: "x", shift: "X"},
53+
evdev.KEY_Z: {plain: "z", altgr: "z", shift: "Z"},
54+
evdev.KEY_T: {plain: "t", altgr: "t", shift: "T"},
55+
evdev.KEY_V: {plain: "v", altgr: "v", shift: "V"},
56+
evdev.KEY_R: {plain: "r", altgr: "r", shift: "R"},
57+
evdev.KEY_Y: {plain: "y", altgr: "y", shift: "Y"},
58+
59+
evdev.KEY_1: {plain: "1", altgr: "~", shift: "!"},
60+
evdev.KEY_2: {plain: "2", altgr: "2", shift: "@"},
61+
evdev.KEY_3: {plain: "3", altgr: "^", shift: "#"},
62+
evdev.KEY_4: {plain: "4", altgr: "4", shift: "$"},
63+
evdev.KEY_5: {plain: "5", altgr: "5", shift: "%"},
64+
evdev.KEY_6: {plain: "6", altgr: "6", shift: "^"},
65+
evdev.KEY_7: {plain: "7", altgr: "7", shift: "&"},
66+
evdev.KEY_8: {plain: "8", altgr: "8", shift: "*"},
67+
evdev.KEY_9: {plain: "9", altgr: "9", shift: "("},
68+
evdev.KEY_0: {plain: "0", altgr: "0", shift: ")"},
69+
70+
evdev.KEY_BACKSLASH: {plain: "\\", altgr: "\\", shift: "|"},
71+
evdev.KEY_TAB: {plain: "\t", altgr: "\t", shift: "\t"},
72+
evdev.KEY_MINUS: {plain: "-", altgr: "-", shift: "-"},
73+
evdev.KEY_SPACE: {plain: " ", altgr: " ", shift: " "},
74+
evdev.KEY_GRAVE: {plain: "`", altgr: "`", shift: "`"},
75+
evdev.KEY_LEFTBRACE: {plain: "[", altgr: "[", shift: "["},
76+
evdev.KEY_RIGHTBRACE: {plain: "]", altgr: "]", shift: "]"},
77+
evdev.KEY_COMMA: {plain: ",", altgr: ",", shift: ","},
78+
evdev.KEY_EQUAL: {plain: "=", altgr: "=", shift: "="},
79+
evdev.KEY_SEMICOLON: {plain: ";", altgr: ";", shift: ";"},
80+
evdev.KEY_APOSTROPHE: {plain: "'", altgr: "'", shift: "'"},
81+
evdev.KEY_DOT: {plain: ".", altgr: ".", shift: "."},
82+
evdev.KEY_SLASH: {plain: "/", altgr: "/", shift: "/"},
83+
}
84+
)

kbext/resolve.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package kbext
2+
3+
import (
4+
"errors"
5+
"log"
6+
7+
evdev "github.com/holoplot/go-evdev"
8+
)
9+
10+
type KbState struct {
11+
layout LayoutID
12+
shift bool
13+
altgr bool
14+
}
15+
16+
var (
17+
ErrNotACharKey = errors.New("last event was not a printable char key down")
18+
ErrNotHandled = errors.New("event not handled")
19+
ErrUnknwownLayout = errors.New("unknown layout")
20+
)
21+
22+
func NewKbState(layout LayoutID) KbState {
23+
return KbState{
24+
layout: layout,
25+
shift: false,
26+
altgr: false,
27+
}
28+
}
29+
30+
func (kb *KbState) KeyEvent(kbEvt *evdev.KeyEvent) (string, error) {
31+
switch kbEvt.State {
32+
case evdev.KeyUp:
33+
_, err := kb.handleKey(kbEvt, false)
34+
if err == nil {
35+
err = ErrNotACharKey
36+
}
37+
return "", err
38+
case evdev.KeyDown:
39+
return kb.handleKey(kbEvt, true)
40+
}
41+
return "", ErrNotHandled
42+
}
43+
44+
func (kb *KbState) handleKey(kbEvt *evdev.KeyEvent, down bool) (string, error) {
45+
switch kbEvt.Scancode {
46+
case evdev.KEY_LEFTSHIFT:
47+
fallthrough
48+
case evdev.KEY_RIGHTSHIFT:
49+
kb.shift = down
50+
return "", ErrNotACharKey
51+
52+
case evdev.KEY_ENTER:
53+
fallthrough
54+
case evdev.KEY_KPENTER:
55+
return "\n", nil
56+
}
57+
58+
layout, ok := layouts[kb.layout]
59+
if !ok {
60+
return "", ErrUnknwownLayout
61+
}
62+
63+
keyinfo, ok := layout[kbEvt.Scancode]
64+
if !ok {
65+
return "", errors.New("KeyInfo not found")
66+
}
67+
68+
if kb.altgr {
69+
if keyinfo.plain != keyinfo.altgr {
70+
return keyinfo.altgr, nil
71+
}
72+
keyName := evdev.KEYToString[kbEvt.Scancode]
73+
log.Printf("TODO : altgr mapping for %v", keyName)
74+
return keyinfo.altgr, nil
75+
}
76+
if kb.shift {
77+
if keyinfo.plain != keyinfo.shift {
78+
return keyinfo.shift, nil
79+
}
80+
keyName := evdev.KEYToString[kbEvt.Scancode]
81+
log.Printf("TODO : shift mapping for %v", keyName)
82+
return keyinfo.shift, nil
83+
}
84+
return keyinfo.plain, nil
85+
}

keyevent.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package evdev
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
type KeyEventState uint8
8+
9+
const (
10+
KeyUp KeyEventState = 0x0
11+
KeyDown KeyEventState = 0x1
12+
KeyHold KeyEventState = 0x2
13+
)
14+
15+
// KeyEvents are used to describe state changes of keyboards, buttons,
16+
// or other key-like devices.
17+
type KeyEvent struct {
18+
Event *InputEvent
19+
Scancode EvCode
20+
Keycode uint16
21+
State KeyEventState
22+
}
23+
24+
func (kev *KeyEvent) New(ev *InputEvent) {
25+
kev.Event = ev
26+
kev.Keycode = 0 // :todo
27+
kev.Scancode = ev.Code
28+
29+
switch ev.Value {
30+
case 0:
31+
kev.State = KeyUp
32+
case 2:
33+
kev.State = KeyHold
34+
case 1:
35+
kev.State = KeyDown
36+
}
37+
}
38+
39+
func NewKeyEvent(ev *InputEvent) *KeyEvent {
40+
kev := &KeyEvent{}
41+
kev.New(ev)
42+
return kev
43+
}
44+
45+
func (ev *KeyEvent) String() string {
46+
state := "unknown"
47+
48+
switch ev.State {
49+
case KeyUp:
50+
state = "up"
51+
case KeyHold:
52+
state = "hold"
53+
case KeyDown:
54+
state = "down"
55+
}
56+
57+
return fmt.Sprintf("key event %d (%d), (%s)", ev.Scancode, ev.Event.Code, state)
58+
}

0 commit comments

Comments
 (0)