mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-24 07:39:07 +02:00
Map uinput keysyms and typed text through the console's active keymap, and cover the keypad, lock and AltGr keysyms
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
//go:build linux
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// consoleTTY is the console device whose keymap uinput events are decoded
|
||||
// with: /dev/tty0 always names the foreground virtual terminal.
|
||||
const consoleTTY = "/dev/tty0"
|
||||
|
||||
// kdgkbent is KDGKBENT from linux/kd.h, which reads one keymap entry.
|
||||
const kdgkbent = 0x4B46
|
||||
|
||||
// Keymap tables (linux/keyboard.h): the character a key produces unmodified,
|
||||
// with Shift, and with AltGr.
|
||||
const (
|
||||
kNormTab = 0
|
||||
kShiftTab = 1
|
||||
kAltGrTab = 2
|
||||
)
|
||||
|
||||
// Key types (linux/keyboard.h). KT_LATIN and KT_LETTER carry a Latin-1
|
||||
// character in the low byte; ktNrTypes is NR_TYPES, one past the last real
|
||||
// type, which is how Unicode entries are told apart.
|
||||
const (
|
||||
ktLatin = 0
|
||||
ktLetter = 11
|
||||
ktNrTypes = 15
|
||||
)
|
||||
|
||||
// kbEntry mirrors struct kbentry.
|
||||
type kbEntry struct {
|
||||
table uint8
|
||||
index uint8
|
||||
value uint16
|
||||
}
|
||||
|
||||
// consoleKey is the key that types a character on the console's active layout,
|
||||
// and the modifier that has to be held for it.
|
||||
type consoleKey struct {
|
||||
code uint16
|
||||
shift bool
|
||||
altGr bool
|
||||
}
|
||||
|
||||
// readConsoleKeymap reads the active console keymap and returns, for every
|
||||
// printable character it can type, the key that types it. The unmodified table
|
||||
// wins over the Shift one, and both over AltGr, so a character reachable more
|
||||
// than one way gets the simplest.
|
||||
//
|
||||
// The kernel decodes uinput key codes with this same keymap, which is what makes
|
||||
// it the right source. A fixed US table types the wrong characters on any other
|
||||
// layout, including into a password prompt on the console.
|
||||
func readConsoleKeymap(tty string) (map[rune]consoleKey, error) {
|
||||
fd, err := unix.Open(tty, unix.O_RDONLY|unix.O_NOCTTY, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open %s: %w", tty, err)
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
|
||||
tables := []struct {
|
||||
table uint8
|
||||
key consoleKey
|
||||
}{
|
||||
{kNormTab, consoleKey{}},
|
||||
{kShiftTab, consoleKey{shift: true}},
|
||||
{kAltGrTab, consoleKey{altGr: true}},
|
||||
}
|
||||
|
||||
out := make(map[rune]consoleKey)
|
||||
for _, tab := range tables {
|
||||
for code := 1; code < 256; code++ {
|
||||
e := kbEntry{table: tab.table, index: uint8(code)}
|
||||
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), kdgkbent, uintptr(unsafe.Pointer(&e))); errno != 0 {
|
||||
if tab.table == kNormTab && code == 1 {
|
||||
return nil, fmt.Errorf("KDGKBENT on %s: %w", tty, errno)
|
||||
}
|
||||
continue
|
||||
}
|
||||
r, ok := consoleKeymapRune(e.value)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if _, taken := out[r]; taken {
|
||||
continue
|
||||
}
|
||||
key := tab.key
|
||||
key.code = uint16(code)
|
||||
out[r] = key
|
||||
}
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return nil, errors.New("console keymap has no printable entries")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// consoleKeymapRune decodes a KDGKBENT value into the character it types.
|
||||
// Unicode entries come back as the code point XOR 0xf000, which leaves their
|
||||
// type byte at NR_TYPES or above, the same test dumpkeys applies; Latin-1 ones
|
||||
// carry a real type and the character in the low byte. Everything else
|
||||
// (function keys, modifiers, dead keys, holes) types no character of its own.
|
||||
func consoleKeymapRune(v uint16) (rune, bool) {
|
||||
var r rune
|
||||
switch t := v >> 8; {
|
||||
case t >= ktNrTypes:
|
||||
r = rune(v ^ 0xf000)
|
||||
case t == ktLatin || t == ktLetter:
|
||||
r = rune(v & 0xff)
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
return r, r >= 0x20 && r != 0x7f
|
||||
}
|
||||
|
||||
// keymapCodes returns the key codes a console keymap uses, so the uinput device
|
||||
// can advertise every one of them.
|
||||
func keymapCodes(km map[rune]consoleKey) []uint16 {
|
||||
codes := make([]uint16, 0, len(km))
|
||||
for _, k := range km {
|
||||
codes = append(codes, k.code)
|
||||
}
|
||||
return codes
|
||||
}
|
||||
Reference in New Issue
Block a user