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.
|
||||
|
||||
## [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.
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# dotool
|
||||
|
||||
dotool reads actions from stdin and simulates keyboard/mouse input using
|
||||
Linux's uinput module. It works system-wide, including in X11, Wayland
|
||||
and TTYs.
|
||||
Linux's uinput module. It works systemwide and supports keyboard layouts.
|
||||
|
||||
## 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.
|
||||
|
||||
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 👀
|
||||
|
||||
|
|
2
build.sh
2
build.sh
|
@ -1,6 +1,6 @@
|
|||
#!/bin/sh
|
||||
# ./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_BINDIR=usr/local/bin}"
|
||||
: "${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
|
||||
keyboard layout than your environment is expecting.
|
||||
|
||||
You can have *dotool* simulate keycodes for whatever layout by setting the
|
||||
environment variables *DOTOOL_XKB_LAYOUT* and *DOTOOL_XKB_VARIANT*. For
|
||||
example:
|
||||
You can specify the layout with 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
|
||||
|
@ -47,9 +50,6 @@ 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 Sup, Lads!' | dotool*
|
||||
*echo type hi | dotool*
|
||||
|
||||
This screams for roughly three seconds:
|
||||
|
||||
|
|
74
dotool.go
74
dotool.go
|
@ -18,7 +18,7 @@ import (
|
|||
var Version string
|
||||
|
||||
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:
|
||||
key CHORD...
|
||||
|
@ -30,14 +30,13 @@ The supported actions are:
|
|||
buttonup left/middle/right
|
||||
wheel AMOUNT
|
||||
hwheel AMOUNT
|
||||
mouseto X Y
|
||||
mousemove X Y
|
||||
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)
|
||||
keydelay MILLISECONDS
|
||||
keyhold MILLISECONDS
|
||||
typedelay 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-x-keys Print the possible XKB keys and exit.
|
||||
--version Print the version and exit.
|
||||
|
@ -63,6 +62,8 @@ 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) {
|
||||
|
@ -151,17 +152,42 @@ 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 && (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)
|
||||
if chord.Key == code {
|
||||
fmt.Printf("%-*s %s\n", margin, name, chord.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,49 +231,47 @@ func main() {
|
|||
initKeys(keymap)
|
||||
}
|
||||
|
||||
keyboardName := []byte("dotool keyboard")
|
||||
{
|
||||
optset := opt.NewOptionSet()
|
||||
o := opt.NewOptSet()
|
||||
|
||||
optset.FlagFunc("h", func() error {
|
||||
o.FlagFunc("h", func() error {
|
||||
usage()
|
||||
os.Exit(0)
|
||||
panic("unreachable")
|
||||
})
|
||||
optset.Alias("h", "help")
|
||||
o.Alias("h", "help")
|
||||
|
||||
optset.Func("keyboard-name", func(s string) error {
|
||||
keyboardName = []byte(s)
|
||||
return nil
|
||||
})
|
||||
|
||||
optset.FlagFunc("list-keys", func() error {
|
||||
o.FlagFunc("list-keys", func() error {
|
||||
listKeys(keymap, LinuxKeys)
|
||||
os.Exit(0)
|
||||
panic("unreachable")
|
||||
})
|
||||
|
||||
optset.FlagFunc("list-x-keys", func() error {
|
||||
o.FlagFunc("list-x-keys", func() error {
|
||||
listKeys(keymap, XKeys)
|
||||
os.Exit(0)
|
||||
panic("unreachable")
|
||||
})
|
||||
|
||||
optset.FlagFunc("version", func() error {
|
||||
o.FlagFunc("version", func() error {
|
||||
fmt.Println(Version)
|
||||
os.Exit(0)
|
||||
panic("unreachable")
|
||||
})
|
||||
|
||||
err := optset.Parse(true, os.Args[1:])
|
||||
err := o.Parse(true, os.Args[1:])
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
if len(optset.Args()) > 0 {
|
||||
if len(o.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
4
go.mod
|
@ -3,6 +3,6 @@ module git.sr.ht/~geb/dotool
|
|||
go 1.19
|
||||
|
||||
require (
|
||||
git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71
|
||||
github.com/bendahl/uinput v1.6.0
|
||||
git.sr.ht/~geb/opt v0.0.0-20230911153257-e72225a1933c
|
||||
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-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=
|
||||
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=
|
||||
|
|
14
keys.go
14
keys.go
|
@ -607,11 +607,14 @@ var DeadKeyResults = []DeadKeyResult{
|
|||
{"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")
|
||||
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,
|
||||
|
@ -619,6 +622,7 @@ func newChord(keymap *xkb.Keymap, mask, code uint32) Chord{
|
|||
(mask & altMask) != 0,
|
||||
(mask & shiftMask) != 0,
|
||||
int(code) - 8,
|
||||
level,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -628,16 +632,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, keymap.KeyGetMod(code, 0, level), code)
|
||||
chord := newChord(keymap, code, level)
|
||||
for name, s := range linuxXSyms {
|
||||
if s == sym {
|
||||
if _, ok := LinuxKeys[name]; !ok {
|
||||
if l, ok := LinuxKeys[name]; !ok || level < l.level {
|
||||
LinuxKeys[name] = chord
|
||||
}
|
||||
}
|
||||
}
|
||||
name := xkb.KeysymGetName(sym)
|
||||
if _, ok := XKeys[name]; !ok {
|
||||
if x, ok := XKeys[name]; !ok || level < x.level {
|
||||
XKeys[name] = chord
|
||||
}
|
||||
}
|
||||
|
@ -657,7 +661,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, keymap.KeyGetMod(code, 0, level), code)
|
||||
return newChord(keymap, code, level)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue