Compare commits

..

No commits in common. "b5812c001daeeaff1f259031661e47f3a612220c" and "a0049f668baa0bdc6578441a6dfe01d8d03928d1" have entirely different histories.

8 changed files with 52 additions and 97 deletions

View file

@ -2,32 +2,14 @@
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)
### Added
## Added
- Support for keyboard layouts.
- hwheel for horizontal scrolling.
### Changed
## Changed
- Now depends on the xkbcommon library.
- XKB key names are now case-sensitive.

View file

@ -1,7 +1,8 @@
# dotool
dotool reads actions from stdin and simulates keyboard/mouse input using
Linux's uinput module. It works systemwide and supports keyboard layouts.
Linux's uinput module. It works system-wide, including in X11, Wayland
and TTYs.
## Install From Packages
@ -34,7 +35,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.
You can also send questions or patches by composing an email to
[~geb/numen@lists.sr.ht](https://lists.sr.ht/~geb/numen).
[~geb/public-inbox@lists.sr.ht](https://lists.sr.ht/~geb/public-inbox).
## Support My Work 👀

View file

@ -1,6 +1,6 @@
#!/bin/sh
# ./build.sh ['install']
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.4)}"
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.3)}"
: "${DOTOOL_DESTDIR=}"
: "${DOTOOL_BINDIR=usr/local/bin}"
: "${DOTOOL_UDEV_RULES_DIR=etc/udev/rules.d}"

View file

@ -34,15 +34,12 @@ 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
keyboard layout than your environment is expecting.
You can specify the layout with the environment variables *DOTOOL_XKB_LAYOUT*
and *DOTOOL_XKB_VARIANT*. For example:
You can have *dotool* simulate keycodes for whatever layout by setting the
environment variables *DOTOOL_XKB_LAYOUT* and *DOTOOL_XKB_VARIANT*. For
example:
*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.
# OPTIONS
@ -50,6 +47,9 @@ Currently the *type* action has only heuristic support for dead keys.
*-h*, *--help*
Print help and exit.
*--keyboard-name=*_NAME_
Specify the name to give the virtual keyboard device.
*--list-keys*
Print the possible Linux keys and exit.
@ -121,7 +121,7 @@ behind the scenes, for example:
This greets the world:
*echo type hi | dotool*
*echo 'type Sup, Lads!' | dotool*
This screams for roughly three seconds:

View file

