overhaul to use xkbcommon
This commit is contained in:
parent
01fcc71667
commit
f8d6461da0
10 changed files with 812 additions and 720 deletions
|
@ -1,7 +0,0 @@
|
||||||
# This can stop dotool typing guff if you are using a non-us keyboard layout.
|
|
||||||
Section "InputClass"
|
|
||||||
Identifier "dotool keyboard"
|
|
||||||
MatchDriver "libinput"
|
|
||||||
MatchProduct "dotool keyboard"
|
|
||||||
Option "XkbLayout" "us"
|
|
||||||
EndSection
|
|
|
@ -1,6 +1,2 @@
|
||||||
# This allows users in group input to use dotool without root permissions.
|
# This allows users in group input to use dotool without root permissions.
|
||||||
KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"
|
KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"
|
||||||
|
|
||||||
# This can stop dotool typing guff if you are using a non-us keyboard layout,
|
|
||||||
# but it only seems to affect X and 50-dotool.conf achieves that better.
|
|
||||||
SUBSYSTEM=="input", ACTION=="add|change", ATTRS{name}=="dotool keyboard", ENV{XKBLAYOUT}="us"
|
|
||||||
|
|
23
README.md
23
README.md
|
@ -1,11 +1,11 @@
|
||||||
# dotool
|
# dotool
|
||||||
|
|
||||||
dotool reads commands from stdin and simulates keyboard and mouse events.
|
dotool reads commands from stdin and simulates keyboard and pointer events.
|
||||||
It works everywhere on Linux, including in X11, Wayland and TTYs.
|
It works everywhere on Linux, including in X11, Wayland and TTYs.
|
||||||
|
|
||||||
## Install From Source
|
## Install From Source
|
||||||
|
|
||||||
With `go` installed, run:
|
With `go` and `libxkbcommon-dev` installed, run:
|
||||||
|
|
||||||
sudo ./install.sh
|
sudo ./install.sh
|
||||||
|
|
||||||
|
@ -35,13 +35,22 @@ and this screams for three seconds:
|
||||||
|
|
||||||
{ echo keydown A; sleep 3; echo key H shift+1; } | dotool
|
{ echo keydown A; sleep 3; echo key H shift+1; } | dotool
|
||||||
|
|
||||||
Each instance takes about half a second to register the virtual input devices,
|
There is an initial delay registering the virtual devices, but you can
|
||||||
but you can keep writing commands to one instance or use the daemon and client,
|
keep writing commands to the same instance or use the daemon and client,
|
||||||
`dotoold` and `dotoolc`:
|
`dotoold` and `dotoolc`.
|
||||||
|
|
||||||
dotoold &
|
dotoold &
|
||||||
echo 'type super' | dotoolc
|
echo type super | dotoolc
|
||||||
echo 'type speedy' | dotoolc
|
echo type speedy | dotoolc
|
||||||
|
|
||||||
|
## Keyboard Layouts
|
||||||
|
|
||||||
|
dotool will produce gobbledygook if your environment has assigned it a
|
||||||
|
different keyboard layout than it's simulating keycodes for. You can
|
||||||
|
match them up with the environment variables `DOTOOL_XKB_LAYOUT` and
|
||||||
|
`DOTOOL_XKB_VARIANT`.
|
||||||
|
|
||||||
|
echo type azerty | DOTOOL_XKB_LAYOUT=fr dotool
|
||||||
|
|
||||||
## Numen and Contact
|
## Numen and Contact
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# ./_install.sh [DESTDIR] [BINDIR]
|
|
||||||
mkdir -p "$1/${2:-usr/local/bin}" || exit
|
|
||||||
cp -v dotoolc dotoold "$1/${2:-usr/local/bin}" || exit
|
|
||||||
mkdir -p "$1/usr/share/X11/xorg.conf.d" || exit
|
|
||||||
cp -v 50-dotool.conf "$1/usr/share/X11/xorg.conf.d" || exit
|
|
||||||
mkdir -p "$1/etc/sway/config.d" || exit
|
|
||||||
cp -v dotool.sway "$1/etc/sway/config.d/dotool" || exit
|
|
203
dotool.go
203
dotool.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.sr.ht/~geb/dotool/xkb"
|
||||||
"git.sr.ht/~geb/opt"
|
"git.sr.ht/~geb/opt"
|
||||||
"github.com/bendahl/uinput"
|
"github.com/bendahl/uinput"
|
||||||
"math"
|
"math"
|
||||||
|
@ -17,7 +18,7 @@ import (
|
||||||
var Version string
|
var Version string
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Fprintln(os.Stderr, `dotool reads commands from stdin and simulates keyboard and pointer events.
|
fmt.Println(`dotool reads commands from stdin and simulates keyboard and pointer events.
|
||||||
|
|
||||||
The commands are:
|
The commands are:
|
||||||
key CHORD...
|
key CHORD...
|
||||||
|
@ -35,26 +36,51 @@ The commands are:
|
||||||
typedelay MILLISECONDS (default: 2)
|
typedelay MILLISECONDS (default: 2)
|
||||||
typehold MILLISECONDS (default: 8)
|
typehold MILLISECONDS (default: 8)
|
||||||
|
|
||||||
Example: echo "key h i shift+1" | dotool
|
|
||||||
|
|
||||||
dotool is installed with a udev rule to allow users in group input to run
|
dotool is installed with a udev rule to allow users in group input to run
|
||||||
it without root permissions. You can make it effective without rebooting by
|
it without root permissions.
|
||||||
running: sudo udevadm trigger
|
|
||||||
|
|
||||||
The keys are those used by Linux, but can also be specified using X11 names
|
You can add yourself to group input by running:
|
||||||
prefixed with x: like x:exclam, as well as their Linux keycode like k:30.
|
|
||||||
They are case insensitive, except uppercase character keys also simulate shift.
|
sudo groupadd -f input
|
||||||
|
sudo usermod -a -G input $USER
|
||||||
|
|
||||||
|
It's foolproof to reboot to make the rule effective.
|
||||||
|
|
||||||
|
|
||||||
|
Keys can be specified by Linux names, XKB names prefixed with x:, or Linux
|
||||||
|
keycodes prefixed with k:. The Linux names are case-insensitive, except
|
||||||
|
uppercase character keys also simulate shift.
|
||||||
|
|
||||||
The modifiers are: super, ctrl, alt and shift.
|
The modifiers are: super, ctrl, alt and shift.
|
||||||
|
|
||||||
The daemon and client, dotoold and dotoolc, can used to keep a persistent
|
echo key shift+1 x:exclam shift+k:2 | dotool
|
||||||
virtual device for a quicker initial response.
|
|
||||||
|
|
||||||
|
There is an initial delay registering the virtual devices, but you can keep
|
||||||
|
writing commands to the same instance or use the daemon and client, dotoold
|
||||||
|
and dotoolc.
|
||||||
|
|
||||||
|
{ echo keydown A; sleep 3; echo key H shift+1; } | dotool
|
||||||
|
|
||||||
|
dotoold &
|
||||||
|
echo type super | dotoolc
|
||||||
|
echo type speedy | dotoolc
|
||||||
|
|
||||||
|
|
||||||
|
dotool will produce gobbledygook if your environment has assigned it a
|
||||||
|
different keyboard layout than it's simulating keycodes for. You can
|
||||||
|
match them up with the environment variables DOTOOL_XKB_LAYOUT and
|
||||||
|
DOTOOL_XKB_VARIANT.
|
||||||
|
|
||||||
|
echo type azerty | DOTOOL_XKB_LAYOUT=fr dotool
|
||||||
|
|
||||||
|
|
||||||
--list-keys
|
--list-keys
|
||||||
Print the supported Linux keys and their keycodes.
|
Print the possible Linux keys and exit.
|
||||||
|
|
||||||
--list-x-keys
|
--list-x-keys
|
||||||
Print the supported X11 keys and their Linux keycodes.
|
Print the possible XKB keys and exit.
|
||||||
|
|
||||||
--version
|
--version
|
||||||
Print the version and exit.
|
Print the version and exit.
|
||||||
|
@ -79,16 +105,38 @@ func log(err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Chord struct {
|
type Chord struct {
|
||||||
Super bool
|
Super, AltGr, Ctrl, Alt, Shift bool
|
||||||
Ctrl bool
|
|
||||||
Alt bool
|
|
||||||
Shift bool
|
|
||||||
Key int
|
Key int
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseChord(chord string) (Chord, error) {
|
func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
|
||||||
var c Chord
|
var c Chord
|
||||||
keys := strings.Split(chord, "+")
|
keys := strings.Split(chord, "+")
|
||||||
|
|
||||||
|
k := keys[len(keys)-1]
|
||||||
|
if strings.HasPrefix(k, "k:") {
|
||||||
|
code, err := strconv.Atoi(k[2:])
|
||||||
|
if err != nil {
|
||||||
|
return c, errors.New("invalid keycode: " + k[2:])
|
||||||
|
}
|
||||||
|
c.Key = code
|
||||||
|
} else if strings.HasPrefix(k, "x:") {
|
||||||
|
var ok bool
|
||||||
|
c, ok = XKeys[k[2:]]
|
||||||
|
if !ok {
|
||||||
|
return c, errors.New("impossible XKB key for layout: " + k[2:])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var ok bool
|
||||||
|
c, ok = LinuxKeys[strings.ToLower(k)]
|
||||||
|
if !ok {
|
||||||
|
return c, errors.New("impossible key for layout: " + k)
|
||||||
|
}
|
||||||
|
if len(k) == 1 && unicode.IsUpper(rune(k[0])) {
|
||||||
|
c.Shift = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < len(keys) - 1; i++ {
|
for i := 0; i < len(keys) - 1; i++ {
|
||||||
switch strings.ToLower(keys[i]) {
|
switch strings.ToLower(keys[i]) {
|
||||||
case "super":
|
case "super":
|
||||||
|
@ -104,34 +152,6 @@ func parseChord(chord string) (Chord, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
k := keys[len(keys)-1]
|
|
||||||
if strings.HasPrefix(k, "k:") {
|
|
||||||
code, err := strconv.Atoi(k[2:])
|
|
||||||
if err != nil {
|
|
||||||
return c, errors.New("invalid keycode: " + k[2:])
|
|
||||||
}
|
|
||||||
c.Key = code
|
|
||||||
} else if strings.HasPrefix(k, "x:") {
|
|
||||||
if code, ok := xKeysShifted[strings.ToLower(k[2:])]; ok {
|
|
||||||
if len(k[2:]) > 1 || unicode.IsUpper(rune(k[2])) {
|
|
||||||
c.Shift = true
|
|
||||||
}
|
|
||||||
c.Key = code
|
|
||||||
} else if code, ok := xKeysNormal[strings.ToLower(k[2:])]; ok {
|
|
||||||
c.Key = code
|
|
||||||
} else {
|
|
||||||
return c, errors.New("unknown X11 key: " + k[2:])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
code, ok := linuxKeys[strings.ToLower(k)]
|
|
||||||
if len(k) == 1 && unicode.IsUpper(rune(k[0])) {
|
|
||||||
c.Shift = true
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return c, errors.New("unknown key: " + k)
|
|
||||||
}
|
|
||||||
c.Key = code
|
|
||||||
}
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,6 +159,9 @@ func (c *Chord) KeyDown(kb uinput.Keyboard) {
|
||||||
if c.Super {
|
if c.Super {
|
||||||
log(kb.KeyDown(uinput.KeyLeftmeta))
|
log(kb.KeyDown(uinput.KeyLeftmeta))
|
||||||
}
|
}
|
||||||
|
if c.AltGr {
|
||||||
|
log(kb.KeyDown(84))
|
||||||
|
}
|
||||||
if c.Ctrl {
|
if c.Ctrl {
|
||||||
log(kb.KeyDown(uinput.KeyLeftctrl))
|
log(kb.KeyDown(uinput.KeyLeftctrl))
|
||||||
}
|
}
|
||||||
|
@ -155,6 +178,9 @@ func (c *Chord) KeyUp(kb uinput.Keyboard) {
|
||||||
if c.Super {
|
if c.Super {
|
||||||
log(kb.KeyUp(uinput.KeyLeftmeta))
|
log(kb.KeyUp(uinput.KeyLeftmeta))
|
||||||
}
|
}
|
||||||
|
if c.AltGr {
|
||||||
|
log(kb.KeyUp(84))
|
||||||
|
}
|
||||||
if c.Ctrl {
|
if c.Ctrl {
|
||||||
log(kb.KeyUp(uinput.KeyLeftctrl))
|
log(kb.KeyUp(uinput.KeyLeftctrl))
|
||||||
}
|
}
|
||||||
|
@ -168,6 +194,21 @@ func (c *Chord) KeyUp(kb uinput.Keyboard) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func listKeys(keymap *xkb.Keymap, keys map[string]Chord) {
|
||||||
|
for code := 1; code < 256; code++ {
|
||||||
|
for name, chord := range keys {
|
||||||
|
if chord.Key == code && (chord == Chord{Key: code}) {
|
||||||
|
fmt.Println(name, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, chord := range keys {
|
||||||
|
if chord.Key == code && (chord != Chord{Key: code}) {
|
||||||
|
fmt.Println(name, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func cutCmd(s, cmd string) (string, bool) {
|
func cutCmd(s, cmd string) (string, bool) {
|
||||||
if strings.HasPrefix(s, cmd + " ") || strings.HasPrefix(s, cmd + "\t") {
|
if strings.HasPrefix(s, cmd + " ") || strings.HasPrefix(s, cmd + "\t") {
|
||||||
return s[len(cmd)+1:], true
|
return s[len(cmd)+1:], true
|
||||||
|
@ -176,6 +217,29 @@ func cutCmd(s, cmd string) (string, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
var keymap *xkb.Keymap
|
||||||
|
{
|
||||||
|
names := xkb.RuleNames{
|
||||||
|
Rules: "",
|
||||||
|
Model: "",
|
||||||
|
Layout: os.Getenv("DOTOOL_XKB_LAYOUT"),
|
||||||
|
Variant: os.Getenv("DOTOOL_XKB_VARIANT"),
|
||||||
|
Options: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := xkb.ContextNew(xkb.ContextNoFlags)
|
||||||
|
defer ctx.Unref()
|
||||||
|
|
||||||
|
keymap = ctx.KeymapNewFromNames(&names, xkb.KeymapCompileNoFlags)
|
||||||
|
defer keymap.Unref()
|
||||||
|
|
||||||
|
if keymap == nil {
|
||||||
|
fatal("failed to compile keymap")
|
||||||
|
}
|
||||||
|
|
||||||
|
initKeys(keymap, names != xkb.RuleNames{})
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
optset := opt.NewOptionSet()
|
optset := opt.NewOptionSet()
|
||||||
|
|
||||||
|
@ -187,37 +251,13 @@ func main() {
|
||||||
optset.Alias("h", "help")
|
optset.Alias("h", "help")
|
||||||
|
|
||||||
optset.BoolFunc("list-keys", func(bool) error {
|
optset.BoolFunc("list-keys", func(bool) error {
|
||||||
for i := 1; i < 249; i++ {
|
listKeys(keymap, LinuxKeys)
|
||||||
for name, code := range linuxKeys {
|
|
||||||
if code == i {
|
|
||||||
fmt.Printf("%s %d\n", name, code)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
optset.BoolFunc("list-x-keys", func(bool) error {
|
optset.BoolFunc("list-x-keys", func(bool) error {
|
||||||
for i := 1; i < 249; i++ {
|
listKeys(keymap, XKeys)
|
||||||
for name, code := range xKeysNormal {
|
|
||||||
if code == i {
|
|
||||||
fmt.Print(name)
|
|
||||||
for name, code := range xKeysShifted {
|
|
||||||
if code == i {
|
|
||||||
if len(name) == 1 {
|
|
||||||
name = strings.ToUpper(name)
|
|
||||||
}
|
|
||||||
fmt.Printf(" %s", name)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf(" %d\n", code)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
@ -269,7 +309,7 @@ func main() {
|
||||||
}
|
}
|
||||||
if s, ok := cutCmd(text, "key"); ok {
|
if s, ok := cutCmd(text, "key"); ok {
|
||||||
for _, field := range strings.Fields(s) {
|
for _, field := range strings.Fields(s) {
|
||||||
if chord, err := parseChord(field); err == nil {
|
if chord, err := parseChord(keymap, field); err == nil {
|
||||||
chord.KeyDown(keyboard)
|
chord.KeyDown(keyboard)
|
||||||
time.Sleep(keyhold)
|
time.Sleep(keyhold)
|
||||||
chord.KeyUp(keyboard)
|
chord.KeyUp(keyboard)
|
||||||
|
@ -280,7 +320,7 @@ func main() {
|
||||||
}
|
}
|
||||||
} else if s, ok := cutCmd(text, "keydown"); ok {
|
} else if s, ok := cutCmd(text, "keydown"); ok {
|
||||||
for _, field := range strings.Fields(s) {
|
for _, field := range strings.Fields(s) {
|
||||||
if chord, err := parseChord(field); err == nil {
|
if chord, err := parseChord(keymap, field); err == nil {
|
||||||
chord.KeyDown(keyboard)
|
chord.KeyDown(keyboard)
|
||||||
} else {
|
} else {
|
||||||
warn(err.Error())
|
warn(err.Error())
|
||||||
|
@ -289,7 +329,7 @@ func main() {
|
||||||
}
|
}
|
||||||
} else if s, ok := cutCmd(text, "keyup"); ok {
|
} else if s, ok := cutCmd(text, "keyup"); ok {
|
||||||
for _, field := range strings.Fields(s) {
|
for _, field := range strings.Fields(s) {
|
||||||
if chord, err := parseChord(field); err == nil {
|
if chord, err := parseChord(keymap, field); err == nil {
|
||||||
chord.KeyUp(keyboard)
|
chord.KeyUp(keyboard)
|
||||||
} else {
|
} else {
|
||||||
warn(err.Error())
|
warn(err.Error())
|
||||||
|
@ -314,13 +354,18 @@ func main() {
|
||||||
}
|
}
|
||||||
} else if s, ok := cutCmd(text, "type"); ok {
|
} else if s, ok := cutCmd(text, "type"); ok {
|
||||||
for _, r := range s {
|
for _, r := range s {
|
||||||
if chord, ok := runeChords[unicode.ToLower(r)]; ok {
|
if sym := xkb.Utf32ToKeysym(uint32(r)); sym == 0 {
|
||||||
if unicode.IsUpper(r) {
|
warn("invalid character: " + string(r))
|
||||||
chord.Shift = true
|
} else {
|
||||||
|
chord := getChord(keymap, sym)
|
||||||
|
if chord.Key == 0 {
|
||||||
|
warn("impossible character for layout: " + string(r))
|
||||||
|
time.Sleep(typehold)
|
||||||
|
} else {
|
||||||
|
chord.KeyDown(keyboard)
|
||||||
|
time.Sleep(typehold)
|
||||||
|
chord.KeyUp(keyboard)
|
||||||
}
|
}
|
||||||
chord.KeyDown(keyboard)
|
|
||||||
time.Sleep(typehold)
|
|
||||||
chord.KeyUp(keyboard)
|
|
||||||
}
|
}
|
||||||
time.Sleep(typedelay)
|
time.Sleep(typedelay)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
# This can stop dotool typing guff if you are using a non-us keyboard layout.
|
|
||||||
input "18193:2069:dotool_keyboard" xkb_layout "us"
|
|
|
@ -1,12 +1,16 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# ./install.sh [DESTDIR] [BINDIR]
|
# ./install.sh [DESTDIR] [BINDIR]
|
||||||
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.2)}"
|
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.2)}"
|
||||||
|
|
||||||
go build -ldflags "-X main.Version=$DOTOOL_VERSION" || exit
|
go build -ldflags "-X main.Version=$DOTOOL_VERSION" || exit
|
||||||
mkdir -p "$1/${2:-usr/local/bin}" || exit
|
mkdir -p "$1/${2:-usr/local/bin}" || exit
|
||||||
cp -v dotool "$1/${2:-usr/local/bin}" || exit
|
cp -v dotool dotoolc dotoold "$1/${2:-usr/local/bin}" || exit
|
||||||
mkdir -p "$1/etc/udev/rules.d" || exit
|
mkdir -p "$1/etc/udev/rules.d" || exit
|
||||||
cp -v 80-dotool.rules "$1/etc/udev/rules.d" || exit
|
cp -v 80-dotool.rules "$1/etc/udev/rules.d" || exit
|
||||||
./_install.sh "$1" "$2" || exit
|
|
||||||
|
# Remove files from before the keyboard layout approach changed
|
||||||
|
rm -f "$1/usr/share/X11/xorg.conf.d/50-dotool.conf"
|
||||||
|
rm -f "$1/etc/sway/config.d/dotool"
|
||||||
|
|
||||||
# Make the new/updated udev rule effective
|
# Make the new/updated udev rule effective
|
||||||
udevadm control --reload
|
udevadm control --reload
|
||||||
|
|
145
xkb/xkb.go
Normal file
145
xkb/xkb.go
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
package xkb
|
||||||
|
|
||||||
|
//#cgo pkg-config: xkbcommon
|
||||||
|
//#cgo LDFLAGS: -ldl
|
||||||
|
//
|
||||||
|
//#include <stdlib.h>
|
||||||
|
//#include <xkbcommon/xkbcommon-compose.h>
|
||||||
|
//#include <xkbcommon/xkbcommon.h>
|
||||||
|
import "C"
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ModNameShift is the name of Shift Modifier
|
||||||
|
ModNameShift = "Shift"
|
||||||
|
// ModNameCaps is the name of Caps Lock Modifier
|
||||||
|
ModNameCaps = "Lock"
|
||||||
|
// ModNameCtrl is the name of Control Modifier
|
||||||
|
ModNameCtrl = "Control"
|
||||||
|
// ModNameAlt is the name of Alt Modifier
|
||||||
|
ModNameAlt = "Mod1"
|
||||||
|
// ModNameNum is the name of Num Lock Modifier
|
||||||
|
ModNameNum = "Mod2"
|
||||||
|
// ModNameLogo is the name of Logo Modifier
|
||||||
|
ModNameLogo = "Mod4"
|
||||||
|
// LedNameCaps is the name of Caps Lock Led
|
||||||
|
LedNameCaps = "Caps Lock"
|
||||||
|
// LedNameNum is the name of Num Lock Led
|
||||||
|
LedNameNum = "Num Lock"
|
||||||
|
// LedNameScroll is the name of Scroll Lock Led
|
||||||
|
LedNameScroll = "Scroll Lock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func KeysymGetName(keysym uint32) string {
|
||||||
|
s := "................................................................"
|
||||||
|
cs := C.CString(s)
|
||||||
|
defer C.free(unsafe.Pointer(cs))
|
||||||
|
_ = C.xkb_keysym_get_name(C.uint(keysym), cs, C.size_t(len(s)))
|
||||||
|
return C.GoString(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeysymToUtf32(keysym uint32) uint32 {
|
||||||
|
return uint32(C.xkb_keysym_to_utf32(C.uint(keysym)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Utf32ToKeysym(ucs uint32) uint32 {
|
||||||
|
return uint32(C.xkb_utf32_to_keysym(C.uint(ucs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeymapCompileFlags int
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeymapCompileNoFlags KeymapCompileFlags = C.XKB_KEYMAP_COMPILE_NO_FLAGS
|
||||||
|
)
|
||||||
|
|
||||||
|
type RuleNames struct {
|
||||||
|
Rules, Model, Layout, Variant, Options string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rn *RuleNames) toC() *C.struct_xkb_rule_names {
|
||||||
|
if rn == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &C.struct_xkb_rule_names{
|
||||||
|
rules: C.CString(rn.Rules),
|
||||||
|
model: C.CString(rn.Model),
|
||||||
|
layout: C.CString(rn.Layout),
|
||||||
|
variant: C.CString(rn.Variant),
|
||||||
|
options: C.CString(rn.Options),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContextNoFlags = 0
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
p *C.struct_xkb_context
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextNew(flags uint32) (ctx *Context) {
|
||||||
|
ctx = new(Context)
|
||||||
|
ctx.p = C.xkb_context_new(flags)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) KeymapNewFromNames(rules *RuleNames, flags KeymapCompileFlags) *Keymap {
|
||||||
|
km := C.xkb_keymap_new_from_names(ctx.p, rules.toC(), C.enum_xkb_keymap_compile_flags(flags))
|
||||||
|
if km == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Keymap{km}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) Unref() {
|
||||||
|
C.xkb_context_unref(ctx.p)
|
||||||
|
ctx.p = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Keymap struct {
|
||||||
|
p *C.struct_xkb_keymap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (km *Keymap) KeyGetMod(key, layout, level uint32) uint32 {
|
||||||
|
var mask C.uint
|
||||||
|
C.xkb_keymap_key_get_mods_for_level(km.p, C.uint(key), C.uint(layout), C.uint(level), &mask, 1)
|
||||||
|
return uint32(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (km *Keymap) KeyGetSymsByLevel(key, layout, level uint32) []uint32 {
|
||||||
|
var syms *C.xkb_keysym_t
|
||||||
|
n := int(C.xkb_keymap_key_get_syms_by_level(km.p, C.uint(key), C.uint(layout), C.uint(level), &syms))
|
||||||
|
if n == 0 || syms == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
data := (*[1 << 30]C.xkb_keysym_t)(unsafe.Pointer(syms))[:n:n]
|
||||||
|
s := make([]uint32, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
s[i] = uint32(data[i])
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (km *Keymap) MaxKeycode() uint32 {
|
||||||
|
return uint32(C.xkb_keymap_max_keycode(km.p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (km *Keymap) MinKeycode() uint32 {
|
||||||
|
return uint32(C.xkb_keymap_min_keycode(km.p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (km *Keymap) ModGetIndex(mod string) uint {
|
||||||
|
return uint(C.xkb_keymap_mod_get_index(km.p, C.CString(mod)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (km *Keymap) NumLevelsForKey(key, layout uint32) uint32 {
|
||||||
|
return uint32(C.xkb_keymap_num_levels_for_key(km.p, C.uint(key), C.uint(layout)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (km *Keymap) Unref() {
|
||||||
|
C.xkb_keymap_unref(km.p)
|
||||||
|
km.p = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func keymapUnref(km *Keymap) {
|
||||||
|
C.xkb_keymap_unref(km.p)
|
||||||
|
km.p = nil
|
||||||
|
}
|
42
xkeys.bash
42
xkeys.bash
|
@ -1,42 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
align() {
|
|
||||||
sed '/\<KeyRo\>/ d
|
|
||||||
/\<KeyKpjpcomma\>/ d
|
|
||||||
/\<KeyMacro\>/ d
|
|
||||||
/\<KeyYen\>/ d
|
|
||||||
/\<KeySetup\>/ d
|
|
||||||
/\<KeyDeletefile\>/ d
|
|
||||||
/\<KeyClosecd\>/ d
|
|
||||||
/\<KeyEjectclosecd\>/ d
|
|
||||||
/\<KeyIso\>/ d
|
|
||||||
/\<KeyMove\>/ d
|
|
||||||
/\<KeyEdit\>/ d
|
|
||||||
/\<KeyAlterase\>/ d
|
|
||||||
/\<KeyUnknown\>/ d
|
|
||||||
/\<KeyMicmute\>/ d'
|
|
||||||
}
|
|
||||||
|
|
||||||
normal="$({
|
|
||||||
paste -d ' ' <(xmodmap -pke | sed '1 d; s/.*= /"/; /.*=/ d; s/ .*/":/' | sed '/^"XF86Eject"/ { N; s/.*\n// }') \
|
|
||||||
<(go doc uinput.keyesc | sed '/Key/ !d; s/^\s*/uinput./; s/ .*/,/' | align) |
|
|
||||||
# Skip really non-matching section, we echo some of them below
|
|
||||||
sed '/^"XF86Tools"/,/^"XF86AudioPreset"/ d' |
|
|
||||||
# Remove duplicate keys
|
|
||||||
sed '/^"XF86Mail":.*Email/ d; /^"Cancel":.*Stop/ d; /^"XF86Send":.*file/ d; /^"Print":.*Sysrq/ d; /Key102Nd,$/ d'
|
|
||||||
|
|
||||||
echo '"XF86WebCam": uinput.KeyCamera,'
|
|
||||||
echo '"Print": uinput.KeyPrint,'
|
|
||||||
} | sed 's/^".*"/\L&/; s/^/\t/')"
|
|
||||||
|
|
||||||
printf %s\\n "var xKeysNormal = map[string]int{
|
|
||||||
$normal
|
|
||||||
}
|
|
||||||
"
|
|
||||||
|
|
||||||
echo 'var xKeysShifted = map[string]int{'
|
|
||||||
{
|
|
||||||
paste -d ' ' <(xmodmap -pke | sed '1 d; s/.*= /"/; /.*=/ d; s/\S* /"/; s/ .*/":/' | sed '/^"XF86Eject"/ { N; s/.*\n// }') \
|
|
||||||
<(go doc uinput.keyesc | sed '/Key/ !d; s/^\s*/uinput./; s/ .*/,/' | align) | sed '/^"NoSymbol"/ d; /^\S*_[LR]"/ d; /Key102Nd,$/ d'
|
|
||||||
} | sed 's/^".*"/\L&/; s/^/\t/' | awk 'NR == FNR {if (length($1) > 4) a[$1]; next} !($1 in a)' <(printf %s\\n "$normal") -
|
|
||||||
echo '}'
|
|
Loading…
Reference in a new issue