373 lines
12 KiB
Go
373 lines
12 KiB
Go
// Package xgbkb provides keyboard input utilities to be used alongside xgb and
|
|
// xgbutil.
|
|
package xgbkb
|
|
|
|
import "unicode"
|
|
import "github.com/jezek/xgbutil"
|
|
import "github.com/jezek/xgb/xproto"
|
|
import "github.com/jezek/xgbutil/keybind"
|
|
|
|
// TODO: support dead keys/compose key
|
|
|
|
// when making changes to this file, look at keysymdef.h and
|
|
// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html
|
|
|
|
var x *xgbutil.XUtil
|
|
|
|
var modifierMasks struct {
|
|
capsLock uint16
|
|
shiftLock uint16
|
|
numLock uint16
|
|
modeSwitch uint16
|
|
|
|
alt uint16
|
|
meta uint16
|
|
super uint16
|
|
hyper uint16
|
|
}
|
|
|
|
// IsOnNumpad returns whether the given keysym resides on the numpad.
|
|
func IsOnNumpad (symbol xproto.Keysym) bool {
|
|
return symbol >= 0xFF80 && symbol <= 0xFFB9
|
|
}
|
|
|
|
// Initialize grabs keyboard mapping information from the X server. This
|
|
// function must be called before calling any other functions in this package.
|
|
// keybind.Initialize must also be called before this function is called.
|
|
func Initialize (connection *xgbutil.XUtil) {
|
|
x = connection
|
|
|
|
modifierMasks.capsLock = KeysymToMask(0xFFE5)
|
|
modifierMasks.shiftLock = KeysymToMask(0xFFE6)
|
|
modifierMasks.numLock = KeysymToMask(0xFF7F)
|
|
modifierMasks.modeSwitch = KeysymToMask(0xFF7E)
|
|
|
|
modifierMasks.hyper = KeysymToMask(0xffed)
|
|
modifierMasks.super = KeysymToMask(0xffeb)
|
|
modifierMasks.meta = KeysymToMask(0xffe7)
|
|
modifierMasks.alt = KeysymToMask(0xffe9)
|
|
}
|
|
|
|
// Modifiers lists which modifier keys are toggled on or being pressed.
|
|
type Modifiers struct {
|
|
CapsLock bool
|
|
ShiftLock bool
|
|
NumLock bool
|
|
ModeSwitch bool
|
|
|
|
Shift bool
|
|
Control bool
|
|
Alt bool
|
|
Meta bool
|
|
Super bool
|
|
Hyper bool
|
|
}
|
|
|
|
// String returns a human-readable comma-separated list of all active modifiers.
|
|
func (modifiers Modifiers) String () (out string) {
|
|
add := func (name string) {
|
|
if out != "" {
|
|
out += ", "
|
|
}
|
|
out += name
|
|
}
|
|
if modifiers.CapsLock { add("CapsLock") }
|
|
if modifiers.ShiftLock { add("ShiftLock") }
|
|
if modifiers.NumLock { add("NumLock") }
|
|
if modifiers.ModeSwitch { add("ModeSwitch") }
|
|
if modifiers.Shift { add("Shift") }
|
|
if modifiers.Control { add("Control") }
|
|
if modifiers.Alt { add("Alt") }
|
|
if modifiers.Meta { add("Meta") }
|
|
if modifiers.Super { add("Super") }
|
|
if modifiers.Hyper { add("Hyper") }
|
|
return
|
|
}
|
|
|
|
// StateToModifiers converts a modifier state given by a keyboard or mouse event
|
|
// to a Modifiers struct.
|
|
func StateToModifiers (state uint16) Modifiers {
|
|
return Modifiers {
|
|
CapsLock: (state & modifierMasks.capsLock) > 0,
|
|
ShiftLock: (state & modifierMasks.shiftLock) > 0,
|
|
NumLock: (state & modifierMasks.numLock) > 0,
|
|
ModeSwitch: (state & modifierMasks.modeSwitch) > 0,
|
|
Shift: (state & xproto.ModMaskShift) > 0,
|
|
Control: (state & xproto.ModMaskControl) > 0,
|
|
Alt: (state & modifierMasks.alt) > 0,
|
|
Meta: (state & modifierMasks.meta) > 0,
|
|
Super: (state & modifierMasks.super) > 0,
|
|
Hyper: (state & modifierMasks.hyper) > 0,
|
|
}
|
|
}
|
|
|
|
// KeysymToKeycode converts an X keysym to an X keycode, instead of the other
|
|
// way around.
|
|
func KeysymToKeycode (
|
|
symbol xproto.Keysym,
|
|
) (
|
|
code xproto.Keycode,
|
|
) {
|
|
mapping := keybind.KeyMapGet(x)
|
|
|
|
for index, testSymbol := range mapping.Keysyms {
|
|
if testSymbol == symbol {
|
|
code = xproto.Keycode (
|
|
index /
|
|
int(mapping.KeysymsPerKeycode) +
|
|
int(x.Setup().MinKeycode))
|
|
break
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// KeysymToMask returns the X modmask for a given modifier key.
|
|
func KeysymToMask (symbol xproto.Keysym) uint16 {
|
|
return keybind.ModGet(x, KeysymToKeycode(symbol))
|
|
}
|
|
|
|
// KeycodeToButton converts an X keycode to a tomo keycode. It implements a more
|
|
// fleshed out version of some of the logic found in xgbutil/keybind/encoding.go
|
|
// to get a full keycode to keysym conversion. To avoid redundant work, this
|
|
// function will also return the rune corresponding to the keycode.
|
|
func KeycodeToKeysym (keycode xproto.Keycode, state uint16) (xproto.Keysym, rune) {
|
|
// PARAGRAPH 3
|
|
//
|
|
// A list of KeySyms is associated with each KeyCode. The list is
|
|
// intended to convey the set of symbols on the corresponding key. If
|
|
// the list (ignoring trailing NoSymbol entries) is a single KeySym
|
|
// ``K'', then the list is treated as if it were the list ``K NoSymbol
|
|
// K NoSymbol''. If the list (ignoring trailing NoSymbol entries) is a
|
|
// pair of KeySyms ``K1 K2'', then the list is treated as if it were the
|
|
// list ``K1 K2 K1 K2''. If the list (ignoring trailing NoSymbol
|
|
// entries) is a triple of KeySyms ``K1 K2 K3'', then the list is
|
|
// treated as if it were the list ``K1 K2 K3 NoSymbol''. When an
|
|
// explicit ``void'' element is desired in the list, the value
|
|
// VoidSymbol can be used.
|
|
symbol1 := keybind.KeysymGet(x, keycode, 0)
|
|
symbol2 := keybind.KeysymGet(x, keycode, 1)
|
|
symbol3 := keybind.KeysymGet(x, keycode, 2)
|
|
symbol4 := keybind.KeysymGet(x, keycode, 3)
|
|
switch {
|
|
case symbol2 == 0 && symbol3 == 0 && symbol4 == 0:
|
|
symbol3 = symbol1
|
|
case symbol3 == 0 && symbol4 == 0:
|
|
symbol3 = symbol1
|
|
symbol4 = symbol2
|
|
case symbol4 == 0:
|
|
symbol4 = 0
|
|
}
|
|
symbol1Rune := KeysymToRune(symbol1)
|
|
symbol2Rune := KeysymToRune(symbol2)
|
|
symbol3Rune := KeysymToRune(symbol3)
|
|
symbol4Rune := KeysymToRune(symbol4)
|
|
|
|
// PARAGRAPH 4
|
|
//
|
|
// The first four elements of the list are split into two groups of
|
|
// KeySyms. Group 1 contains the first and second KeySyms; Group 2
|
|
// contains the third and fourth KeySyms. Within each group, if the
|
|
// second element of the group is NoSymbol , then the group should be
|
|
// treated as if the second element were the same as the first element,
|
|
// except when the first element is an alphabetic KeySym ``K'' for which
|
|
// both lowercase and uppercase forms are defined. In that case, the
|
|
// group should be treated as if the first element were the lowercase
|
|
// form of ``K'' and the second element were the uppercase form of
|
|
// ``K.''
|
|
cased := false
|
|
if symbol2 == 0 {
|
|
upper := unicode.IsUpper(symbol1Rune)
|
|
lower := unicode.IsLower(symbol1Rune)
|
|
if upper || lower {
|
|
symbol1Rune = unicode.ToLower(symbol1Rune)
|
|
symbol2Rune = unicode.ToUpper(symbol1Rune)
|
|
cased = true
|
|
} else {
|
|
symbol2 = symbol1
|
|
symbol2Rune = symbol1Rune
|
|
}
|
|
}
|
|
if symbol4 == 0 {
|
|
upper := unicode.IsUpper(symbol3Rune)
|
|
lower := unicode.IsLower(symbol3Rune)
|
|
if upper || lower {
|
|
symbol3Rune = unicode.ToLower(symbol3Rune)
|
|
symbol4Rune = unicode.ToUpper(symbol3Rune)
|
|
cased = true
|
|
} else {
|
|
symbol4 = symbol3
|
|
symbol4Rune = symbol3Rune
|
|
}
|
|
}
|
|
|
|
// PARAGRAPH 5
|
|
//
|
|
// The standard rules for obtaining a KeySym from a KeyPress event make
|
|
// use of only the Group 1 and Group 2 KeySyms; no interpretation of/
|
|
// other KeySyms in the list is given. Which group to use is determined
|
|
// by the modifier state. Switching between groups is controlled by the
|
|
// KeySym named MODE SWITCH, by attaching that KeySym to some KeyCode
|
|
// and attaching that KeyCode to any one of the modifiers Mod1 through
|
|
// Mod5. This modifier is called the group modifier. For any KeyCode,
|
|
// Group 1 is used when the group modifier is off, and Group 2 is used
|
|
// when the group modifier is on.
|
|
modeSwitch := state & modifierMasks.modeSwitch > 0
|
|
if modeSwitch {
|
|
symbol1 = symbol3
|
|
symbol1Rune = symbol3Rune
|
|
symbol2 = symbol4
|
|
symbol2Rune = symbol4Rune
|
|
|
|
}
|
|
|
|
// PARAGRAPH 6
|
|
//
|
|
// The Lock modifier is interpreted as CapsLock when the KeySym named
|
|
// XK_Caps_Lock is attached to some KeyCode and that KeyCode is attached
|
|
// to the Lock modifier. The Lock modifier is interpreted as ShiftLock
|
|
// when the KeySym named XK_Shift_Lock is attached to some KeyCode and
|
|
// that KeyCode is attached to the Lock modifier. If the Lock modifier
|
|
// could be interpreted as both CapsLock and ShiftLock, the CapsLock
|
|
// interpretation is used.
|
|
shift :=
|
|
state & xproto.ModMaskShift > 0 ||
|
|
state & modifierMasks.shiftLock > 0
|
|
capsLock := state & modifierMasks.capsLock > 0
|
|
|
|
// PARAGRAPH 7
|
|
//
|
|
// The operation of keypad keys is controlled by the KeySym named
|
|
// XK_Num_Lock, by attaching that KeySym to some KeyCode and attaching
|
|
// that KeyCode to any one of the modifiers Mod1 through Mod5 . This
|
|
// modifier is called the numlock modifier. The standard KeySyms with
|
|
// the prefix ``XK_KP_'' in their name are called keypad KeySyms; these
|
|
// are KeySyms with numeric value in the hexadecimal range 0xFF80 to
|
|
// 0xFFBD inclusive. In addition, vendor-specific KeySyms in the
|
|
// hexadecimal range 0x11000000 to 0x1100FFFF are also keypad KeySyms.
|
|
numLock := state & modifierMasks.numLock > 0
|
|
|
|
// PARAGRAPH 8
|
|
//
|
|
// Within a group, the choice of KeySym is determined by applying the
|
|
// first rule that is satisfied from the following list:
|
|
var selectedKeysym xproto.Keysym
|
|
var selectedRune rune
|
|
symbol2IsNumPad := IsOnNumpad(symbol2)
|
|
switch {
|
|
case numLock && symbol2IsNumPad:
|
|
// The numlock modifier is on and the second KeySym is a keypad
|
|
// KeySym. In this case, if the Shift modifier is on, or if the
|
|
// Lock modifier is on and is interpreted as ShiftLock, then the
|
|
// first KeySym is used, otherwise the second KeySym is used.
|
|
if shift {
|
|
selectedKeysym = symbol1
|
|
selectedRune = symbol1Rune
|
|
} else {
|
|
selectedKeysym = symbol2
|
|
selectedRune = symbol2Rune
|
|
}
|
|
|
|
case !shift && !capsLock:
|
|
// The Shift and Lock modifiers are both off. In this case, the
|
|
// first KeySym is used.
|
|
selectedKeysym = symbol1
|
|
selectedRune = symbol1Rune
|
|
|
|
case !shift && capsLock:
|
|
// The Shift modifier is off, and the Lock modifier is on and is
|
|
// interpreted as CapsLock. In this case, the first KeySym is
|
|
// used, but if that KeySym is lowercase alphabetic, then the
|
|
// corresponding uppercase KeySym is used instead.
|
|
if cased && unicode.IsLower(symbol1Rune) {
|
|
selectedRune = symbol2Rune
|
|
} else {
|
|
selectedKeysym = symbol1
|
|
selectedRune = symbol1Rune
|
|
}
|
|
|
|
case shift && capsLock:
|
|
// The Shift modifier is on, and the Lock modifier is on and is
|
|
// interpreted as CapsLock. In this case, the second KeySym is
|
|
// used, but if that KeySym is lowercase alphabetic, then the
|
|
// corresponding uppercase KeySym is used instead.
|
|
if cased && unicode.IsLower(symbol2Rune) {
|
|
selectedRune = unicode.ToUpper(symbol2Rune)
|
|
} else {
|
|
selectedKeysym = symbol2
|
|
selectedRune = symbol2Rune
|
|
}
|
|
|
|
case shift:
|
|
// The Shift modifier is on, or the Lock modifier is on and is
|
|
// interpreted as ShiftLock, or both. In this case, the second
|
|
// KeySym is used.
|
|
selectedKeysym = symbol2
|
|
selectedRune = symbol2Rune
|
|
}
|
|
|
|
return selectedKeysym, selectedRune
|
|
}
|
|
|
|
// runeExceptions contains runes that violate the heuristics in KeysymToRune.
|
|
var runeExceptions = map[xproto.Keysym] rune {
|
|
// TODO: flesh out this list
|
|
|
|
// number pad
|
|
0xFF9F: '\x7F', // delete
|
|
0xFF80: ' ',
|
|
0xFF89: '\t',
|
|
0xFF8D: '\r', // enter
|
|
0xFFBD: '=',
|
|
0xFFAA: '*',
|
|
0xFFAB: '+',
|
|
0xFFAC: ',', // FIXME: not always comma, could be dot
|
|
0xFFAD: '-',
|
|
0xFFAE: '.', // FIXME: not always dot, could be comma
|
|
0xFFAF: '/',
|
|
|
|
// misc. control
|
|
0xFFFF: '\x7F', // delete
|
|
0xFF09: '\t',
|
|
0xFE20: '\t',
|
|
0xFE34: '\r', // enter
|
|
}
|
|
|
|
// KeysymToRune takes in an X keysym and outputs a utf32 code point. If a
|
|
// corresponding code point was not found, zero will be returned.
|
|
func KeysymToRune (keysym xproto.Keysym) rune {
|
|
// there are a few keysyms that can be shoehorned into runes that don't
|
|
// fit the below rules or warrant their own
|
|
if char, ok := runeExceptions[keysym]; ok {
|
|
return char
|
|
}
|
|
|
|
// TTY function keys
|
|
if keysym >= 0xFF08 && keysym <= 0xFF1B {
|
|
return rune(keysym & 0xF)
|
|
}
|
|
|
|
// number pad numbers
|
|
if keysym >= 0xFFB0 && keysym <= 0xFFB9 {
|
|
return '0' + rune(keysym & 0xF)
|
|
}
|
|
|
|
// X keysyms like 0xFF.. or 0xFE.. are non-character keys. these cannot
|
|
// be converted so we return a zero.
|
|
if (keysym >> 8) == 0xFF || (keysym >> 8) == 0xFE {
|
|
return 0
|
|
}
|
|
|
|
// some X keysyms have a single bit set to 1 here. i believe this is to
|
|
// prevent conflicts with existing codes. if we mask it off we will get
|
|
// a correct utf-32 code point.
|
|
if keysym & 0xF000000 == 0x1000000 {
|
|
return rune(keysym & 0x0111111)
|
|
}
|
|
|
|
// if none of these things happened, we can safely (i think) assume that
|
|
// the keysym is an exact utf-32 code point.
|
|
return rune(keysym)
|
|
}
|