Compare commits

...

10 commits

Author SHA1 Message Date
John Gebbie
b5812c001d prefer keys on lower levels for realsies 2023-11-05 16:51:46 +00:00
John Gebbie
ba67589517 version 1.4 2023-10-26 14:33:08 +01:00
John Gebbie
5404859248 replace --keyboard-name with $DOTOOL_KEYBOARD_NAME 2023-10-26 13:13:48 +01:00
John Gebbie
ba251d1afd readme & man: tweak 2023-10-12 23:09:57 +01:00
John Gebbie
7688cc321c detail mouseto and mousemove usage 2023-09-20 10:37:02 +01:00
John Gebbie
39689f9b34 man: tweak example 2023-09-20 10:24:43 +01:00
John Gebbie
a169e2e131 bump dependencies 2023-09-13 11:55:19 +01:00
John Gebbie
f47908ebf2 tweak usage 2023-09-13 11:42:47 +01:00
John Gebbie
240298271c readme: public-inbox -> numen 2023-09-13 11:03:02 +01:00
John Gebbie
a580b0944b more verbose --list-keys output with modifiers 2023-09-13 11:03:02 +01:00
8 changed files with 95 additions and 50 deletions

View file

@ -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.

View file

@ -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 👀

View file

@ -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}"

View file

@ -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:

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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)
} }
} }
} }