remote-touchpad/inputcontrol/keymap.go
2023-04-25 01:38:48 +02:00

130 lines
3.4 KiB
Go

//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, &UnsupportedPlatformError{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
}