Compare commits
No commits in common. "master" and "1.3" have entirely different histories.
12 changed files with 174 additions and 469 deletions
|
@ -1,2 +1,2 @@
|
|||
# This allows users in group input to use dotool without root permissions.
|
||||
KERNEL=="uinput", GROUP="input", MODE="0620", OPTIONS+="static_node=uinput"
|
||||
KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"
|
||||
|
|
28
CHANGELOG.md
28
CHANGELOG.md
|
@ -2,38 +2,14 @@
|
|||
|
||||
Notable changes to dotool will be documented in this file.
|
||||
|
||||
## [1.5](https://git.sr.ht/~geb/dotool/refs/1.5)
|
||||
|
||||
### Fixed
|
||||
|
||||
- The selecting of the fewest modifiers for simulating keys.
|
||||
|
||||
## [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. (UPDATE: see 1.5)
|
||||
|
||||
## [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.
|
||||
|
|
65
README.md
65
README.md
|
@ -1,42 +1,65 @@
|
|||
# dotool
|
||||
|
||||
dotool reads actions from stdin and simulates keyboard/mouse input using
|
||||
Linux's uinput module. It works systemwide and supports keyboard layouts.
|
||||
|
||||
## Install From Packages
|
||||
|
||||
Packages of dotool are available on:
|
||||
|
||||
- [Alpine](https://pkgs.alpinelinux.org/packages?name=dotool)
|
||||
- [Arch (AUR)](https://aur.archlinux.org/packages?SeB=n&K=dotool)
|
||||
- [Nix](https://search.nixos.org/packages?channel=unstable&type=packages&query=dotool)
|
||||
- [Void](https://voidlinux.org/packages/?q=dotool)
|
||||
|
||||
and potentially other platforms.
|
||||
dotool reads commands from stdin and simulates keyboard and pointer events.
|
||||
It works everywhere on Linux, including in X11, Wayland and TTYs.
|
||||
|
||||
## Install From Source
|
||||
|
||||
With `go`, `libxkbcommon-dev` and `scdoc` installed, run:
|
||||
With `go` and `libxkbcommon-dev` installed, run:
|
||||
|
||||
./build.sh && sudo ./build.sh install
|
||||
sudo ./install.sh
|
||||
|
||||
And to trigger the udev rule, run:
|
||||
## Permission
|
||||
|
||||
sudo udevadm control --reload && sudo udevadm trigger
|
||||
dotool requires permission to `/dev/uinput` to create the virtual input
|
||||
devices, and a udev rule grants this to users in group input.
|
||||
|
||||
You could try:
|
||||
|
||||
echo type hello | dotool
|
||||
|
||||
and if need be, you can run:
|
||||
|
||||
sudo groupadd -f input
|
||||
sudo usermod -a -G input $USER
|
||||
|
||||
and re-login and trigger the udev rule or just reboot.
|
||||
|
||||
## Usage
|
||||
|
||||
See the [manpage](doc/dotool.1.scd).
|
||||
See `dotool --help`, but this greets the world:
|
||||
|
||||
echo 'type Sup, Lads!' | dotool
|
||||
|
||||
and this screams for three seconds:
|
||||
|
||||
{ echo keydown A; sleep 3; echo key H shift+1; } | dotool
|
||||
|
||||
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`.
|
||||
|
||||
dotoold &
|
||||
echo type super | dotoolc
|
||||
echo type speedy | dotoolc
|
||||
|
||||
## Keyboard Layouts
|
||||
|
||||
dotool will type 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
|
||||
|
||||
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).
|
||||
You can also send questions, thoughts or patches by composing an email to
|
||||
[~geb/public-inbox@lists.sr.ht](https://lists.sr.ht/~geb/public-inbox).
|
||||
|
||||
## Support My Work 👀
|
||||
## Support Me
|
||||
|
||||
[Thank you!](https://liberapay.com/geb)
|
||||
|
||||
|
|
22
build.sh
22
build.sh
|
@ -1,22 +0,0 @@
|
|||
#!/bin/sh
|
||||
# ./build.sh ['install']
|
||||
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.5)}"
|
||||
: "${DOTOOL_DESTDIR=}"
|
||||
: "${DOTOOL_BINDIR=usr/local/bin}"
|
||||
: "${DOTOOL_UDEV_RULES_DIR=etc/udev/rules.d}"
|
||||
|
||||
if [ "$*" != '' ] && [ "$*" != install ]; then
|
||||
echo bad usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ "$1" ]; then
|
||||
go build -ldflags "-X main.Version=$DOTOOL_VERSION" || exit
|
||||
echo Built Successfully.
|
||||
else
|
||||
install -Dm755 dotool dotoolc dotoold -t "$DOTOOL_DESTDIR/$DOTOOL_BINDIR" || exit
|
||||
install -Dm644 80-dotool.rules -t "$DOTOOL_DESTDIR/$DOTOOL_UDEV_RULES_DIR" || exit
|
||||
mkdir -p "$DOTOOL_DESTDIR/usr/share/man/man1" || exit
|
||||
scdoc < doc/dotool.1.scd > "$DOTOOL_DESTDIR/usr/share/man/man1/dotool.1" || exit
|
||||
echo Installed Successfully.
|
||||
fi
|
132
doc/dotool.1.scd
132
doc/dotool.1.scd
|
@ -1,132 +0,0 @@
|
|||
dotool(1)
|
||||
|
||||
# NAME
|
||||
|
||||
*dotool* - uinput tool
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
*dotool* < _actions_
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
*dotool* reads actions from stdin and simulates keyboard/mouse input using
|
||||
Linux's uinput module.
|
||||
|
||||
# PERMISSION
|
||||
|
||||
*dotool* requires write permission to */dev/uinput*, which is granted to
|
||||
users in group *input* by a udev rule.
|
||||
|
||||
You can test:
|
||||
|
||||
*echo type hello | dotool*
|
||||
|
||||
and if need be, you could add your user to group *input* with:
|
||||
|
||||
*groupadd -f input*++
|
||||
*usermod -a -G input $USER*
|
||||
|
||||
and then it's foolproof to reboot to make the group and rule effective.
|
||||
|
||||
# KEYBOARD LAYOUTS
|
||||
|
||||
*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:
|
||||
|
||||
*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
|
||||
|
||||
*-h*, *--help*
|
||||
Print help and exit.
|
||||
|
||||
*--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.
|
||||
|
||||
# ACTIONS
|
||||
|
||||
*key* _CHORD_...++
|
||||
*keydown* _CHORD_...++
|
||||
*keyup* _CHORD_...
|
||||
Press and/or release each _CHORD_. A _CHORD_ is a key or a key with
|
||||
modifiers, such as *a*, *shift+a* or *ctrl+shift+a*.
|
||||
|
||||
The supported modifiers are *super*, *altgr*, *ctrl*, *alt* and *shift*.
|
||||
|
||||
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. This example types *!!!* with the *us* layout:
|
||||
|
||||
*echo key shift+1 x:exclam shift+k:2 | dotool*
|
||||
|
||||
*type* _TEXT_
|
||||
Type _TEXT_.
|
||||
|
||||
*click* *left*/*middle*/*right*++
|
||||
*buttondown* *left*/*middle*/*right*++
|
||||
*buttonup* *left*/*middle*/*right*
|
||||
Press and/or release a mouse button.
|
||||
|
||||
*wheel* _AMOUNT_++
|
||||
*hwheel* _AMOUNT_
|
||||
Scroll a vertical/horizontal mouse wheel by a positive or negative
|
||||
_AMOUNT_.
|
||||
|
||||
*mouseto* _X_ _Y_
|
||||
Jump the cursor to the position _X_ _Y_, where _X_ and _Y_ are
|
||||
percentages between 0.0 and 1.0.
|
||||
|
||||
*mousemove* _X_ _Y_
|
||||
Move the cursor relative to its current position.
|
||||
|
||||
*keydelay* _MILLISECONDS_++
|
||||
*keyhold* _MILLISECONDS_++
|
||||
*typedelay* _MILLISECONDS_++
|
||||
*typehold* _MILLISECONDS_
|
||||
Set the delay between/holding each key with the *key*\* actions/*type*
|
||||
action.
|
||||
|
||||
The default *keydelay* and *typedelay* is 2ms, and the default
|
||||
*keyhold* and *typehold* is 8ms.
|
||||
|
||||
# LONG-RUNNING INSTANCE
|
||||
|
||||
Each instance of *dotool* has an initial delay registering the virtual
|
||||
devices, but you can keep writing actions to a long-running instance. The
|
||||
daemon and client, *dotoold* and *dotoolc*, let you do this with a pipe
|
||||
behind the scenes, for example:
|
||||
|
||||
*dotoold &*++
|
||||
*echo type super | dotoolc*++
|
||||
*echo type speedy | dotoolc*
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
This greets the world:
|
||||
|
||||
*echo type hi | dotool*
|
||||
|
||||
This screams for roughly three seconds:
|
||||
|
||||
*{ echo keydown A; sleep 3; echo key H shift+1; } | dotool*
|
||||
|
||||
# AUTHOR
|
||||
|
||||
John Gebbie
|
182
dotool.go
182
dotool.go
|
@ -18,9 +18,9 @@ import (
|
|||
var Version string
|
||||
|
||||
func usage() {
|
||||
fmt.Println(`dotool reads actions from stdin and simulates input using uinput.
|
||||
fmt.Println(`dotool reads commands from stdin and simulates keyboard and pointer events.
|
||||
|
||||
The supported actions are:
|
||||
The commands are:
|
||||
key CHORD...
|
||||
keydown CHORD...
|
||||
keyup CHORD...
|
||||
|
@ -28,20 +28,62 @@ The supported actions are:
|
|||
click left/middle/right
|
||||
buttondown left/middle/right
|
||||
buttonup left/middle/right
|
||||
wheel AMOUNT
|
||||
hwheel AMOUNT
|
||||
wheel AMOUNT (a positive AMOUNT is up, a negative is down)
|
||||
hwheel AMOUNT (a positive AMOUNT is right, a negative is left)
|
||||
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
|
||||
mousemove X Y (where X and Y are the number of pixels to move)
|
||||
keydelay MILLISECONDS (default: 2)
|
||||
keyhold MILLISECONDS (default: 8)
|
||||
typedelay MILLISECONDS (default: 2)
|
||||
typehold MILLISECONDS (default: 8)
|
||||
|
||||
--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.`)
|
||||
dotool is installed with a udev rule to allow users in group input to run
|
||||
it without root permissions.
|
||||
|
||||
You can add yourself to group input by running:
|
||||
|
||||
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.
|
||||
|
||||
echo key shift+1 x:exclam shift+k:2 | dotool
|
||||
|
||||
|
||||
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 type 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
|
||||
Print the possible Linux keys and exit.
|
||||
|
||||
--list-x-keys
|
||||
Print the possible XKB keys and exit.
|
||||
|
||||
--version
|
||||
Print the version and exit.`)
|
||||
}
|
||||
|
||||
func fatal(a ...any) {
|
||||
|
@ -62,8 +104,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) {
|
||||
|
@ -98,8 +138,6 @@ func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
|
|||
switch strings.ToLower(keys[i]) {
|
||||
case "super":
|
||||
c.Super = true
|
||||
case "altgr":
|
||||
c.AltGr = true
|
||||
case "ctrl", "control":
|
||||
c.Ctrl = true
|
||||
case "alt":
|
||||
|
@ -114,80 +152,55 @@ func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
|
|||
return c, nil
|
||||
}
|
||||
|
||||
func (c Chord) KeyDown(kb uinput.Keyboard) {
|
||||
func (c *Chord) KeyDown(kb uinput.Keyboard) {
|
||||
if c.Super {
|
||||
log(kb.KeyDown(super))
|
||||
log(kb.KeyDown(uinput.KeyLeftmeta))
|
||||
}
|
||||
if c.AltGr {
|
||||
log(kb.KeyDown(altgr))
|
||||
log(kb.KeyDown(84))
|
||||
}
|
||||
if c.Ctrl {
|
||||
log(kb.KeyDown(ctrl))
|
||||
log(kb.KeyDown(uinput.KeyLeftctrl))
|
||||
}
|
||||
if c.Alt {
|
||||
log(kb.KeyDown(alt))
|
||||
log(kb.KeyDown(uinput.KeyLeftalt))
|
||||
}
|
||||
if c.Shift {
|
||||
log(kb.KeyDown(shift))
|
||||
log(kb.KeyDown(uinput.KeyLeftshift))
|
||||
}
|
||||
log(kb.KeyDown(c.Key))
|
||||
}
|
||||
|
||||
func (c Chord) KeyUp(kb uinput.Keyboard) {
|
||||
func (c *Chord) KeyUp(kb uinput.Keyboard) {
|
||||
if c.Super {
|
||||
log(kb.KeyUp(super))
|
||||
log(kb.KeyUp(uinput.KeyLeftmeta))
|
||||
}
|
||||
if c.AltGr {
|
||||
log(kb.KeyUp(altgr))
|
||||
log(kb.KeyUp(84))
|
||||
}
|
||||
if c.Ctrl {
|
||||
log(kb.KeyUp(ctrl))
|
||||
log(kb.KeyUp(uinput.KeyLeftctrl))
|
||||
}
|
||||
if c.Alt {
|
||||
log(kb.KeyUp(alt))
|
||||
log(kb.KeyUp(uinput.KeyLeftalt))
|
||||
}
|
||||
if c.Shift {
|
||||
log(kb.KeyUp(shift))
|
||||
log(kb.KeyUp(uinput.KeyLeftshift))
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -232,47 +245,43 @@ func main() {
|
|||
}
|
||||
|
||||
{
|
||||
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.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)
|
||||
keyboard, err := uinput.CreateKeyboard("/dev/uinput", []byte("dotool keyboard"))
|
||||
if err != nil {
|
||||
fatal(err.Error())
|
||||
}
|
||||
|
@ -349,23 +358,18 @@ func main() {
|
|||
}
|
||||
} else if s, ok := cutWord(text, "type"); ok {
|
||||
for _, r := range s {
|
||||
sym := xkb.Utf32ToKeysym(uint32(r))
|
||||
if sym == 0 {
|
||||
if sym := xkb.Utf32ToKeysym(uint32(r)); sym == 0 {
|
||||
warn("invalid character: " + string(r))
|
||||
} else if c := getChord(keymap, sym); c.Key != 0 {
|
||||
c.KeyDown(keyboard)
|
||||
time.Sleep(typehold)
|
||||
c.KeyUp(keyboard)
|
||||
} else if c1, c2 := getDeadChords(keymap, r); c1.Key != 0 {
|
||||
c1.KeyDown(keyboard)
|
||||
time.Sleep(typehold)
|
||||
c1.KeyUp(keyboard)
|
||||
time.Sleep(typedelay)
|
||||
c2.KeyDown(keyboard)
|
||||
time.Sleep(typehold)
|
||||
c2.KeyUp(keyboard)
|
||||
} else {
|
||||
warn("impossible character for layout: " + string(r))
|
||||
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)
|
||||
}
|
||||
}
|
||||
time.Sleep(typedelay)
|
||||
}
|
||||
|
|
17
dotoold
17
dotoold
|
@ -1,15 +1,10 @@
|
|||
#!/bin/sh
|
||||
for a; do
|
||||
case "$a" in
|
||||
-h|--help)
|
||||
echo 'dotoold runs dotool reading from a pipe for dotoolc to write to. dotoold
|
||||
if [ $# != 0 ]; then
|
||||
echo 'dotoold runs dotool reading from a pipe for dotoolc to write to. dotoold
|
||||
will exit immediately if the pipe is already being read. The path used
|
||||
for the pipe is $DOTOOL_PIPE else /tmp/dotool-pipe.' >&2
|
||||
exit
|
||||
;;
|
||||
--) break;;
|
||||
esac
|
||||
done
|
||||
[ "$1" = -h ] || [ "$1" = --help ]; exit
|
||||
fi
|
||||
|
||||
fifo_being_read(){
|
||||
[ -p "$1" ] && /bin/echo 1<>"$1" >"$1"
|
||||
|
@ -25,5 +20,5 @@ fi
|
|||
rm -f -- "$p" || exit 1
|
||||
trap 'rm -f -- "$p"; pkill -P $$; trap - EXIT; exit' EXIT INT TERM HUP
|
||||
mkfifo -m 660 "$p" || exit 1
|
||||
dotool "$@" <> "$p" &
|
||||
wait $!
|
||||
dotool <> "$p" &
|
||||
wait
|
||||
|
|
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-20230911153257-e72225a1933c
|
||||
github.com/bendahl/uinput v1.7.0
|
||||
git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71
|
||||
github.com/bendahl/uinput v1.6.0
|
||||
)
|
||||
|
|
8
go.sum
8
go.sum
|
@ -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.7.0 h1:nA4fm8Wu8UYNOPykIZm66nkWEyvxzfmJ8YC02PM40jg=
|
||||
github.com/bendahl/uinput v1.7.0/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=
|
||||
|
|
17
install.sh
Executable file
17
install.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
# ./install.sh [DESTDIR] [BINDIR]
|
||||
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.3)}"
|
||||
|
||||
go build -ldflags "-X main.Version=$DOTOOL_VERSION" || exit
|
||||
mkdir -p "$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
|
||||
cp -v 80-dotool.rules "$1/etc/udev/rules.d" || 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
|
||||
udevadm control --reload
|
||||
udevadm trigger
|
165
keys.go
165
keys.go
|
@ -3,11 +3,8 @@ package main
|
|||
import (
|
||||
"git.sr.ht/~geb/dotool/xkb"
|
||||
"github.com/bendahl/uinput"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var super, altgr, ctrl, alt, shift int
|
||||
|
||||
var LinuxKeys = map[string]Chord{
|
||||
// Linux Only
|
||||
"micmute": Chord{Key: uinput.KeyMicmute},
|
||||
|
@ -484,137 +481,11 @@ var linuxXSyms = map[string]uint32{
|
|||
|
||||
var XKeys = map[string]Chord{}
|
||||
|
||||
type DeadKeyResult struct {
|
||||
DeadKey, Key string
|
||||
Result rune
|
||||
}
|
||||
|
||||
// this relies on a conventional keymap
|
||||
// this isn't exhaustive (e.g. acircumflexacute)
|
||||
var DeadKeyResults = []DeadKeyResult{
|
||||
{"dead_abovedot", "B", 'Ḃ'},
|
||||
{"dead_abovedot", "C", 'Ċ'},
|
||||
{"dead_abovedot", "D", 'Ḋ'},
|
||||
{"dead_abovedot", "E", 'Ė'},
|
||||
{"dead_abovedot", "F", 'Ḟ'},
|
||||
{"dead_abovedot", "G", 'Ġ'},
|
||||
{"dead_abovedot", "I", 'İ'},
|
||||
{"dead_abovedot", "M", 'Ṁ'},
|
||||
{"dead_abovedot", "P", 'Ṗ'},
|
||||
{"dead_abovedot", "S", 'Ṡ'},
|
||||
{"dead_abovedot", "T", 'Ṫ'},
|
||||
{"dead_abovedot", "X", 'Ẋ'},
|
||||
{"dead_abovedot", "Z", 'Ż'},
|
||||
{"dead_acute", "A", 'Á'},
|
||||
{"dead_acute", "C", 'Ć'},
|
||||
{"dead_acute", "E", 'É'},
|
||||
{"dead_acute", "I", 'Í'},
|
||||
{"dead_acute", "L", 'Ĺ'},
|
||||
{"dead_acute", "N", 'Ń'},
|
||||
{"dead_acute", "O", 'Ó'},
|
||||
{"dead_acute", "R", 'Ŕ'},
|
||||
{"dead_acute", "S", 'Ś'},
|
||||
{"dead_acute", "U", 'Ú'},
|
||||
{"dead_acute", "W", 'Ẃ'},
|
||||
{"dead_acute", "Y", 'Ý'},
|
||||
{"dead_acute", "Z", 'Ź'},
|
||||
{"dead_belowdot", "A", 'Ạ'},
|
||||
{"dead_belowdot", "E", 'Ẹ'},
|
||||
{"dead_belowdot", "I", 'Ị'},
|
||||
{"dead_belowdot", "L", 'Ḷ'},
|
||||
{"dead_belowdot", "O", 'Ọ'},
|
||||
{"dead_belowdot", "U", 'Ụ'},
|
||||
{"dead_belowdot", "Y", 'Ỵ'},
|
||||
{"dead_breve", "A", 'Ă'},
|
||||
{"dead_breve", "G", 'Ğ'},
|
||||
{"dead_breve", "I", 'Ĭ'},
|
||||
{"dead_breve", "U", 'Ŭ'},
|
||||
{"dead_caron", "C", 'Č'},
|
||||
{"dead_caron", "D", 'Ď'},
|
||||
{"dead_caron", "E", 'Ě'},
|
||||
{"dead_caron", "G", 'Ǧ'},
|
||||
{"dead_caron", "L", 'Ľ'},
|
||||
{"dead_caron", "N", 'Ň'},
|
||||
{"dead_caron", "O", 'Ǒ'},
|
||||
{"dead_caron", "R", 'Ř'},
|
||||
{"dead_caron", "S", 'Š'},
|
||||
{"dead_caron", "T", 'Ť'},
|
||||
{"dead_caron", "Z", 'Ž'},
|
||||
{"dead_cedilla", "C", 'Ç'},
|
||||
{"dead_cedilla", "G", 'Ģ'},
|
||||
{"dead_cedilla", "K", 'Ķ'},
|
||||
{"dead_cedilla", "L", 'Ļ'},
|
||||
{"dead_cedilla", "N", 'Ņ'},
|
||||
{"dead_cedilla", "R", 'Ŗ'},
|
||||
{"dead_cedilla", "S", 'Ş'},
|
||||
{"dead_cedilla", "T", 'Ţ'},
|
||||
{"dead_circumflex", "A", 'Â'},
|
||||
{"dead_circumflex", "C", 'Ĉ'},
|
||||
{"dead_circumflex", "E", 'Ê'},
|
||||
{"dead_circumflex", "G", 'Ĝ'},
|
||||
{"dead_circumflex", "H", 'Ĥ'},
|
||||
{"dead_circumflex", "I", 'Î'},
|
||||
{"dead_circumflex", "J", 'Ĵ'},
|
||||
{"dead_circumflex", "O", 'Ô'},
|
||||
{"dead_circumflex", "S", 'Ŝ'},
|
||||
{"dead_circumflex", "U", 'Û'},
|
||||
{"dead_circumflex", "W", 'Ŵ'},
|
||||
{"dead_circumflex", "Y", 'Ŷ'},
|
||||
{"dead_diaeresis", "A", 'Ä'},
|
||||
{"dead_diaeresis", "E", 'Ë'},
|
||||
{"dead_diaeresis", "I", 'Ï'},
|
||||
{"dead_diaeresis", "O", 'Ö'},
|
||||
{"dead_diaeresis", "U", 'Ü'},
|
||||
{"dead_diaeresis", "W", 'Ẅ'},
|
||||
{"dead_diaeresis", "Y", 'Ÿ'},
|
||||
{"dead_doubleacute", "O", 'Ő'},
|
||||
{"dead_doubleacute", "U", 'Ű'},
|
||||
{"dead_grave", "A", 'À'},
|
||||
{"dead_grave", "E", 'È'},
|
||||
{"dead_grave", "I", 'Ì'},
|
||||
{"dead_grave", "O", 'Ò'},
|
||||
{"dead_grave", "U", 'Ù'},
|
||||
{"dead_grave", "W", 'Ẁ'},
|
||||
{"dead_grave", "Y", 'Ỳ'},
|
||||
{"dead_hook", "A", 'Ả'},
|
||||
{"dead_hook", "E", 'Ẻ'},
|
||||
{"dead_hook", "I", 'Ỉ'},
|
||||
{"dead_hook", "O", 'Ỏ'},
|
||||
{"dead_hook", "U", 'Ủ'},
|
||||
{"dead_hook", "Y", 'Ỷ'},
|
||||
{"dead_horn", "O", 'Ơ'},
|
||||
{"dead_horn", "T", 'Þ'},
|
||||
{"dead_horn", "U", 'Ư'},
|
||||
{"dead_macron", "A", 'Ā'},
|
||||
{"dead_macron", "E", 'Ē'},
|
||||
{"dead_macron", "I", 'Ī'},
|
||||
{"dead_macron", "O", 'Ō'},
|
||||
{"dead_macron", "U", 'Ū'},
|
||||
{"dead_ogonek", "A", 'Ą'},
|
||||
{"dead_ogonek", "E", 'Ę'},
|
||||
{"dead_ogonek", "I", 'Į'},
|
||||
{"dead_ogonek", "U", 'Ų'},
|
||||
{"dead_stroke", "D", 'Đ'},
|
||||
{"dead_stroke", "H", 'Ħ'},
|
||||
{"dead_stroke", "L", 'Ł'},
|
||||
{"dead_stroke", "Z", 'Ƶ'},
|
||||
{"dead_tilde", "A", 'Ã'},
|
||||
{"dead_tilde", "E", 'Ẽ'},
|
||||
{"dead_tilde", "I", 'Ĩ'},
|
||||
{"dead_tilde", "N", 'Ñ'},
|
||||
{"dead_tilde", "O", 'Õ'},
|
||||
{"dead_tilde", "U", 'Ũ'},
|
||||
{"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 +493,6 @@ func newChord(keymap *xkb.Keymap, code, level uint32) Chord {
|
|||
(mask & altMask) != 0,
|
||||
(mask & shiftMask) != 0,
|
||||
int(code) - 8,
|
||||
level,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -632,26 +502,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 {
|
||||
LinuxKeys[name] = chord
|
||||
}
|
||||
LinuxKeys[name] = chord
|
||||
}
|
||||
}
|
||||
name := xkb.KeysymGetName(sym)
|
||||
if x, ok := XKeys[name]; !ok || level < x.level {
|
||||
XKeys[name] = chord
|
||||
}
|
||||
XKeys[xkb.KeysymGetName(sym)] = chord
|
||||
}
|
||||
}
|
||||
}
|
||||
super = XKeys["Super_L"].Key
|
||||
altgr = XKeys["ISO_Level3_Shift"].Key
|
||||
ctrl = XKeys["Control_L"].Key
|
||||
alt = XKeys["Alt_L"].Key
|
||||
shift = XKeys["Shift_L"].Key
|
||||
}
|
||||
|
||||
func getChord(keymap *xkb.Keymap, keysym uint32) Chord {
|
||||
|
@ -661,25 +521,10 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Chord{}
|
||||
}
|
||||
|
||||
func getDeadChords(keymap *xkb.Keymap, result rune) (Chord, Chord) {
|
||||
r := unicode.ToUpper(result)
|
||||
for _, d := range DeadKeyResults {
|
||||
if d.Result == r {
|
||||
deadKey := XKeys[d.DeadKey]
|
||||
key := XKeys[d.Key]
|
||||
if deadKey.Key != 0 && key.Key != 0 {
|
||||
key.Shift = d.Result == result
|
||||
return deadKey, key
|
||||
}
|
||||
}
|
||||
}
|
||||
return Chord{}, Chord{}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#!/bin/sh
|
||||
rm -vf /usr/local/bin/dotool /usr/local/bin/dotoolc /usr/local/bin/dotoold
|
||||
rm -vf /etc/udev/rules.d/80-dotool.rules
|
||||
rm -vf /usr/share/man/man1/dotool.1
|
||||
|
|
Loading…
Add table
Reference in a new issue