@ -18,7 +18,7 @@ import (
var Version string
func usage() {
fmt.Println(`dotool reads actions from stdin and simulates input using uinput.
fmt.Println(`dotool reads actions from stdin and simulates keyboard/mouse input using uinput.
The supported actions are:
key CHORD...
@ -30,16 +30,17 @@ The supported actions are:
buttonup left/middle/right
wheel AMOUNT
hwheel AMOUNT
mouseto X Y (where X and Y are percentages between 0.0 and 1.0)
mousemove X Y (where X and Y are amounts to move)
mouseto X Y
mousemove X Y
keydelay MILLISECONDS
keyhold MILLISECONDS
typedelay MILLISECONDS
typehold MILLISECONDS
--list-keys Print the possible Linux keys and exit.
--list-x-keys Print the possible XKB keys and exit.
--version Print the version and exit.
--keyboard-name=NAME Specify the name to give the virtual keyboard device.
--list-keys Print the possible Linux keys and exit.
--list-x-keys Print the possible XKB keys and exit.
--version Print the version and exit.
See 'man dotool' for the documentation.`)
}
@ -62,8 +63,6 @@ func log(err error) {
type Chord struct {
Super, AltGr, Ctrl, Alt, Shift bool
Key int
level uint32 // just for initKeys
}
func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
@ -152,42 +151,17 @@ func (c Chord) KeyUp(kb uinput.Keyboard) {
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) {
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 name, chord := range keys {
if chord.Key == code {
fmt.Printf("%-*s %s\n", margin, name, chord.String())
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)
}
}
}
@ -231,47 +205,49 @@ func main() {
initKeys(keymap)
}
keyboardName := []byte("dotool keyboard")
{
o := opt.NewOptSet()
optset := opt.NewOptionSet()
o.FlagFunc("h", func() error {
optset.FlagFunc("h", func() error {
usage()
os.Exit(0)
panic("unreachable")
})
o.Alias("h", "help")
optset.Alias("h", "help")
o.FlagFunc("list-keys", func() error {
optset.Func("keyboard-name", func(s string) error {
keyboardName = []byte(s)
return nil
})
optset.FlagFunc("list-keys", func() error {
listKeys(keymap, LinuxKeys)
os.Exit(0)
panic("unreachable")
})
o.FlagFunc("list-x-keys", func() error {
optset.FlagFunc("list-x-keys", func() error {
listKeys(keymap, XKeys)
os.Exit(0)
panic("unreachable")
})
o.FlagFunc("version", func() error {
optset.FlagFunc("version", func() error {
fmt.Println(Version)
os.Exit(0)
panic("unreachable")
})
err := o.Parse(true, os.Args[1:])
err := optset.Parse(true, os.Args[1:])
if err != nil {
fatal(err.Error())
}
if len(o.Args()) > 0 {
if len(optset.Args()) > 0 {
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)
if err != nil {
fatal(err.Error())

4
go.mod
View file

@ -3,6 +3,6 @@ module git.sr.ht/~geb/dotool
go 1.19
require (
git.sr.ht/~geb/opt v0.0.0-20230911153257-e72225a1933c
github.com/bendahl/uinput v1.6.2
git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71
github.com/bendahl/uinput v1.6.0
)

8
go.sum
View file

@ -1,4 +1,4 @@
git.sr.ht/~geb/opt v0.0.0-20230911153257-e72225a1933c h1:gIC1gnCgoasPHks1x6MB+bgDmIWMxKc5HIJPJrsV5Ck=
git.sr.ht/~geb/opt v0.0.0-20230911153257-e72225a1933c/go.mod h1:S6h1g8P7DyG7i7YIHZ5IpYbC6lzZB9DYIEl8PyXOmsg=
github.com/bendahl/uinput v1.6.2 h1:tIz52QyKDx1i1nObUkts3AZa/bULfLhPA5a+xKGlRPI=
github.com/bendahl/uinput v1.6.2/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=
git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71 h1:jh3Ite7R1ZvdLt6j52e4njO2SS/z5dWOLXllw7inalc=
git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71/go.mod h1:S6h1g8P7DyG7i7YIHZ5IpYbC6lzZB9DYIEl8PyXOmsg=
github.com/bendahl/uinput v1.6.0 h1:fM6r3OSC17rHh758mizKjSBuqi+XinhiGd4N3pWvZiI=
github.com/bendahl/uinput v1.6.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=

14
keys.go
View file

@ -607,14 +607,11 @@ var DeadKeyResults = []DeadKeyResult{
{"dead_tilde", "Y", 'Ỹ'},
}
func newChord(keymap *xkb.Keymap, code, level uint32) Chord {
func newChord(keymap *xkb.Keymap, mask, code uint32) Chord{
altGrMask := uint32(1) << keymap.ModGetIndex("Mod5")
ctrlMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameCtrl)
altMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameAlt)
shiftMask := uint32(1) << keymap.ModGetIndex(xkb.ModNameShift)
// TODO support layouts other than 0
mask := keymap.KeyGetMod(code, 0, level)
return Chord{
false,
(mask & altGrMask) != 0,
@ -622,7 +619,6 @@ func newChord(keymap *xkb.Keymap, code, level uint32) Chord {
(mask & altMask) != 0,
(mask & shiftMask) != 0,
int(code) - 8,
level,
}
}
@ -632,16 +628,16 @@ func initKeys(keymap *xkb.Keymap) {
numLevels := keymap.NumLevelsForKey(code, 0)
for level := uint32(0); level < numLevels; level++ {
for _, sym := range keymap.KeyGetSymsByLevel(code, 0, level) {
chord := newChord(keymap, code, level)
chord := newChord(keymap, keymap.KeyGetMod(code, 0, level), code)
for name, s := range linuxXSyms {
if s == sym {
if l, ok := LinuxKeys[name]; !ok || level < l.level {
if _, ok := LinuxKeys[name]; !ok {
LinuxKeys[name] = chord
}
}
}
name := xkb.KeysymGetName(sym)
if x, ok := XKeys[name]; !ok || level < x.level {
if _, ok := XKeys[name]; !ok {
XKeys[name] = chord
}
}
@ -661,7 +657,7 @@ func getChord(keymap *xkb.Keymap, keysym uint32) Chord {
for level := uint32(0); level < numLevels; level++ {
for _, sym := range keymap.KeyGetSymsByLevel(code, 0, level) {
if sym == keysym {
return newChord(keymap, code, level)
return newChord(keymap, keymap.KeyGetMod(code, 0, level), code)
}
}
}