Skip to content

Commit

Permalink
feat(keyboard): keyboard state/layout from events
Browse files Browse the repository at this point in the history
  • Loading branch information
maitredede committed Mar 6, 2024
1 parent e40ebd2 commit 214338e
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 4 deletions.
37 changes: 33 additions & 4 deletions cmd/evtest/main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package main

import (
"flag"
"fmt"
"os"

"github.com/holoplot/go-evdev"
"github.com/holoplot/go-evdev/kbext"
)

func listDevices() {
Expand All @@ -18,18 +20,36 @@ func listDevices() {
}
}

func listLayouts() {
ids := kbext.LayoutKeys()
for _, d := range ids {
fmt.Println(d)
}
}

var (
kbLayoutStr string
)

func main() {
if len(os.Args) < 2 {
flag.StringVar(&kbLayoutStr, "kblayout", string(kbext.LayoutQuertyEnUs), "Keyboard layout")
flag.Parse()

args := flag.Args()
if len(args) < 1 {
fmt.Printf("Usage: %s <input device>\n\n", os.Args[0])
fmt.Printf("Available devices:\n")

listDevices()

fmt.Printf("\nAvailable keyboard layouts:\n")
listLayouts()
return
}

d, err := evdev.Open(os.Args[1])
d, err := evdev.Open(args[0])
if err != nil {
fmt.Printf("Cannot read %s: %v\n", os.Args[1], err)
fmt.Printf("Cannot read %s: %v\n", args[0], err)
return
}

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

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

for {
e, err := d.ReadOne()
Expand All @@ -125,6 +146,14 @@ func main() {
default:
fmt.Printf("%s, -------------- %s ------------\n", ts, e.CodeName())
}
case evdev.EV_KEY:
kbevt := evdev.NewKeyEvent(e)
fmt.Printf("%s, %s\n", ts, kbevt.String())

pressed, err := kbState.KeyEvent(kbevt)
if err == nil {
fmt.Printf(" kb char: %s\n", pressed)
}
default:
fmt.Printf("%s, %s\n", ts, e.String())
}
Expand Down
1 change: 1 addition & 0 deletions kbext/azerty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package kbext
7 changes: 7 additions & 0 deletions kbext/keymap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kbext

type keymap struct {
plain string
altgr string
shift string
}
23 changes: 23 additions & 0 deletions kbext/layouts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kbext

import evdev "github.com/holoplot/go-evdev"

type LayoutID string

var (
LayoutQuertyEnUs LayoutID = "querty-en-US"
)

type layout map[evdev.EvCode]keymap

var layouts = map[LayoutID]layout{
LayoutQuertyEnUs: quertyEnUs,
}

func LayoutKeys() []LayoutID {
r := make([]LayoutID, 0, len(layouts))
for k := range layouts {
r = append(r, k)
}
return r
}
84 changes: 84 additions & 0 deletions kbext/querty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package kbext

import evdev "github.com/holoplot/go-evdev"

var (
// LayoutQuertyEnUs LayoutID = "querty-en-US"

quertyEnUs = map[evdev.EvCode]keymap{
evdev.KEY_NUMERIC_STAR: {plain: "*", altgr: "*", shift: "*"},
evdev.KEY_NUMERIC_3: {plain: "3", altgr: "3", shift: "3"},
evdev.KEY_NUMERIC_2: {plain: "2", altgr: "2", shift: "2"},
evdev.KEY_NUMERIC_5: {plain: "5", altgr: "5", shift: "5"},
evdev.KEY_NUMERIC_4: {plain: "4", altgr: "4", shift: "4"},
evdev.KEY_NUMERIC_7: {plain: "7", altgr: "7", shift: "7"},
evdev.KEY_NUMERIC_6: {plain: "6", altgr: "6", shift: "6"},
evdev.KEY_NUMERIC_9: {plain: "9", altgr: "9", shift: "9"},
evdev.KEY_NUMERIC_8: {plain: "8", altgr: "8", shift: "8"},
evdev.KEY_NUMERIC_1: {plain: "1", altgr: "1", shift: "1"},
evdev.KEY_NUMERIC_0: {plain: "0", altgr: "0", shift: "0"},

evdev.KEY_KP4: {plain: "4", altgr: "4", shift: "4"},
evdev.KEY_KP5: {plain: "5", altgr: "5", shift: "5"},
evdev.KEY_KP6: {plain: "6", altgr: "6", shift: "6"},
evdev.KEY_KP7: {plain: "7", altgr: "7", shift: "7"},
evdev.KEY_KP0: {plain: "0", altgr: "0", shift: "0"},
evdev.KEY_KP1: {plain: "1", altgr: "1", shift: "1"},
evdev.KEY_KP2: {plain: "2", altgr: "2", shift: "2"},
evdev.KEY_KP3: {plain: "3", altgr: "3", shift: "3"},
evdev.KEY_KP8: {plain: "8", altgr: "8", shift: "8"},
evdev.KEY_KP9: {plain: "9", altgr: "9", shift: "9"},

evdev.KEY_U: {plain: "u", altgr: "u", shift: "U"},
evdev.KEY_W: {plain: "w", altgr: "w", shift: "W"},
evdev.KEY_E: {plain: "e", altgr: "e", shift: "E"},
evdev.KEY_D: {plain: "d", altgr: "d", shift: "D"},
evdev.KEY_G: {plain: "g", altgr: "g", shift: "G"},
evdev.KEY_F: {plain: "f", altgr: "f", shift: "F"},
evdev.KEY_A: {plain: "a", altgr: "a", shift: "A"},
evdev.KEY_C: {plain: "c", altgr: "c", shift: "C"},
evdev.KEY_B: {plain: "b", altgr: "b", shift: "B"},
evdev.KEY_M: {plain: "m", altgr: "m", shift: "M"},
evdev.KEY_L: {plain: "l", altgr: "l", shift: "L"},
evdev.KEY_O: {plain: "o", altgr: "o", shift: "O"},
evdev.KEY_N: {plain: "n", altgr: "n", shift: "N"},
evdev.KEY_I: {plain: "i", altgr: "i", shift: "I"},
evdev.KEY_H: {plain: "h", altgr: "h", shift: "H"},
evdev.KEY_K: {plain: "k", altgr: "k", shift: "K"},
evdev.KEY_J: {plain: "j", altgr: "j", shift: "J"},
evdev.KEY_Q: {plain: "q", altgr: "q", shift: "Q"},
evdev.KEY_P: {plain: "p", altgr: "p", shift: "P"},
evdev.KEY_S: {plain: "s", altgr: "s", shift: "S"},
evdev.KEY_X: {plain: "x", altgr: "x", shift: "X"},
evdev.KEY_Z: {plain: "z", altgr: "z", shift: "Z"},
evdev.KEY_T: {plain: "t", altgr: "t", shift: "T"},
evdev.KEY_V: {plain: "v", altgr: "v", shift: "V"},
evdev.KEY_R: {plain: "r", altgr: "r", shift: "R"},
evdev.KEY_Y: {plain: "y", altgr: "y", shift: "Y"},

evdev.KEY_1: {plain: "1", altgr: "~", shift: "!"},
evdev.KEY_2: {plain: "2", altgr: "2", shift: "@"},
evdev.KEY_3: {plain: "3", altgr: "^", shift: "#"},
evdev.KEY_4: {plain: "4", altgr: "4", shift: "$"},
evdev.KEY_5: {plain: "5", altgr: "5", shift: "%"},
evdev.KEY_6: {plain: "6", altgr: "6", shift: "^"},
evdev.KEY_7: {plain: "7", altgr: "7", shift: "&"},
evdev.KEY_8: {plain: "8", altgr: "8", shift: "*"},
evdev.KEY_9: {plain: "9", altgr: "9", shift: "("},
evdev.KEY_0: {plain: "0", altgr: "0", shift: ")"},

evdev.KEY_BACKSLASH: {plain: "\\", altgr: "\\", shift: "|"},
evdev.KEY_TAB: {plain: "\t", altgr: "\t", shift: "\t"},
evdev.KEY_MINUS: {plain: "-", altgr: "-", shift: "-"},
evdev.KEY_SPACE: {plain: " ", altgr: " ", shift: " "},
evdev.KEY_GRAVE: {plain: "`", altgr: "`", shift: "`"},
evdev.KEY_LEFTBRACE: {plain: "[", altgr: "[", shift: "["},
evdev.KEY_RIGHTBRACE: {plain: "]", altgr: "]", shift: "]"},
evdev.KEY_COMMA: {plain: ",", altgr: ",", shift: ","},
evdev.KEY_EQUAL: {plain: "=", altgr: "=", shift: "="},
evdev.KEY_SEMICOLON: {plain: ";", altgr: ";", shift: ";"},
evdev.KEY_APOSTROPHE: {plain: "'", altgr: "'", shift: "'"},
evdev.KEY_DOT: {plain: ".", altgr: ".", shift: "."},
evdev.KEY_SLASH: {plain: "/", altgr: "/", shift: "/"},
}
)
85 changes: 85 additions & 0 deletions kbext/resolve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package kbext

import (
"errors"
"log"

evdev "github.com/holoplot/go-evdev"
)

type KbState struct {
layout LayoutID
shift bool
altgr bool
}

var (
ErrNotACharKey = errors.New("last event was not a printable char key down")
ErrNotHandled = errors.New("event not handled")
ErrUnknwownLayout = errors.New("unknown layout")
)

func NewKbState(layout LayoutID) KbState {
return KbState{
layout: layout,
shift: false,
altgr: false,
}
}

func (kb *KbState) KeyEvent(kbEvt *evdev.KeyEvent) (string, error) {
switch kbEvt.State {
case evdev.KeyUp:
_, err := kb.handleKey(kbEvt, false)
if err == nil {
err = ErrNotACharKey
}
return "", err
case evdev.KeyDown:
return kb.handleKey(kbEvt, true)
}
return "", ErrNotHandled
}

func (kb *KbState) handleKey(kbEvt *evdev.KeyEvent, down bool) (string, error) {
switch kbEvt.Scancode {
case evdev.KEY_LEFTSHIFT:
fallthrough
case evdev.KEY_RIGHTSHIFT:
kb.shift = down
return "", ErrNotACharKey

case evdev.KEY_ENTER:
fallthrough
case evdev.KEY_KPENTER:
return "\n", nil
}

layout, ok := layouts[kb.layout]
if !ok {
return "", ErrUnknwownLayout
}

keyinfo, ok := layout[kbEvt.Scancode]
if !ok {
return "", errors.New("KeyInfo not found")
}

if kb.altgr {
if keyinfo.plain != keyinfo.altgr {
return keyinfo.altgr, nil
}
keyName := evdev.KEYToString[kbEvt.Scancode]
log.Printf("TODO : altgr mapping for %v", keyName)
return keyinfo.altgr, nil
}
if kb.shift {
if keyinfo.plain != keyinfo.shift {
return keyinfo.shift, nil
}
keyName := evdev.KEYToString[kbEvt.Scancode]
log.Printf("TODO : shift mapping for %v", keyName)
return keyinfo.shift, nil
}
return keyinfo.plain, nil
}
58 changes: 58 additions & 0 deletions keyevent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package evdev

import (
"fmt"
)

type KeyEventState uint8

const (
KeyUp KeyEventState = 0x0
KeyDown KeyEventState = 0x1
KeyHold KeyEventState = 0x2
)

// KeyEvents are used to describe state changes of keyboards, buttons,
// or other key-like devices.
type KeyEvent struct {
Event *InputEvent
Scancode EvCode
Keycode uint16
State KeyEventState
}

func (kev *KeyEvent) New(ev *InputEvent) {
kev.Event = ev
kev.Keycode = 0 // :todo
kev.Scancode = ev.Code

switch ev.Value {
case 0:
kev.State = KeyUp
case 2:
kev.State = KeyHold
case 1:
kev.State = KeyDown
}
}

func NewKeyEvent(ev *InputEvent) *KeyEvent {
kev := &KeyEvent{}
kev.New(ev)
return kev
}

func (ev *KeyEvent) String() string {
state := "unknown"

switch ev.State {
case KeyUp:
state = "up"
case KeyHold:
state = "hold"
case KeyDown:
state = "down"
}

return fmt.Sprintf("key event %d (%d), (%s)", ev.Scancode, ev.Event.Code, state)
}

0 comments on commit 214338e

Please sign in to comment.