Compare commits
10 commits
a0049f668b
...
b5812c001d
Author | SHA1 | Date | |
---|---|---|---|
|
b5812c001d | ||
|
ba67589517 | ||
|
5404859248 | ||
|
ba251d1afd | ||
|
7688cc321c | ||
|
39689f9b34 | ||
|
a169e2e131 | ||
|
f47908ebf2 | ||
|
240298271c | ||
|
a580b0944b |
8 changed files with 95 additions and 50 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -2,14 +2,32 @@
|
||||||
|
|
||||||
Notable changes to dotool will be documented in this file.
|
Notable changes to dotool will be documented in this file.
|
||||||
|
|
||||||
|
## [1.4](https://git.sr.ht/~geb/dotool/refs/1.4)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- A manpage, requiring scdoc.
|
||||||
|
- Heuristic support for dead keys.
|
||||||
|
- Support for altgr.
|
||||||
|
- $DOTOOL_KEYBOARD_NAME to set the virtual keyboard's name.
|
||||||
|
- More verbose --list-keys output.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Replaced ./install.sh with ./build.sh.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Now prefers the fewest modifiers for simulating keys.
|
||||||
|
|
||||||
## [1.3](https://git.sr.ht/~geb/dotool/refs/1.3)
|
## [1.3](https://git.sr.ht/~geb/dotool/refs/1.3)
|
||||||
|
|
||||||
## Added
|
### Added
|
||||||
|
|
||||||
- Support for keyboard layouts.
|
- Support for keyboard layouts.
|
||||||
- hwheel for horizontal scrolling.
|
- hwheel for horizontal scrolling.
|
||||||
|
|
||||||
## Changed
|
### Changed
|
||||||
|
|
||||||
- Now depends on the xkbcommon library.
|
- Now depends on the xkbcommon library.
|
||||||
- XKB key names are now case-sensitive.
|
- XKB key names are now case-sensitive.
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
# dotool
|
# dotool
|
||||||
|
|
||||||
dotool reads actions from stdin and simulates keyboard/mouse input using
|
dotool reads actions from stdin and simulates keyboard/mouse input using
|
||||||
Linux's uinput module. It works system-wide, including in X11, Wayland
|
Linux's uinput module. It works systemwide and supports keyboard layouts.
|
||||||
and TTYs.
|
|
||||||
|
|
||||||
## Install From Packages
|
## Install From Packages
|
||||||
|
|
||||||
|
@ -35,7 +34,7 @@ dotool was written for [Numen](https://numenvoice.org), which has a
|
||||||
[chat on Matrix](https://matrix.to/#/#numen:matrix.org) you're welcome to join.
|
[chat on Matrix](https://matrix.to/#/#numen:matrix.org) you're welcome to join.
|
||||||
|
|
||||||
You can also send questions or patches by composing an email to
|
You can also send questions or patches by composing an email to
|
||||||
[~geb/public-inbox@lists.sr.ht](https://lists.sr.ht/~geb/public-inbox).
|
[~geb/numen@lists.sr.ht](https://lists.sr.ht/~geb/numen).
|
||||||
|
|
||||||
## Support My Work 👀
|
## Support My Work 👀
|
||||||
|
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# ./build.sh ['install']
|
# ./build.sh ['install']
|
||||||
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.3)}"
|
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.4)}"
|
||||||
: "${DOTOOL_DESTDIR=}"
|
: "${DOTOOL_DESTDIR=}"
|
||||||
: "${DOTOOL_BINDIR=usr/local/bin}"
|
: "${DOTOOL_BINDIR=usr/local/bin}"
|
||||||
: "${DOTOOL_UDEV_RULES_DIR=etc/udev/rules.d}"
|
: "${DOTOOL_UDEV_RULES_DIR=etc/udev/rules.d}"
|
||||||
|
|
|
@ -34,12 +34,15 @@ and then it's foolproof to reboot to make the group and rule effective.
|
||||||
*dotool* may type gobbledygook if it's simulating keycodes for a different
|
*dotool* may type gobbledygook if it's simulating keycodes for a different
|
||||||
keyboard layout than your environment is expecting.
|
keyboard layout than your environment is expecting.
|
||||||
|
|
||||||
You can have *dotool* simulate keycodes for whatever layout by setting the
|
You can specify the layout with the environment variables *DOTOOL_XKB_LAYOUT*
|
||||||
environment variables *DOTOOL_XKB_LAYOUT* and *DOTOOL_XKB_VARIANT*. For
|
and *DOTOOL_XKB_VARIANT*. For example:
|
||||||
example:
|
|
||||||
|
|
||||||
*echo type azerty | DOTOOL_XKB_LAYOUT=fr dotool*
|
*echo type azerty | DOTOOL_XKB_LAYOUT=fr dotool*
|
||||||
|
|
||||||
|
You can also specify the name to give the virtual keyboard with the environment
|
||||||
|
variable *DOTOOL_KEYBOARD_NAME*, which can be useful making rules for your
|
||||||
|
environment.
|
||||||
|
|
||||||
Currently the *type* action has only heuristic support for dead keys.
|
Currently the *type* action has only heuristic support for dead keys.
|
||||||
|
|
||||||
# OPTIONS
|
# OPTIONS
|
||||||
|
@ -47,9 +50,6 @@ Currently the *type* action has only heuristic support for dead keys.
|
||||||
*-h*, *--help*
|
*-h*, *--help*
|
||||||
Print help and exit.
|
Print help and exit.
|
||||||
|
|
||||||
*--keyboard-name=*_NAME_
|
|
||||||
Specify the name to give the virtual keyboard device.
|
|
||||||
|
|
||||||
*--list-keys*
|
*--list-keys*
|
||||||
Print the possible Linux keys and exit.
|
Print the possible Linux keys and exit.
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ behind the scenes, for example:
|
||||||
|
|
||||||
This greets the world:
|
This greets the world:
|
||||||
|
|
||||||
*echo 'type Sup, Lads!' | dotool*
|
*echo type hi | dotool*
|
||||||
|
|
||||||
This screams for roughly three seconds:
|
This screams for roughly three seconds:
|
||||||
|
|
||||||
|
|
74
dotool.go
74
dotool.go
|
@ -18,7 +18,7 @@ import (
|
||||||
var Version string
|
var Version string
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Println(`dotool reads actions from stdin and simulates keyboard/mouse input using uinput.
|
fmt.Println(`dotool reads actions from stdin and simulates input using uinput.
|
||||||
|
|
||||||
The supported actions are:
|
The supported actions are:
|
||||||
key CHORD...
|
key CHORD...
|
||||||
|
@ -30,14 +30,13 @@ The supported actions are:
|
||||||
buttonup left/middle/right
|
buttonup left/middle/right
|
||||||
wheel AMOUNT
|
wheel AMOUNT
|
||||||
hwheel AMOUNT
|
hwheel AMOUNT
|
||||||
mouseto X Y
|
mouseto X Y (where X and Y are percentages between 0.0 and 1.0)
|
||||||
mousemove X Y
|
mousemove X Y (where X and Y are amounts to move)
|
||||||
keydelay MILLISECONDS
|
keydelay MILLISECONDS
|
||||||
keyhold MILLISECONDS
|
keyhold MILLISECONDS
|
||||||
typedelay MILLISECONDS
|
typedelay MILLISECONDS
|
||||||
typehold MILLISECONDS
|
typehold MILLISECONDS
|
||||||
|
|
||||||
--keyboard-name=NAME Specify the name to give the virtual keyboard device.
|
|
||||||
--list-keys Print the possible Linux keys and exit.
|
--list-keys Print the possible Linux keys and exit.
|
||||||
--list-x-keys Print the possible XKB keys and exit.
|
--list-x-keys Print the possible XKB keys and exit.
|
||||||
--version Print the version and exit.
|
--version Print the version and exit.
|
||||||
|
@ -63,6 +62,8 @@ func log(err error) {
|
||||||
type Chord struct {
|
type Chord struct {
|
||||||
Super, AltGr, Ctrl, Alt, Shift bool
|
Super, AltGr, Ctrl, Alt, Shift bool
|
||||||
Key int
|
Key int
|
||||||
|
|
||||||
|
level uint32 // just for initKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
|
func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
|
||||||
|
@ -151,17 +152,42 @@ func (c Chord) KeyUp(kb uinput.Keyboard) {
|
||||||
log(kb.KeyUp(c.Key))
|
log(kb.KeyUp(c.Key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Chord) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
if c.Super {
|
||||||
|
sb.WriteString("super+")
|
||||||
|
}
|
||||||
|
if c.AltGr {
|
||||||
|
sb.WriteString("altgr+")
|
||||||
|
}
|
||||||
|
if c.Ctrl {
|
||||||
|
sb.WriteString("ctrl+")
|
||||||
|
}
|
||||||
|
if c.Alt {
|
||||||
|
sb.WriteString("alt+")
|
||||||
|
}
|
||||||
|
if c.Shift {
|
||||||
|
sb.WriteString("shift+")
|
||||||
|
}
|
||||||
|
sb.WriteString("k:")
|
||||||
|
sb.WriteString(strconv.Itoa(c.Key))
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func listKeys(keymap *xkb.Keymap, keys map[string]Chord) {
|
func listKeys(keymap *xkb.Keymap, keys map[string]Chord) {
|
||||||
|
var margin int
|
||||||
|
for code := 1; code < 256; code++ {
|
||||||
|
for name := range keys {
|
||||||
|
if len(name) > margin {
|
||||||
|
margin = len(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for code := 1; code < 256; code++ {
|
for code := 1; code < 256; code++ {
|
||||||
for name, chord := range keys {
|
for name, chord := range keys {
|
||||||
if chord.Key == code && (chord == Chord{Key: code}) {
|
if chord.Key == code {
|
||||||
fmt.Println(name, code)
|
fmt.Printf("%-*s %s\n", margin, name, chord.String())
|
||||||
}
|
|
||||||
}
|
|
||||||
for name, chord := range keys {
|
|
||||||
if chord.Key == code && (chord != Chord{Key: code}) {
|
|
||||||
fmt.Println(name, code)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,49 +231,47 @@ func main() {
|
||||||
initKeys(keymap)
|
initKeys(keymap)
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboardName := []byte("dotool keyboard")
|
|
||||||
{
|
{
|
||||||
optset := opt.NewOptionSet()
|
o := opt.NewOptSet()
|
||||||
|
|
||||||
optset.FlagFunc("h", func() error {
|
o.FlagFunc("h", func() error {
|
||||||
usage()
|
usage()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
optset.Alias("h", "help")
|
o.Alias("h", "help")
|
||||||
|
|
||||||
optset.Func("keyboard-name", func(s string) error {
|
o.FlagFunc("list-keys", func() error {
|
||||||
keyboardName = []byte(s)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
optset.FlagFunc("list-keys", func() error {
|
|
||||||
listKeys(keymap, LinuxKeys)
|
listKeys(keymap, LinuxKeys)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
optset.FlagFunc("list-x-keys", func() error {
|
o.FlagFunc("list-x-keys", func() error {
|
||||||
listKeys(keymap, XKeys)
|
listKeys(keymap, XKeys)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
optset.FlagFunc("version", func() error {
|
o.FlagFunc("version", func() error {
|
||||||
fmt.Println(Version)
|
fmt.Println(Version)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
err := optset.Parse(true, os.Args[1:])
|
err := o.Parse(true, os.Args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal(err.Error())
|
fatal(err.Error())
|
||||||
}
|
}
|
||||||
if len(optset.Args()) > 0 {
|
if len(o.Args()) > 0 {
|
||||||
fatal("there should be no arguments, commands are read from stdin")
|
fatal("there should be no arguments, commands are read from stdin")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyboardName := []byte(os.Getenv("DOTOOL_KEYBOARD_NAME"))
|
||||||
|
if len(keyboardName) == 0 {
|
||||||
|
keyboardName = []byte("dotool keyboard")
|
||||||
|
}
|
||||||
keyboard, err := uinput.CreateKeyboard("/dev/uinput", keyboardName)
|
keyboard, err := uinput.CreateKeyboard("/dev/uinput", keyboardName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatal(err.Error())
|
fatal(err.Error())
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -3,6 +3,6 @@ module git.sr.ht/~geb/dotool
|
||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71
|
git.sr.ht/~geb/opt v0.0.0-20230911153257-e72225a1933c
|
||||||
github.com/bendahl/uinput v1.6.0
|
github.com/bendahl/uinput v1.6.2
|
||||||
)
|
)
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -1,4 +1,4 @@
|
||||||
git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71 h1:jh3Ite7R1ZvdLt6j52e4njO2SS/z5dWOLXllw7inalc=
|
git.sr.ht/~geb/opt v0.0.0-20230911153257-e72225a1933c h1:gIC1gnCgoasPHks1x6MB+bgDmIWMxKc5HIJPJrsV5Ck=
|
||||||
git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71/go.mod h1:S6h1g8P7DyG7i7YIHZ5IpYbC6lzZB9DYIEl8PyXOmsg=
|
git.sr.ht/~geb/opt v0.0.0-20230911153257-e72225a1933c/go.mod h1:S6h1g8P7DyG7i7YIHZ5IpYbC6lzZB9DYIEl8PyXOmsg=
|
||||||
github.com/bendahl/uinput v1.6.0 h1:fM6r3OSC17rHh758mizKjSBuqi+XinhiGd4N3pWvZiI=
|
github.com/bendahl/uinput v1.6.2 h1:tIz52QyKDx1i1nObUkts3AZa/bULfLhPA5a+xKGlRPI=
|
||||||
github.com/bendahl/uinput v1.6.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=
|
github.com/bendahl/uinput v1.6.2/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=
|
||||||
|
|
14
keys.go
14
keys.go
|
@ -607,11 +607,14 @@ var DeadKeyResults = []DeadKeyResult{
|
||||||
{"dead_tilde", "Y", 'Ỹ'},
|
{"dead_tilde", "Y", 'Ỹ'},
|
||||||
}
|
}
|
||||||
|
|
||||||
func newChord(keymap *xkb.Keymap, mask, code uint32) Chord{
|
func newChord(keymap *xkb.Keymap, code, level uint32) Chord {
|
||||||
altGrMask := uint32(1) << keymap.ModGetIndex("Mod5")
|
altGrMask := uint32(1) << keymap.ModGetIndex("Mod5")
|
||||||
ctrlMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameCtrl)
|
ctrlMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameCtrl)
|
||||||
altMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameAlt)
|
altMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameAlt)
|
||||||
shiftMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameShift)
|
shiftMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameShift)
|
||||||
|
|
||||||
|
// TODO support layouts other than 0
|
||||||
|
mask := keymap.KeyGetMod(code, 0, level)
|
||||||
return Chord{
|
return Chord{
|
||||||
false,
|
false,
|
||||||
(mask & altGrMask) != 0,
|
(mask & altGrMask) != 0,
|
||||||
|
@ -619,6 +622,7 @@ func newChord(keymap *xkb.Keymap, mask, code uint32) Chord{
|
||||||
(mask & altMask) != 0,
|
(mask & altMask) != 0,
|
||||||
(mask & shiftMask) != 0,
|
(mask & shiftMask) != 0,
|
||||||
int(code) - 8,
|
int(code) - 8,
|
||||||
|
level,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,16 +632,16 @@ func initKeys(keymap *xkb.Keymap) {
|
||||||
numLevels := keymap.NumLevelsForKey(code, 0)
|
numLevels := keymap.NumLevelsForKey(code, 0)
|
||||||
for level := uint32(0); level < numLevels; level++ {
|
for level := uint32(0); level < numLevels; level++ {
|
||||||
for _, sym := range keymap.KeyGetSymsByLevel(code, 0, level) {
|
for _, sym := range keymap.KeyGetSymsByLevel(code, 0, level) {
|
||||||
chord := newChord(keymap, keymap.KeyGetMod(code, 0, level), code)
|
chord := newChord(keymap, code, level)
|
||||||
for name, s := range linuxXSyms {
|
for name, s := range linuxXSyms {
|
||||||
if s == sym {
|
if s == sym {
|
||||||
if _, ok := LinuxKeys[name]; !ok {
|
if l, ok := LinuxKeys[name]; !ok || level < l.level {
|
||||||
LinuxKeys[name] = chord
|
LinuxKeys[name] = chord
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
name := xkb.KeysymGetName(sym)
|
name := xkb.KeysymGetName(sym)
|
||||||
if _, ok := XKeys[name]; !ok {
|
if x, ok := XKeys[name]; !ok || level < x.level {
|
||||||
XKeys[name] = chord
|
XKeys[name] = chord
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -657,7 +661,7 @@ func getChord(keymap *xkb.Keymap, keysym uint32) Chord {
|
||||||
for level := uint32(0); level < numLevels; level++ {
|
for level := uint32(0); level < numLevels; level++ {
|
||||||
for _, sym := range keymap.KeyGetSymsByLevel(code, 0, level) {
|
for _, sym := range keymap.KeyGetSymsByLevel(code, 0, level) {
|
||||||
if sym == keysym {
|
if sym == keysym {
|
||||||
return newChord(keymap, keymap.KeyGetMod(code, 0, level), code)
|
return newChord(keymap, code, level)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue