uinput-controller: Initial support for keymaps
Keymap can be set with environment variable REMOTE_TOUCHPAD_UINPUT_KEYMAP
This commit is contained in:
parent
d254ae5b0d
commit
bbb6cc4dc1
2 changed files with 181 additions and 76 deletions
|
|
@ -25,67 +25,14 @@ package inputcontrol
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/bendahl/uinput"
|
"github.com/bendahl/uinput"
|
||||||
"unicode"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ukeysMap = map[rune]int{
|
const shiftKeysDelay time.Duration = 50 * time.Millisecond
|
||||||
'a': uinput.KeyA,
|
|
||||||
'b': uinput.KeyB,
|
|
||||||
'c': uinput.KeyC,
|
|
||||||
'd': uinput.KeyD,
|
|
||||||
'e': uinput.KeyE,
|
|
||||||
'f': uinput.KeyF,
|
|
||||||
'g': uinput.KeyG,
|
|
||||||
'h': uinput.KeyH,
|
|
||||||
'i': uinput.KeyI,
|
|
||||||
'j': uinput.KeyJ,
|
|
||||||
'k': uinput.KeyK,
|
|
||||||
'l': uinput.KeyL,
|
|
||||||
'm': uinput.KeyM,
|
|
||||||
'n': uinput.KeyN,
|
|
||||||
'o': uinput.KeyO,
|
|
||||||
'p': uinput.KeyP,
|
|
||||||
'q': uinput.KeyQ,
|
|
||||||
'r': uinput.KeyR,
|
|
||||||
's': uinput.KeyS,
|
|
||||||
't': uinput.KeyT,
|
|
||||||
'u': uinput.KeyU,
|
|
||||||
'v': uinput.KeyV,
|
|
||||||
'w': uinput.KeyW,
|
|
||||||
'x': uinput.KeyX,
|
|
||||||
'y': uinput.KeyY,
|
|
||||||
'z': uinput.KeyZ,
|
|
||||||
'0': uinput.Key0,
|
|
||||||
'1': uinput.Key1,
|
|
||||||
'2': uinput.Key2,
|
|
||||||
'3': uinput.Key3,
|
|
||||||
'4': uinput.Key4,
|
|
||||||
'5': uinput.Key5,
|
|
||||||
'6': uinput.Key6,
|
|
||||||
'7': uinput.Key7,
|
|
||||||
'8': uinput.Key8,
|
|
||||||
'9': uinput.Key9,
|
|
||||||
0x1b: uinput.KeyEsc,
|
|
||||||
'-': uinput.KeyMinus,
|
|
||||||
'+': uinput.KeyKpplus,
|
|
||||||
'*': uinput.KeyKpasterisk,
|
|
||||||
'=': uinput.KeyEqual,
|
|
||||||
0x08: uinput.KeyBackspace,
|
|
||||||
'\t': uinput.KeyTab,
|
|
||||||
'(': uinput.KeyLeftbrace,
|
|
||||||
')': uinput.KeyRightbrace,
|
|
||||||
'\n': uinput.KeyEnter,
|
|
||||||
';': uinput.KeySemicolon,
|
|
||||||
'\'': uinput.KeyApostrophe,
|
|
||||||
'`': uinput.KeyGrave,
|
|
||||||
'\\': uinput.KeyBackslash,
|
|
||||||
',': uinput.KeyComma,
|
|
||||||
'.': uinput.KeyDot,
|
|
||||||
'/': uinput.KeySlash,
|
|
||||||
' ': uinput.KeySpace,
|
|
||||||
}
|
|
||||||
|
|
||||||
type uinputController struct {
|
type uinputController struct {
|
||||||
|
keymap *Keymap
|
||||||
keyboard uinput.Keyboard
|
keyboard uinput.Keyboard
|
||||||
mouse uinput.Mouse
|
mouse uinput.Mouse
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +42,14 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitUinputController() (Controller, error) {
|
func InitUinputController() (Controller, error) {
|
||||||
|
keymapName := "defkeymap"
|
||||||
|
if s, ok := os.LookupEnv("REMOTE_TOUCHPAD_UINPUT_KEYMAP"); ok {
|
||||||
|
keymapName = s
|
||||||
|
}
|
||||||
|
keymap, err := LoadKeymap(keymapName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
keyboard, err := uinput.CreateKeyboard("/dev/uinput", []byte("remote-touchpad-keyboard"))
|
keyboard, err := uinput.CreateKeyboard("/dev/uinput", []byte("remote-touchpad-keyboard"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -104,7 +59,7 @@ func InitUinputController() (Controller, error) {
|
||||||
keyboard.Close()
|
keyboard.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &uinputController{keyboard, mouse}, nil
|
return &uinputController{keymap, keyboard, mouse}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *uinputController) Close() error {
|
func (p *uinputController) Close() error {
|
||||||
|
|
@ -115,33 +70,53 @@ func (p *uinputController) Close() error {
|
||||||
return p.mouse.Close()
|
return p.mouse.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// No support for keyboard layouts, only works with QWERTY
|
|
||||||
func (p *uinputController) KeyboardText(text string) error {
|
func (p *uinputController) KeyboardText(text string) error {
|
||||||
for _, runeValue := range text {
|
var activeShiftKeys KeyCombo
|
||||||
modifierShift := false
|
updateShiftKeys := func(keyCombo KeyCombo) error {
|
||||||
if unicode.IsUpper(runeValue) {
|
if activeShiftKeys.ShiftKeys == keyCombo.ShiftKeys {
|
||||||
modifierShift = true
|
activeShiftKeys.Key = keyCombo.Key
|
||||||
runeValue = unicode.ToLower(runeValue)
|
return nil
|
||||||
}
|
}
|
||||||
uinputKey, found := ukeysMap[runeValue]
|
if activeShiftKeys.Key != 0 {
|
||||||
|
time.Sleep(shiftKeysDelay)
|
||||||
|
activeShiftKeys.Key = 0
|
||||||
|
}
|
||||||
|
for i := range keyCombo.ShiftKeys {
|
||||||
|
if activeShiftKeys.ShiftKeys[i] != keyCombo.ShiftKeys[i] {
|
||||||
|
if activeShiftKeys.ShiftKeys[i] != 0 {
|
||||||
|
if err := p.keyboard.KeyUp(activeShiftKeys.ShiftKeys[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
activeShiftKeys.ShiftKeys[i] = 0
|
||||||
|
}
|
||||||
|
if keyCombo.ShiftKeys[i] != 0 {
|
||||||
|
if err := p.keyboard.KeyDown(keyCombo.ShiftKeys[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
activeShiftKeys.ShiftKeys[i] = keyCombo.ShiftKeys[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if keyCombo.Key != 0 {
|
||||||
|
activeShiftKeys.Key = keyCombo.Key
|
||||||
|
time.Sleep(shiftKeysDelay)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer updateShiftKeys(KeyCombo{})
|
||||||
|
for _, runeValue := range text {
|
||||||
|
keyCombo, found := p.keymap.Get(runeValue)
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("unsupported rune: %q", runeValue)
|
return fmt.Errorf("unsupported rune: %q", runeValue)
|
||||||
}
|
}
|
||||||
if modifierShift {
|
if err := updateShiftKeys(keyCombo); err != nil {
|
||||||
if err := p.keyboard.KeyDown(uinput.KeyLeftshift); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := p.keyboard.KeyPress(uinputKey); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if modifierShift {
|
if err := p.keyboard.KeyPress(keyCombo.Key); err != nil {
|
||||||
if err := p.keyboard.KeyUp(uinput.KeyLeftshift); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return updateShiftKeys(KeyCombo{Key: 1})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *uinputController) KeyboardKey(key Key) error {
|
func (p *uinputController) KeyboardKey(key Key) error {
|
||||||
|
|
|
||||||
130
inputcontrol/keymap.go
Normal file
130
inputcontrol/keymap.go
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
//go:build uinput
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Unrud <unrud@outlook.com>
|
||||||
|
*
|
||||||
|
* This file is part of Remote-Touchpad.
|
||||||
|
*
|
||||||
|
* Remote-Touchpad is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Remote-Touchpad is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Remote-Touchpad. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package inputcontrol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bkeymapFileSignature = "bkeymap"
|
||||||
|
|
||||||
|
// uapi/linux/keyboard.h
|
||||||
|
const (
|
||||||
|
nrKeys = 128
|
||||||
|
maxNrKeymaps = 256
|
||||||
|
nrShift = 9
|
||||||
|
|
||||||
|
ktLatin = 0
|
||||||
|
ktShift = 7
|
||||||
|
ktLetter = 11
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyCombo struct {
|
||||||
|
Key int
|
||||||
|
ShiftKeys [nrShift]int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Keymap struct {
|
||||||
|
keyComboMap map[rune]KeyCombo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (keymap Keymap) Get(character rune) (keyCombo KeyCombo, found bool) {
|
||||||
|
keyCombo, found = keymap.keyComboMap[character]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadKeymap(name string) (*Keymap, error) {
|
||||||
|
if len(name) == 0 || strings.HasPrefix(name, "-") {
|
||||||
|
return nil, fmt.Errorf("invalid keymap name: %#v", name)
|
||||||
|
}
|
||||||
|
bkeymap, err := exec.Command("loadkeys", "--bkeymap", name).Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(string(bkeymap), bkeymapFileSignature) {
|
||||||
|
return nil, fmt.Errorf("invalid bkeymap: signature not found")
|
||||||
|
}
|
||||||
|
bkeymapHeader := bkeymap[len(bkeymapFileSignature):]
|
||||||
|
var keymaps [][nrKeys]int
|
||||||
|
if len(bkeymapHeader) < maxNrKeymaps {
|
||||||
|
return nil, fmt.Errorf("invalid bkeymap: EOF")
|
||||||
|
}
|
||||||
|
currentBkeymap := bkeymapHeader[maxNrKeymaps:]
|
||||||
|
for keymapIndex := 0; keymapIndex < maxNrKeymaps; keymapIndex += 1 {
|
||||||
|
if bkeymapHeader[keymapIndex] == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(currentBkeymap) < nrKeys*2 {
|
||||||
|
return nil, fmt.Errorf("invalid bkeymap: EOF")
|
||||||
|
}
|
||||||
|
for len(keymaps) <= keymapIndex {
|
||||||
|
keymaps = append(keymaps, [nrKeys]int{})
|
||||||
|
}
|
||||||
|
for key := 0; key < nrKeys; key += 1 {
|
||||||
|
var keysym uint16
|
||||||
|
copy(unsafe.Slice((*byte)(unsafe.Pointer(&keysym)), 2),
|
||||||
|
currentBkeymap[key*2:])
|
||||||
|
keymaps[keymapIndex][key] = int(keysym)
|
||||||
|
}
|
||||||
|
currentBkeymap = currentBkeymap[nrKeys*2:]
|
||||||
|
}
|
||||||
|
keymap := Keymap{keyComboMap: make(map[rune]KeyCombo)}
|
||||||
|
if len(keymaps) == 0 {
|
||||||
|
return &keymap, nil
|
||||||
|
}
|
||||||
|
var shiftKeys [nrShift]int
|
||||||
|
for key, keysym := range keymaps[0] {
|
||||||
|
keyType := keysym >> 8
|
||||||
|
keyValue := keysym & 0xff
|
||||||
|
if keyType == ktShift && keyValue < nrShift && shiftKeys[keyValue] == 0 {
|
||||||
|
shiftKeys[keyValue] = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keymapLoop:
|
||||||
|
for keymapIndex := range keymaps {
|
||||||
|
var activeShiftKeys [nrShift]int
|
||||||
|
for i, shiftKey := range shiftKeys {
|
||||||
|
if keymapIndex&(1<<i) != 0 {
|
||||||
|
if shiftKey == 0 {
|
||||||
|
continue keymapLoop
|
||||||
|
}
|
||||||
|
activeShiftKeys[i] = shiftKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for key, keysym := range keymaps[keymapIndex] {
|
||||||
|
keyType := keysym >> 8
|
||||||
|
keyValue := keysym & 0xff
|
||||||
|
if (keyType != ktLatin && keyType != ktLetter) || keyValue == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
character := rune(keyValue)
|
||||||
|
if _, ok := keymap.keyComboMap[character]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keymap.keyComboMap[character] = KeyCombo{key, activeShiftKeys}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &keymap, nil
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue