Compare commits
No commits in common. "master" and "1.2" have entirely different histories.
18 changed files with 849 additions and 1269 deletions
6
50-dotool.conf
Normal file
6
50-dotool.conf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# This can stop dotool typing guff if you are using a non-us keyboard layout.
|
||||||
|
Section "InputClass"
|
||||||
|
Identifier "dotool keyboard"
|
||||||
|
MatchDriver "libinput"
|
||||||
|
Option "XkbLayout" "us"
|
||||||
|
EndSection
|
|
@ -1,2 +1,6 @@
|
||||||
# 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"
|
||||||
|
|
||||||
|
# This can stop dotool typing guff if you are using a non-us keyboard layout,
|
||||||
|
# but it only seems to affect X and 50-dotool.conf achieves that better.
|
||||||
|
SUBSYSTEM=="input", ACTION=="add|change", ATTRS{name}=="dotool keyboard", ENV{XKBLAYOUT}="us"
|
||||||
|
|
37
CHANGELOG.md
37
CHANGELOG.md
|
@ -2,43 +2,6 @@
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- Support for keyboard layouts.
|
|
||||||
- hwheel for horizontal scrolling.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Now depends on the xkbcommon library.
|
|
||||||
- XKB key names are now case-sensitive.
|
|
||||||
- scroll -> wheel
|
|
||||||
|
|
||||||
## [1.2](https://git.sr.ht/~geb/dotool/refs/1.2)
|
## [1.2](https://git.sr.ht/~geb/dotool/refs/1.2)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
61
README.md
61
README.md
|
@ -1,42 +1,53 @@
|
||||||
# dotool
|
# dotool
|
||||||
|
|
||||||
dotool reads actions from stdin and simulates keyboard/mouse input using
|
dotool reads commands from stdin and simulates keyboard and mouse 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
|
It takes about half a second to register the virtual device, but it can be
|
||||||
|
kept using the daemon.
|
||||||
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 (>=1.19) run:
|
||||||
|
|
||||||
./build.sh && sudo ./build.sh install
|
sudo ./install.sh
|
||||||
|
|
||||||
And to trigger the udev rule, run:
|
|
||||||
|
|
||||||
sudo udevadm control --reload && sudo udevadm trigger
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
See the [manpage](doc/dotool.1.scd).
|
dotool will require root permissions unless you are in group input.
|
||||||
|
See:
|
||||||
|
|
||||||
## Numen and Contact
|
dotool --help
|
||||||
|
|
||||||
dotool was written for [Numen](https://numenvoice.org), which has a
|
This greets the world:
|
||||||
[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
|
echo 'type Sup, Lads!' | dotool
|
||||||
[~geb/numen@lists.sr.ht](https://lists.sr.ht/~geb/numen).
|
|
||||||
|
|
||||||
## Support My Work 👀
|
This screams for three seconds:
|
||||||
|
|
||||||
|
{ echo keydown A; sleep 3; echo key H shift+1; } | dotool
|
||||||
|
|
||||||
|
This drags the mouse:
|
||||||
|
|
||||||
|
printf %s\\n 'buttondown left' 'mousemove 0 100' 'buttonup left' | dotool
|
||||||
|
|
||||||
|
The daemon and client, `dotoold` and `dotoolc`, can used to keep a persistent
|
||||||
|
virtual device for a quicker initial response:
|
||||||
|
|
||||||
|
dotoold &
|
||||||
|
echo 'type super' | dotoolc
|
||||||
|
echo 'type speedy' | dotoolc
|
||||||
|
|
||||||
|
## Numen, Chat and Contact
|
||||||
|
|
||||||
|
dotool was written for [Numen Voice Control](https://numenvoice.com)
|
||||||
|
and you're very welcome to join the Matrix chat at
|
||||||
|
[#numen:matrix.org](https://matrix.to/#/#numen:matrix.org).
|
||||||
|
|
||||||
|
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 Me
|
||||||
|
|
||||||
[Thank you!](https://liberapay.com/geb)
|
[Thank you!](https://liberapay.com/geb)
|
||||||
|
|
||||||
|
|
8
_install.sh
Executable file
8
_install.sh
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# ./_install.sh [DESTDIR] [BINDIR]
|
||||||
|
mkdir -p "$1/${2:-usr/local/bin}" || exit
|
||||||
|
cp -v dotoolc dotoold "$1/${2:-usr/local/bin}" || exit
|
||||||
|
mkdir -p "$1/usr/share/X11/xorg.conf.d" || exit
|
||||||
|
cp -v 50-dotool.conf "$1/usr/share/X11/xorg.conf.d" || exit
|
||||||
|
mkdir -p "$1/etc/sway/config.d" || exit
|
||||||
|
cp -v dotool.sway "$1/etc/sway/config.d/dotool" || exit
|
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
|
|
355
dotool.go
355
dotool.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.sr.ht/~geb/dotool/xkb"
|
|
||||||
"git.sr.ht/~geb/opt"
|
"git.sr.ht/~geb/opt"
|
||||||
"github.com/bendahl/uinput"
|
"github.com/bendahl/uinput"
|
||||||
"math"
|
"math"
|
||||||
|
@ -18,39 +17,59 @@ import (
|
||||||
var Version string
|
var Version string
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Println(`dotool reads actions from stdin and simulates input using uinput.
|
fmt.Fprintln(os.Stderr, `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...
|
||||||
type TEXT
|
type TEXT
|
||||||
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
|
scroll NUMBER (where NUMBER is the amount down/up if positive/negative)
|
||||||
hwheel AMOUNT
|
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 the number of pixels to move)
|
||||||
mousemove X Y (where X and Y are amounts to move)
|
keydelay MILLISECONDS (default: 2)
|
||||||
keydelay MILLISECONDS
|
keyhold MILLISECONDS (default: 8)
|
||||||
keyhold MILLISECONDS
|
typedelay MILLISECONDS (default: 2)
|
||||||
typedelay MILLISECONDS
|
typehold MILLISECONDS (default: 8)
|
||||||
typehold MILLISECONDS
|
|
||||||
|
|
||||||
--list-keys Print the possible Linux keys and exit.
|
Example: echo "key h i shift+1" | dotool
|
||||||
--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 make it effective without rebooting by
|
||||||
|
running: sudo udevadm trigger
|
||||||
|
|
||||||
|
The keys are those used by Linux, but can also be specified using X11 names
|
||||||
|
prefixed with x: like x:exclam, as well as their Linux keycode like k:30.
|
||||||
|
They are case insensitive, except uppercase character keys also simulate shift.
|
||||||
|
|
||||||
|
The modifiers are: super, ctrl, alt and shift.
|
||||||
|
|
||||||
|
The daemon and client, dotoold and dotoolc, can used to keep a persistent
|
||||||
|
virtual device for a quicker initial response.
|
||||||
|
|
||||||
|
--list-keys
|
||||||
|
Print the supported Linux keys and their keycodes.
|
||||||
|
|
||||||
|
--list-x-keys
|
||||||
|
Print the supported X11 keys and their Linux keycodes.
|
||||||
|
|
||||||
|
--version
|
||||||
|
Print the version and exit.
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fatal(a ...any) {
|
func fatal(a ...any) {
|
||||||
fmt.Fprintln(os.Stderr, "dotool:", fmt.Sprint(a...))
|
fmt.Fprint(os.Stderr, "dotool: ")
|
||||||
|
fmt.Fprintln(os.Stderr, a...)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func warn(a ...any) {
|
func warn(a ...any) {
|
||||||
fmt.Fprintln(os.Stderr, "dotool: WARNING:", fmt.Sprint(a...))
|
fmt.Fprint(os.Stderr, "dotool WARNING: ")
|
||||||
|
fmt.Fprintln(os.Stderr, a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func log(err error) {
|
func log(err error) {
|
||||||
|
@ -60,46 +79,20 @@ func log(err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Chord struct {
|
type Chord struct {
|
||||||
Super, AltGr, Ctrl, Alt, Shift bool
|
Super bool
|
||||||
|
Ctrl bool
|
||||||
|
Alt bool
|
||||||
|
Shift bool
|
||||||
Key int
|
Key int
|
||||||
|
|
||||||
level uint32 // just for initKeys
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
|
func parseChord(chord string) (Chord, error) {
|
||||||
var c Chord
|
var c Chord
|
||||||
keys := strings.Split(chord, "+")
|
keys := strings.Split(chord, "+")
|
||||||
|
|
||||||
k := keys[len(keys)-1]
|
|
||||||
if strings.HasPrefix(k, "k:") {
|
|
||||||
code, err := strconv.Atoi(k[2:])
|
|
||||||
if err != nil {
|
|
||||||
return c, errors.New("invalid keycode: " + k[2:])
|
|
||||||
}
|
|
||||||
c.Key = code
|
|
||||||
} else if strings.HasPrefix(k, "x:") {
|
|
||||||
var ok bool
|
|
||||||
c, ok = XKeys[k[2:]]
|
|
||||||
if !ok {
|
|
||||||
return c, errors.New("impossible XKB key for layout: " + k[2:])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var ok bool
|
|
||||||
c, ok = LinuxKeys[strings.ToLower(k)]
|
|
||||||
if !ok {
|
|
||||||
return c, errors.New("impossible key for layout: " + k)
|
|
||||||
}
|
|
||||||
if len(k) == 1 && unicode.IsUpper(rune(k[0])) {
|
|
||||||
c.Shift = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(keys) - 1; i++ {
|
for i := 0; i < len(keys) - 1; i++ {
|
||||||
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":
|
||||||
|
@ -111,168 +104,140 @@ func parseChord(keymap *xkb.Keymap, chord string) (Chord, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
k := keys[len(keys)-1]
|
||||||
|
if strings.HasPrefix(k, "k:") {
|
||||||
|
code, err := strconv.Atoi(k[2:])
|
||||||
|
if err != nil {
|
||||||
|
return c, errors.New("invalid keycode: " + k[2:])
|
||||||
|
}
|
||||||
|
c.Key = code
|
||||||
|
} else if strings.HasPrefix(k, "x:") {
|
||||||
|
if code, ok := xKeysShifted[strings.ToLower(k[2:])]; ok {
|
||||||
|
if len(k[2:]) > 1 || unicode.IsUpper(rune(k[2])) {
|
||||||
|
c.Shift = true
|
||||||
|
}
|
||||||
|
c.Key = code
|
||||||
|
} else if code, ok := xKeysNormal[strings.ToLower(k[2:])]; ok {
|
||||||
|
c.Key = code
|
||||||
|
} else {
|
||||||
|
return c, errors.New("unknown X11 key: " + k[2:])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
code, ok := linuxKeys[strings.ToLower(k)]
|
||||||
|
if len(k) == 1 && unicode.IsUpper(rune(k[0])) {
|
||||||
|
c.Shift = true
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return c, errors.New("unknown key: " + k)
|
||||||
|
}
|
||||||
|
c.Key = code
|
||||||
|
}
|
||||||
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 {
|
|
||||||
log(kb.KeyDown(altgr))
|
|
||||||
}
|
}
|
||||||
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 {
|
|
||||||
log(kb.KeyUp(altgr))
|
|
||||||
}
|
}
|
||||||
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 cutCmd(s, cmd string) (string, bool) {
|
||||||
func listKeys(keymap *xkb.Keymap, keys map[string]Chord) {
|
if strings.HasPrefix(s, cmd + " ") || strings.HasPrefix(s, cmd + "\t") {
|
||||||
var margin int
|
return s[len(cmd)+1:], true
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cutWord(s, word string) (string, bool) {
|
|
||||||
if s == word {
|
|
||||||
return "", true
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(s, word + " ") || strings.HasPrefix(s, word + "\t") {
|
|
||||||
return s[len(word)+1:], true
|
|
||||||
}
|
}
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var keymap *xkb.Keymap
|
|
||||||
{
|
{
|
||||||
layout := os.Getenv("DOTOOL_XKB_LAYOUT")
|
optset := opt.NewOptionSet()
|
||||||
if layout == "" {
|
|
||||||
layout = os.Getenv("XKB_DEFAULT_LAYOUT")
|
|
||||||
}
|
|
||||||
variant := os.Getenv("DOTOOL_XKB_VARIANT")
|
|
||||||
if variant == "" {
|
|
||||||
variant = os.Getenv("XKB_DEFAULT_VARIANT")
|
|
||||||
}
|
|
||||||
if variant != "" && layout == "" {
|
|
||||||
// Otherwise xkbcommon just ignores the variant.
|
|
||||||
fatal("you need to set $DOTOOL_XKB_LAYOUT or $XKB_DEFAULT_LAYOUT if the variant is set")
|
|
||||||
}
|
|
||||||
names := xkb.RuleNames{Layout: layout, Variant: variant}
|
|
||||||
|
|
||||||
ctx := xkb.ContextNew(xkb.ContextNoFlags)
|
optset.FlagFunc("h", func() error {
|
||||||
defer ctx.Unref()
|
|
||||||
keymap = ctx.KeymapNewFromNames(&names, xkb.KeymapCompileNoFlags)
|
|
||||||
if keymap == nil {
|
|
||||||
fatal("failed to compile keymap")
|
|
||||||
}
|
|
||||||
defer keymap.Unref()
|
|
||||||
|
|
||||||
initKeys(keymap)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
o := opt.NewOptSet()
|
|
||||||
|
|
||||||
o.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.BoolFunc("list-keys", func(bool) error {
|
||||||
listKeys(keymap, LinuxKeys)
|
for i := 1; i < 249; i++ {
|
||||||
|
for name, code := range linuxKeys {
|
||||||
|
if code == i {
|
||||||
|
fmt.Printf("%s %d\n", name, code)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
})
|
})
|
||||||
|
|
||||||
o.FlagFunc("list-x-keys", func() error {
|
optset.BoolFunc("list-x-keys", func(bool) error {
|
||||||
listKeys(keymap, XKeys)
|
for i := 1; i < 249; i++ {
|
||||||
|
for name, code := range xKeysNormal {
|
||||||
|
if code == i {
|
||||||
|
fmt.Print(name)
|
||||||
|
for name, code := range xKeysShifted {
|
||||||
|
if code == i {
|
||||||
|
if len(name) == 1 {
|
||||||
|
name = strings.ToUpper(name)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s", name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf(" %d\n", code)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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())
|
||||||
}
|
}
|
||||||
|
@ -302,9 +267,9 @@ func main() {
|
||||||
if text == "" {
|
if text == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if s, ok := cutWord(text, "key"); ok {
|
if s, ok := cutCmd(text, "key"); ok {
|
||||||
for _, field := range strings.Fields(s) {
|
for _, field := range strings.Fields(s) {
|
||||||
if chord, err := parseChord(keymap, field); err == nil {
|
if chord, err := parseChord(field); err == nil {
|
||||||
chord.KeyDown(keyboard)
|
chord.KeyDown(keyboard)
|
||||||
time.Sleep(keyhold)
|
time.Sleep(keyhold)
|
||||||
chord.KeyUp(keyboard)
|
chord.KeyUp(keyboard)
|
||||||
|
@ -313,25 +278,25 @@ func main() {
|
||||||
}
|
}
|
||||||
time.Sleep(keydelay)
|
time.Sleep(keydelay)
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "keydown"); ok {
|
} else if s, ok := cutCmd(text, "keydown"); ok {
|
||||||
for _, field := range strings.Fields(s) {
|
for _, field := range strings.Fields(s) {
|
||||||
if chord, err := parseChord(keymap, field); err == nil {
|
if chord, err := parseChord(field); err == nil {
|
||||||
chord.KeyDown(keyboard)
|
chord.KeyDown(keyboard)
|
||||||
} else {
|
} else {
|
||||||
warn(err.Error())
|
warn(err.Error())
|
||||||
}
|
}
|
||||||
time.Sleep(keydelay)
|
time.Sleep(keydelay)
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "keyup"); ok {
|
} else if s, ok := cutCmd(text, "keyup"); ok {
|
||||||
for _, field := range strings.Fields(s) {
|
for _, field := range strings.Fields(s) {
|
||||||
if chord, err := parseChord(keymap, field); err == nil {
|
if chord, err := parseChord(field); err == nil {
|
||||||
chord.KeyUp(keyboard)
|
chord.KeyUp(keyboard)
|
||||||
} else {
|
} else {
|
||||||
warn(err.Error())
|
warn(err.Error())
|
||||||
}
|
}
|
||||||
time.Sleep(keydelay)
|
time.Sleep(keydelay)
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "keydelay"); ok {
|
} else if s, ok := cutCmd(text, "keydelay"); ok {
|
||||||
var d float64
|
var d float64
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f\n", &d)
|
_, err := fmt.Sscanf(s + "\n", "%f\n", &d)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -339,7 +304,7 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
warn("invalid delay: " + sc.Text())
|
warn("invalid delay: " + sc.Text())
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "keyhold"); ok {
|
} else if s, ok := cutCmd(text, "keyhold"); ok {
|
||||||
var d float64
|
var d float64
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f\n", &d)
|
_, err := fmt.Sscanf(s + "\n", "%f\n", &d)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -347,29 +312,19 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
warn("invalid hold time: " + sc.Text())
|
warn("invalid hold time: " + sc.Text())
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "type"); ok {
|
} else if s, ok := cutCmd(text, "type"); ok {
|
||||||
for _, r := range s {
|
for _, r := range s {
|
||||||
sym := xkb.Utf32ToKeysym(uint32(r))
|
if chord, ok := runeChords[unicode.ToLower(r)]; ok {
|
||||||
if sym == 0 {
|
if unicode.IsUpper(r) {
|
||||||
warn("invalid character: " + string(r))
|
chord.Shift = true
|
||||||
} else if c := getChord(keymap, sym); c.Key != 0 {
|
}
|
||||||
c.KeyDown(keyboard)
|
chord.KeyDown(keyboard)
|
||||||
time.Sleep(typehold)
|
time.Sleep(typehold)
|
||||||
c.KeyUp(keyboard)
|
chord.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))
|
|
||||||
}
|
}
|
||||||
time.Sleep(typedelay)
|
time.Sleep(typedelay)
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "typedelay"); ok {
|
} else if s, ok := cutCmd(text, "typedelay"); ok {
|
||||||
var d float64
|
var d float64
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f\n", &d)
|
_, err := fmt.Sscanf(s + "\n", "%f\n", &d)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -377,7 +332,7 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
warn("invalid delay: " + sc.Text())
|
warn("invalid delay: " + sc.Text())
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "typehold"); ok {
|
} else if s, ok := cutCmd(text, "typehold"); ok {
|
||||||
var d float64
|
var d float64
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f\n", &d)
|
_, err := fmt.Sscanf(s + "\n", "%f\n", &d)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -385,7 +340,7 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
warn("invalid hold time: " + sc.Text())
|
warn("invalid hold time: " + sc.Text())
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "click"); ok {
|
} else if s, ok := cutCmd(text, "click"); ok {
|
||||||
for _, button := range strings.Fields(s) {
|
for _, button := range strings.Fields(s) {
|
||||||
switch button {
|
switch button {
|
||||||
case "left", "1":
|
case "left", "1":
|
||||||
|
@ -398,7 +353,7 @@ func main() {
|
||||||
warn("unknown button: " + button)
|
warn("unknown button: " + button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "buttondown"); ok {
|
} else if s, ok := cutCmd(text, "buttondown"); ok {
|
||||||
for _, button := range strings.Fields(s) {
|
for _, button := range strings.Fields(s) {
|
||||||
switch button {
|
switch button {
|
||||||
case "left", "1":
|
case "left", "1":
|
||||||
|
@ -411,7 +366,7 @@ func main() {
|
||||||
warn("unknown button: " + button)
|
warn("unknown button: " + button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "buttonup"); ok {
|
} else if s, ok := cutCmd(text, "buttonup"); ok {
|
||||||
for _, button := range strings.Fields(s) {
|
for _, button := range strings.Fields(s) {
|
||||||
switch button {
|
switch button {
|
||||||
case "left", "1":
|
case "left", "1":
|
||||||
|
@ -424,23 +379,7 @@ func main() {
|
||||||
warn("unknown button: " + button)
|
warn("unknown button: " + button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "wheel"); ok {
|
} else if s, ok := cutCmd(text, "scroll"); ok {
|
||||||
var n float64
|
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f\n", &n)
|
|
||||||
if err == nil {
|
|
||||||
log(mouse.Wheel(false, int32(n)))
|
|
||||||
} else {
|
|
||||||
warn("invalid wheel amount: " + sc.Text())
|
|
||||||
}
|
|
||||||
} else if s, ok := cutWord(text, "hwheel"); ok {
|
|
||||||
var n float64
|
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f\n", &n)
|
|
||||||
if err == nil {
|
|
||||||
log(mouse.Wheel(true, int32(n)))
|
|
||||||
} else {
|
|
||||||
warn("invalid hwheel amount: " + sc.Text())
|
|
||||||
}
|
|
||||||
} else if s, ok := cutWord(text, "scroll"); ok {
|
|
||||||
var n float64
|
var n float64
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f\n", &n)
|
_, err := fmt.Sscanf(s + "\n", "%f\n", &n)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -448,7 +387,7 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
warn("invalid scroll amount: " + sc.Text())
|
warn("invalid scroll amount: " + sc.Text())
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "mouseto"); ok {
|
} else if s, ok := cutCmd(text, "mouseto"); ok {
|
||||||
var x, y float64
|
var x, y float64
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f %f\n", &x, &y)
|
_, err := fmt.Sscanf(s + "\n", "%f %f\n", &x, &y)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -465,7 +404,7 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
warn("invalid coordinate: " + sc.Text())
|
warn("invalid coordinate: " + sc.Text())
|
||||||
}
|
}
|
||||||
} else if s, ok := cutWord(text, "mousemove"); ok {
|
} else if s, ok := cutCmd(text, "mousemove"); ok {
|
||||||
var x, y float64
|
var x, y float64
|
||||||
_, err := fmt.Sscanf(s + "\n", "%f %f\n", &x, &y)
|
_, err := fmt.Sscanf(s + "\n", "%f %f\n", &x, &y)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
2
dotool.sway
Normal file
2
dotool.sway
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# This can stop dotool typing guff if you are using a non-us keyboard layout.
|
||||||
|
input "18193:2069:dotool_keyboard" xkb_layout "us"
|
9
dotoolc
9
dotoolc
|
@ -1,8 +1,9 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
if [ $# != 0 ]; then
|
if [ $# != 0 ]; then
|
||||||
echo 'dotoolc writes its stdin to the pipe being read by dotoold. dotoolc will
|
echo 'dotoolc writes its stdin to the pipe being read by dotoold.
|
||||||
exit immediately if the pipe is not being read. The path of the pipe
|
dotoolc will exit immediately if the pipe is not being read.
|
||||||
is $DOTOOL_PIPE else /tmp/dotool-pipe.' >&2
|
The path of the pipe is $DOTOOL_PIPE else /tmp/dotool_pipe.
|
||||||
|
' >&2
|
||||||
[ "$1" = -h ] || [ "$1" = --help ]; exit
|
[ "$1" = -h ] || [ "$1" = --help ]; exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ fifo_being_read(){
|
||||||
[ -p "$1" ] && /bin/echo 1<>"$1" >"$1"
|
[ -p "$1" ] && /bin/echo 1<>"$1" >"$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
p="${DOTOOL_PIPE:-/tmp/dotool-pipe}"
|
p="${DOTOOL_PIPE:-/tmp/dotool_pipe}"
|
||||||
|
|
||||||
if [ -p "$p" ] && ! [ -w "$p" ]; then
|
if [ -p "$p" ] && ! [ -w "$p" ]; then
|
||||||
printf %s\\n "dotoolc: the pipe does not grant write permission: $p" >&2
|
printf %s\\n "dotoolc: the pipe does not grant write permission: $p" >&2
|
||||||
|
|
24
dotoold
24
dotoold
|
@ -1,21 +1,17 @@
|
||||||
#!/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.
|
||||||
-h|--help)
|
dotoold will exit immediately if the pipe is already being read.
|
||||||
echo 'dotoold runs dotool reading from a pipe for dotoolc to write to. dotoold
|
The path used for the pipe is $DOTOOL_PIPE else /tmp/dotool_pipe.
|
||||||
will exit immediately if the pipe is already being read. The path used
|
' >&2
|
||||||
for the pipe is $DOTOOL_PIPE else /tmp/dotool-pipe.' >&2
|
[ "$1" = -h ] || [ "$1" = --help ]; exit
|
||||||
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
p="${DOTOOL_PIPE:-/tmp/dotool-pipe}"
|
p="${DOTOOL_PIPE:-/tmp/dotool_pipe}"
|
||||||
|
|
||||||
if fifo_being_read "$p" 2> /dev/null; then
|
if fifo_being_read "$p" 2> /dev/null; then
|
||||||
printf %s\\n "dotoold: another instance is already reading the pipe: $p" >&2
|
printf %s\\n "dotoold: another instance is already reading the pipe: $p" >&2
|
||||||
|
@ -25,5 +21,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
4
go.mod
|
@ -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
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-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=
|
||||||
|
|
13
install.sh
Executable file
13
install.sh
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# ./install.sh [DESTDIR] [BINDIR]
|
||||||
|
: "${DOTOOL_VERSION=$(git describe --long --abbrev=12 --tags --dirty 2>/dev/null || echo 1.2)}"
|
||||||
|
go build -ldflags "-X main.Version=$DOTOOL_VERSION" || exit
|
||||||
|
mkdir -p "$1/${2:-usr/local/bin}" || exit
|
||||||
|
cp -v dotool "$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
|
||||||
|
./_install.sh "$1" "$2" || exit
|
||||||
|
|
||||||
|
# Make the new/updated udev rule effective
|
||||||
|
udevadm control --reload
|
||||||
|
udevadm trigger
|
|
@ -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
|
|
||||||
|
|
144
xkb/xkb.go
144
xkb/xkb.go
|
@ -1,144 +0,0 @@
|
||||||
package xkb
|
|
||||||
|
|
||||||
//#cgo pkg-config: xkbcommon
|
|
||||||
//#cgo LDFLAGS: -ldl
|
|
||||||
//
|
|
||||||
//#include <stdlib.h>
|
|
||||||
//#include <xkbcommon/xkbcommon.h>
|
|
||||||
import "C"
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ModNameShift is the name of Shift Modifier
|
|
||||||
ModNameShift = "Shift"
|
|
||||||
// ModNameCaps is the name of Caps Lock Modifier
|
|
||||||
ModNameCaps = "Lock"
|
|
||||||
// ModNameCtrl is the name of Control Modifier
|
|
||||||
ModNameCtrl = "Control"
|
|
||||||
// ModNameAlt is the name of Alt Modifier
|
|
||||||
ModNameAlt = "Mod1"
|
|
||||||
// ModNameNum is the name of Num Lock Modifier
|
|
||||||
ModNameNum = "Mod2"
|
|
||||||
// ModNameLogo is the name of Logo Modifier
|
|
||||||
ModNameLogo = "Mod4"
|
|
||||||
// LedNameCaps is the name of Caps Lock Led
|
|
||||||
LedNameCaps = "Caps Lock"
|
|
||||||
// LedNameNum is the name of Num Lock Led
|
|
||||||
LedNameNum = "Num Lock"
|
|
||||||
// LedNameScroll is the name of Scroll Lock Led
|
|
||||||
LedNameScroll = "Scroll Lock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func KeysymGetName(keysym uint32) string {
|
|
||||||
s := "................................................................"
|
|
||||||
cs := C.CString(s)
|
|
||||||
defer C.free(unsafe.Pointer(cs))
|
|
||||||
_ = C.xkb_keysym_get_name(C.uint(keysym), cs, C.size_t(len(s)))
|
|
||||||
return C.GoString(cs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func KeysymToUtf32(keysym uint32) uint32 {
|
|
||||||
return uint32(C.xkb_keysym_to_utf32(C.uint(keysym)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Utf32ToKeysym(ucs uint32) uint32 {
|
|
||||||
return uint32(C.xkb_utf32_to_keysym(C.uint(ucs)))
|
|
||||||
}
|
|
||||||
|
|
||||||
type KeymapCompileFlags int
|
|
||||||
|
|
||||||
const (
|
|
||||||
KeymapCompileNoFlags KeymapCompileFlags = C.XKB_KEYMAP_COMPILE_NO_FLAGS
|
|
||||||
)
|
|
||||||
|
|
||||||
type RuleNames struct {
|
|
||||||
Rules, Model, Layout, Variant, Options string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rn *RuleNames) toC() *C.struct_xkb_rule_names {
|
|
||||||
if rn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &C.struct_xkb_rule_names{
|
|
||||||
rules: C.CString(rn.Rules),
|
|
||||||
model: C.CString(rn.Model),
|
|
||||||
layout: C.CString(rn.Layout),
|
|
||||||
variant: C.CString(rn.Variant),
|
|
||||||
options: C.CString(rn.Options),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ContextNoFlags = 0
|
|
||||||
|
|
||||||
type Context struct {
|
|
||||||
p *C.struct_xkb_context
|
|
||||||
}
|
|
||||||
|
|
||||||
func ContextNew(flags uint32) (ctx *Context) {
|
|
||||||
ctx = new(Context)
|
|
||||||
ctx.p = C.xkb_context_new(flags)
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *Context) KeymapNewFromNames(rules *RuleNames, flags KeymapCompileFlags) *Keymap {
|
|
||||||
km := C.xkb_keymap_new_from_names(ctx.p, rules.toC(), C.enum_xkb_keymap_compile_flags(flags))
|
|
||||||
if km == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Keymap{km}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *Context) Unref() {
|
|
||||||
C.xkb_context_unref(ctx.p)
|
|
||||||
ctx.p = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Keymap struct {
|
|
||||||
p *C.struct_xkb_keymap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *Keymap) KeyGetMod(key, layout, level uint32) uint32 {
|
|
||||||
var mask C.uint
|
|
||||||
C.xkb_keymap_key_get_mods_for_level(km.p, C.uint(key), C.uint(layout), C.uint(level), &mask, 1)
|
|
||||||
return uint32(mask)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *Keymap) KeyGetSymsByLevel(key, layout, level uint32) []uint32 {
|
|
||||||
var syms *C.xkb_keysym_t
|
|
||||||
n := int(C.xkb_keymap_key_get_syms_by_level(km.p, C.uint(key), C.uint(layout), C.uint(level), &syms))
|
|
||||||
if n == 0 || syms == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data := unsafe.Slice(syms, n)
|
|
||||||
s := make([]uint32, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
s[i] = uint32(data[i])
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *Keymap) MaxKeycode() uint32 {
|
|
||||||
return uint32(C.xkb_keymap_max_keycode(km.p))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *Keymap) MinKeycode() uint32 {
|
|
||||||
return uint32(C.xkb_keymap_min_keycode(km.p))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *Keymap) ModGetIndex(mod string) uint {
|
|
||||||
return uint(C.xkb_keymap_mod_get_index(km.p, C.CString(mod)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *Keymap) NumLevelsForKey(key, layout uint32) uint32 {
|
|
||||||
return uint32(C.xkb_keymap_num_levels_for_key(km.p, C.uint(key), C.uint(layout)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (km *Keymap) Unref() {
|
|
||||||
C.xkb_keymap_unref(km.p)
|
|
||||||
km.p = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func keymapUnref(km *Keymap) {
|
|
||||||
C.xkb_keymap_unref(km.p)
|
|
||||||
km.p = nil
|
|
||||||
}
|
|
42
xkeys.bash
Executable file
42
xkeys.bash
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
align() {
|
||||||
|
sed '/\<KeyRo\>/ d
|
||||||
|
/\<KeyKpjpcomma\>/ d
|
||||||
|
/\<KeyMacro\>/ d
|
||||||
|
/\<KeyYen\>/ d
|
||||||
|
/\<KeySetup\>/ d
|
||||||
|
/\<KeyDeletefile\>/ d
|
||||||
|
/\<KeyClosecd\>/ d
|
||||||
|
/\<KeyEjectclosecd\>/ d
|
||||||
|
/\<KeyIso\>/ d
|
||||||
|
/\<KeyMove\>/ d
|
||||||
|
/\<KeyEdit\>/ d
|
||||||
|
/\<KeyAlterase\>/ d
|
||||||
|
/\<KeyUnknown\>/ d
|
||||||
|
/\<KeyMicmute\>/ d'
|
||||||
|
}
|
||||||
|
|
||||||
|
normal="$({
|
||||||
|
paste -d ' ' <(xmodmap -pke | sed '1 d; s/.*= /"/; /.*=/ d; s/ .*/":/' | sed '/^"XF86Eject"/ { N; s/.*\n// }') \
|
||||||
|
<(go doc uinput.keyesc | sed '/Key/ !d; s/^\s*/uinput./; s/ .*/,/' | align) |
|
||||||
|
# Skip really non-matching section, we echo some of them below
|
||||||
|
sed '/^"XF86Tools"/,/^"XF86AudioPreset"/ d' |
|
||||||
|
# Remove duplicate keys
|
||||||
|
sed '/^"XF86Mail":.*Email/ d; /^"Cancel":.*Stop/ d; /^"XF86Send":.*file/ d; /^"Print":.*Sysrq/ d; /Key102Nd,$/ d'
|
||||||
|
|
||||||
|
echo '"XF86WebCam": uinput.KeyCamera,'
|
||||||
|
echo '"Print": uinput.KeyPrint,'
|
||||||
|
} | sed 's/^".*"/\L&/; s/^/\t/')"
|
||||||
|
|
||||||
|
printf %s\\n "var xKeysNormal = map[string]int{
|
||||||
|
$normal
|
||||||
|
}
|
||||||
|
"
|
||||||
|
|
||||||
|
echo 'var xKeysShifted = map[string]int{'
|
||||||
|
{
|
||||||
|
paste -d ' ' <(xmodmap -pke | sed '1 d; s/.*= /"/; /.*=/ d; s/\S* /"/; s/ .*/":/' | sed '/^"XF86Eject"/ { N; s/.*\n// }') \
|
||||||
|
<(go doc uinput.keyesc | sed '/Key/ !d; s/^\s*/uinput./; s/ .*/,/' | align) | sed '/^"NoSymbol"/ d; /^\S*_[LR]"/ d; /Key102Nd,$/ d'
|
||||||
|
} | sed 's/^".*"/\L&/; s/^/\t/' | awk 'NR == FNR {if (length($1) > 4) a[$1]; next} !($1 in a)' <(printf %s\\n "$normal") -
|
||||||
|
echo '}'
|
Loading…
Add table
Reference in a new issue