label_printer/epson/faxmachine/keyboard/keyboard.go

149 lines
3.1 KiB
Go

package keyboard
import (
"context"
"fmt"
"log"
"git.sr.ht/~guacamolie/faxmachine/xkbcommon"
"github.com/holoplot/go-evdev"
)
type Keyboard struct {
dev *evdev.InputDevice
ctx *xkbcommon.Context
keymap *xkbcommon.Keymap
state *xkbcommon.State
}
func Open(devnode string) (*Keyboard, error) {
var err error
k := &Keyboard{}
k.dev, err = evdev.Open(devnode)
if err != nil {
return nil, err
}
err = k.dev.Grab()
if err != nil {
k.dev.Close()
return nil, fmt.Errorf("failed to grab input: %w", err)
}
k.ctx, err = xkbcommon.NewContext(xkbcommon.ContextNoFlags)
if err != nil {
k.dev.Close()
return nil, err
}
k.keymap, err = xkbcommon.NewKeymapFromNames(k.ctx, nil, xkbcommon.KeymapCompileFlagsNoFlags)
if err != nil {
k.ctx.Unref()
k.dev.Close()
return nil, err
}
k.state, err = xkbcommon.NewState(k.keymap)
if err != nil {
k.keymap.Unref()
k.ctx.Unref()
k.dev.Close()
return nil, err
}
return k, nil
}
func (k *Keyboard) Close() {
k.state.Unref()
k.keymap.Unref()
k.ctx.Unref()
k.dev.Close()
}
func (k *Keyboard) Listen(ctx context.Context, ch chan<- KeyPress) {
go func() {
for {
inputEvent, err := k.dev.ReadOne()
if err != nil {
log.Fatalf("keyboard read failed: %v\n", err)
}
if inputEvent.Type != evdev.EV_KEY {
continue
}
if inputEvent.Value == 1 {
keyPress := KeyPress{
Sym: k.state.GetOneSym(keycode(inputEvent)),
ShortcutMods: k.getShortcutMods(keycode(inputEvent)),
Char: k.state.KeyGetUtf8(keycode(inputEvent)),
}
log.Printf("keypress: %#v\n", keyPress)
ch <- keyPress
}
// This should be called _after_ procesing the key, so that the key
// doesn't influence itself.
if inputEvent.Value == 1 {
k.state.UpdateKey(keycode(inputEvent), xkbcommon.KeyDown)
} else if inputEvent.Value == 0 {
k.state.UpdateKey(keycode(inputEvent), xkbcommon.KeyUp)
}
}
}()
}
func (k *Keyboard) getShortcutMods(key xkbcommon.Keycode) ModMask {
// This implements the algorithm as described here:
// https://xkbcommon.org/doc/current/group__state.html#consumed-modifiers
stateMods := k.state.SerializeMods(xkbcommon.StateModsEffective)
consumedMods := k.state.KeyGetConsumedMods(key)
significantMods := xkbcommon.ModMask(
1<<k.keymap.ModGetIndex("Control") |
1<<k.keymap.ModGetIndex("Alt") |
1<<k.keymap.ModGetIndex("Shift") |
1<<k.keymap.ModGetIndex("Super"))
shortcutMods := stateMods & ^consumedMods & significantMods
return k.parseModMask(shortcutMods)
}
func (k *Keyboard) parseModMask(modMask xkbcommon.ModMask) (mod ModMask) {
if modMask&(1<<k.keymap.ModGetIndex("Control")) != 0 {
mod |= Control
}
if modMask&(1<<k.keymap.ModGetIndex("Alt")) != 0 {
mod |= Alt
}
if modMask&(1<<k.keymap.ModGetIndex("Shift")) != 0 {
mod |= Shift
}
if modMask&(1<<k.keymap.ModGetIndex("Super")) != 0 {
mod |= Super
}
return mod
}
func keycode(event *evdev.InputEvent) xkbcommon.Keycode {
return xkbcommon.Keycode(event.Code + 8)
}
type KeyPress struct {
Sym xkbcommon.Keysym
ShortcutMods ModMask
Char string
}
type ModMask int
const (
Control ModMask = 1 << iota
Alt
Shift
Super
)