Compare commits

..

No commits in common. "master" and "1.3" have entirely different histories.
master ... 1.3

12 changed files with 174 additions and 469 deletions

View file

@ -1,2 +1,2 @@
# This allows users in group input to use dotool without root permissions. # 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"

View file

@ -2,38 +2,14 @@
Notable changes to dotool will be documented in this file. 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) ## [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,42 +1,65 @@
# dotool # dotool
dotool reads actions from stdin and simulates keyboard/mouse input using dotool reads commands from stdin and simulates keyboard and pointer events.
Linux's uinput module. It works systemwide and supports keyboard layouts. It works everywhere on Linux, including in X11, Wayland and TTYs.
## 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.
## Install From Source ## 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 ## 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 ## Numen and Contact
dotool was written for [Numen](https://numenvoice.org), which has a 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, thoughts 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 👀 ## Support Me
[Thank you!](https://liberapay.com/geb) [Thank you!](https://liberapay.com/geb)

View file

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

View file

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

@ -18,9 +18,9 @@ import (
var Version string var Version string
func usage() { 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... key CHORD...
keydown CHORD... keydown CHORD...
keyup CHORD... keyup CHORD...
@ -28,20 +28,62 @@ The supported actions are:
click left/middle/right click left/middle/right
buttondown left/middle/right buttondown left/middle/right
buttonup left/middle/right buttonup left/middle/right
wheel AMOUNT wheel AMOUNT (a positive AMOUNT is up, a negative is down)
hwheel AMOUNT 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) 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) mousemove X Y (where X and Y are the number of pixels to move)
keydelay MILLISECONDS keydelay MILLISECONDS (default: 2)
keyhold MILLISECONDS keyhold MILLISECONDS (default: 8)
typedelay MILLISECONDS typedelay MILLISECONDS (default: 2)
typehold MILLISECONDS 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) { func fatal(a ...any) {
@ -62,8 +104,6 @@ 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) {
@ -98,8 +138,6 @@ func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
switch strings.ToLower(keys[i]) { switch strings.ToLower(keys[i]) {
case "super": case "super":
c.Super = true c.Super = true
case "altgr":
c.AltGr = true
case "ctrl", "control": case "ctrl", "control":
c.Ctrl = true c.Ctrl = true
case "alt": case "alt":
@ -114,80 +152,55 @@ func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
return c, nil return c, nil
} }
func (c Chord) KeyDown(kb uinput.Keyboard) { func (c *Chord) KeyDown(kb uinput.Keyboard) {
if c.Super { if c.Super {
log(kb.KeyDown(super)) log(kb.KeyDown(uinput.KeyLeftmeta))
} }
if c.AltGr { if c.AltGr {
log(kb.KeyDown(altgr)) log(kb.KeyDown(84))
} }
if c.Ctrl { if c.Ctrl {
log(kb.KeyDown(ctrl)) log(kb.KeyDown(uinput.KeyLeftctrl))
} }
if c.Alt { if c.Alt {
log(kb.KeyDown(alt)) log(kb.KeyDown(uinput.KeyLeftalt))
} }
if c.Shift { if c.Shift {
log(kb.KeyDown(shift)) log(kb.KeyDown(uinput.KeyLeftshift))
} }
log(kb.KeyDown(c.Key)) log(kb.KeyDown(c.Key))
} }
func (c Chord) KeyUp(kb uinput.Keyboard) { func (c *Chord) KeyUp(kb uinput.Keyboard) {
if c.Super { if c.Super {
log(kb.KeyUp(super)) log(kb.KeyUp(uinput.KeyLeftmeta))
} }
if c.AltGr { if c.AltGr {
log(kb.KeyUp(altgr)) log(kb.KeyUp(84))
} }
if c.Ctrl { if c.Ctrl {
log(kb.KeyUp(ctrl)) log(kb.KeyUp(uinput.KeyLeftctrl))
} }
if c.Alt { if c.Alt {
log(kb.KeyUp(alt)) log(kb.KeyUp(uinput.KeyLeftalt))
} }
if c.Shift { if c.Shift {
log(kb.KeyUp(shift)) log(kb.KeyUp(uinput.KeyLeftshift))
} }
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 { if chord.Key == code && (chord == Chord{Key: code}) {
fmt.Printf("%-*s %s\n", margin, name, chord.String()) 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() usage()
os.Exit(0) os.Exit(0)
panic("unreachable") 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) listKeys(keymap, LinuxKeys)
os.Exit(0) os.Exit(0)
panic("unreachable") panic("unreachable")
}) })
o.FlagFunc("list-x-keys", func() error { optset.FlagFunc("list-x-keys", func() error {
listKeys(keymap, XKeys) listKeys(keymap, XKeys)
os.Exit(0) os.Exit(0)
panic("unreachable") panic("unreachable")
}) })
o.FlagFunc("version", func() error { optset.FlagFunc("version", func() error {
fmt.Println(Version) fmt.Println(Version)
os.Exit(0) os.Exit(0)
panic("unreachable") panic("unreachable")
}) })
err := o.Parse(true, os.Args[1:]) err := optset.Parse(true, os.Args[1:])
if err != nil { if err != nil {
fatal(err.Error()) fatal(err.Error())
} }
if len(o.Args()) > 0 { if len(optset.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")) keyboard, err := uinput.CreateKeyboard("/dev/uinput", []byte("dotool keyboard"))
if len(keyboardName) == 0 {
keyboardName = []byte("dotool keyboard")
}
keyboard, err := uinput.CreateKeyboard("/dev/uinput", keyboardName)
if err != nil { if err != nil {
fatal(err.Error()) fatal(err.Error())
} }
@ -349,23 +358,18 @@ func main() {
} }
} else if s, ok := cutWord(text, "type"); ok { } else if s, ok := cutWord(text, "type"); ok {
for _, r := range s { for _, r := range s {
sym := xkb.Utf32ToKeysym(uint32(r)) if sym := xkb.Utf32ToKeysym(uint32(r)); sym == 0 {
if sym == 0 {
warn("invalid character: " + string(r)) 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 { } 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) time.Sleep(typedelay)
} }

17
dotoold
View file

@ -1,15 +1,10 @@
#!/bin/sh #!/bin/sh
for a; do if [ $# != 0 ]; then
case "$a" in echo 'dotoold runs dotool reading from a pipe for dotoolc to write to. dotoold
-h|--help)
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 will exit immediately if the pipe is already being read. The path used
for the pipe is $DOTOOL_PIPE else /tmp/dotool-pipe.' >&2 for the pipe is $DOTOOL_PIPE else /tmp/dotool-pipe.' >&2
exit [ "$1" = -h ] || [ "$1" = --help ]; exit
;; fi
--) break;;
esac
done
fifo_being_read(){ fifo_being_read(){
[ -p "$1" ] && /bin/echo 1<>"$1" >"$1" [ -p "$1" ] && /bin/echo 1<>"$1" >"$1"
@ -25,5 +20,5 @@ fi
rm -f -- "$p" || exit 1 rm -f -- "$p" || exit 1
trap 'rm -f -- "$p"; pkill -P $$; trap - EXIT; exit' EXIT INT TERM HUP trap 'rm -f -- "$p"; pkill -P $$; trap - EXIT; exit' EXIT INT TERM HUP
mkfifo -m 660 "$p" || exit 1 mkfifo -m 660 "$p" || exit 1
dotool "$@" <> "$p" & dotool <> "$p" &
wait $! wait

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-20230911153257-e72225a1933c git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71
github.com/bendahl/uinput v1.7.0 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-20221207200434-dad26d091d71 h1:jh3Ite7R1ZvdLt6j52e4njO2SS/z5dWOLXllw7inalc=
git.sr.ht/~geb/opt v0.0.0-20230911153257-e72225a1933c/go.mod h1:S6h1g8P7DyG7i7YIHZ5IpYbC6lzZB9DYIEl8PyXOmsg= git.sr.ht/~geb/opt v0.0.0-20221207200434-dad26d091d71/go.mod h1:S6h1g8P7DyG7i7YIHZ5IpYbC6lzZB9DYIEl8PyXOmsg=
github.com/bendahl/uinput v1.7.0 h1:nA4fm8Wu8UYNOPykIZm66nkWEyvxzfmJ8YC02PM40jg= github.com/bendahl/uinput v1.6.0 h1:fM6r3OSC17rHh758mizKjSBuqi+XinhiGd4N3pWvZiI=
github.com/bendahl/uinput v1.7.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8= github.com/bendahl/uinput v1.6.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8=

17
install.sh Executable file
View 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
View file

@ -3,11 +3,8 @@ package main
import ( import (
"git.sr.ht/~geb/dotool/xkb" "git.sr.ht/~geb/dotool/xkb"
"github.com/bendahl/uinput" "github.com/bendahl/uinput"
"unicode"
) )
var super, altgr, ctrl, alt, shift int
var LinuxKeys = map[string]Chord{ var LinuxKeys = map[string]Chord{
// Linux Only // Linux Only
"micmute": Chord{Key: uinput.KeyMicmute}, "micmute": Chord{Key: uinput.KeyMicmute},
@ -484,137 +481,11 @@ var linuxXSyms = map[string]uint32{
var XKeys = map[string]Chord{} var XKeys = map[string]Chord{}
type DeadKeyResult struct { func newChord(keymap *xkb.Keymap, mask, code uint32) Chord{
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 {
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,
@ -622,7 +493,6 @@ func newChord(keymap *xkb.Keymap, code, level uint32) Chord {
(mask & altMask) != 0, (mask & altMask) != 0,
(mask & shiftMask) != 0, (mask & shiftMask) != 0,
int(code) - 8, int(code) - 8,
level,
} }
} }
@ -632,26 +502,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, code, level) chord := newChord(keymap, keymap.KeyGetMod(code, 0, level), code)
for name, s := range linuxXSyms { for name, s := range linuxXSyms {
if s == sym { if s == sym {
if l, ok := LinuxKeys[name]; !ok || level < l.level { LinuxKeys[name] = chord
LinuxKeys[name] = chord
}
} }
} }
name := xkb.KeysymGetName(sym) XKeys[xkb.KeysymGetName(sym)] = chord
if x, ok := XKeys[name]; !ok || level < x.level {
XKeys[name] = 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 { 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 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, code, level) return newChord(keymap, keymap.KeyGetMod(code, 0, level), code)
} }
} }
} }
} }
return Chord{} 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{}
}

View file

@ -1,4 +1,3 @@
#!/bin/sh #!/bin/sh
rm -vf /usr/local/bin/dotool /usr/local/bin/dotoolc /usr/local/bin/dotoold 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 /etc/udev/rules.d/80-dotool.rules
rm -vf /usr/share/man/man1/dotool.1