Skip to content

Keyboard state/layout from events #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
13 changes: 9 additions & 4 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,18 @@ type InputMask struct {
CodesPtr uint64
}

// UinputUserDevice is used when creating or cloning a device
type UinputUserDevice struct {
Name [uinputMaxNameSize]byte
ID InputID
// UserDeviceAbsParams specifies the range (min/max), fuzz, and flat values for an absolute input.
type UserDeviceAbsParams struct {
EffectsMax uint32
Absmax [absSize]int32
Absmin [absSize]int32
Absfuzz [absSize]int32
Absflat [absSize]int32
}

// UinputUserDevice is used when creating or cloning a device
type UinputUserDevice struct {
Name [uinputMaxNameSize]byte
ID InputID
UserDeviceAbsParams UserDeviceAbsParams
}
13 changes: 11 additions & 2 deletions uinput.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ const (
// If set up fails the device will be removed from the system,
// once set up it can be removed by calling dev.Close
func CreateDevice(name string, id InputID, capabilities map[EvType][]EvCode) (*InputDevice, error) {
return CreateDeviceWithAbsParams(name, id, capabilities, UserDeviceAbsParams{})
}

// CreateDeviceWithAbsParams creates a device from scratch with the specified name, identifiers,
// capabilities, and a set of parameters for absolute inputs.
// If setup fails, the device is removed from the system.
// It can be closed manually using the dev.Close() method.
func CreateDeviceWithAbsParams(name string, id InputID, capabilities map[EvType][]EvCode, absParams UserDeviceAbsParams) (*InputDevice, error) {
deviceFile, err := os.OpenFile("/dev/uinput", syscall.O_WRONLY|syscall.O_NONBLOCK, 0660)
if err != nil {
return nil, err
Expand All @@ -39,8 +47,9 @@ func CreateDevice(name string, id InputID, capabilities map[EvType][]EvCode) (*I
}

if _, err = createInputDevice(newDev.file, UinputUserDevice{
Name: toUinputName([]byte(name)),
ID: id,
Name: toUinputName([]byte(name)),
ID: id,
UserDeviceAbsParams: absParams,
}); err != nil {
DestroyDevice(newDev)
return nil, fmt.Errorf("failed to create device: %w", err)
Expand Down