Documentation; add an initial lag before typing, to help with mod-key release;
add a (useless) special key type test (useless, because the issue with special
keys is in the TypeKey() function, not any quasiauto code); update robotgo to
catch my fix merge.
5 files changed, 67 insertions(+), 4 deletions(-)

M README.md
M exec.go
M exec_test.go
M go.mod
M go.sum
M README.md +29 -2
@@ 41,9 41,9 @@ Running
 
 There's only one argument: `-ms <millisecs>`, which defines the amount of "lag" between simulated key presses. It is set to default at 50. All other data, it parses from STDIN.
 
-The intended use is as a utility for [kpmenu](https://github.com/AlessioDP/kpmenu). The idea is that you install `quasiauto` and then bind a hotkey to call `[kpmenu](https://github.com/AlessioDP/kpmenu) --autotype`; [kpmenu](https://github.com/AlessioDP/kpmenu) farms out the actual autotyping part to quasiauto. All in all, it would look like this:
+The intended use is as a utility for [kpmenu](https://github.com/AlessioDP/kpmenu). The idea is that you install `quasiauto` and then bind a hotkey to call `kpmenu --autotype`; [kpmenu](https://github.com/AlessioDP/kpmenu) farms out the actual autotyping part to quasiauto. All in all, it would look like this:
 
-1. Install [kpmenu](https://github.com/AlessioDP/kpmenu); configure it as your KeePass client.
+1. Install [kpmenu](https://github.com/AlessioDP/kpmenu); configure it as your KeePass client. **Until the patch is merged, you need the PR that adds autotype support.**
 2. Configure [kpmenu](https://github.com/AlessioDP/kpmenu) to use quasiauto to identify the active window by adding this to `~/.config/kpmenu/config`:
 ```
 [executable]

          
@@ 67,6 67,33 @@ Make sure the `Username` field is select
 
 quasiauto can autotype on behalf of any program that sends it data in the correct format.
 
+On the kpmenu side, there are a few options that can make autotype work better for you:
+
+```
+--autotypealwaysconfirm           Always confirm autotype, even when there's only 1 selection
+```
+
+This forces kpmenu to *always* ask to start autotype, even if a unique match is made. Without this, autotype can start immediately and -- possibly -- before you've released all of the MOD keys you used to launch autotype, which is less than ideal. I hope to find a way to ensure no keys are pressed before starting autotype, but I don't have a way of doing that right now, so this is a good option to use.
+
+```
+--autotypeusersel                 Prompt for autotype entry instead of trying to detect by active window title
+```
+
+The kpmenu patch runs a program to find the active window. If you want to disable that, and always select the entry yourself, use this option.
+
+```
+--customAutotypeTyper string      Custom executable for autotype typer (default "quasiauto")
+```
+
+Set this to `quasiauto -ms 100` to change the type delay.
+
+```
+--customAutotypeWindowID string   Custom executable for identifying active window for autotype (default "quasiauto -title")
+```
+
+quasiauto can identify the active window, but you could also use, e.g. `xdotool getwindowfocus getwindowname`.  Set this option to change how window titles are selected.
+
+
 Limitations
 ================
 

          
M exec.go +6 -0
@@ 17,6 17,9 @@ func (s Sequence) Exec(ds Pairs, typer T
 	// Give use time to release the control key
 	var err error
 	metas := regexp.MustCompile("([+^%@]+[a-z~]?)|([^+^%@~]+)|(~)")
+	lag := time.Duration(s.Keylag)
+	// Give a little pause before we start doing anything
+	time.Sleep(lag)
 	for _, seq := range s.SeqEntries {
 		err = nil
 		switch seq.Type {

          
@@ 27,6 30,7 @@ func (s Sequence) Exec(ds Pairs, typer T
 			i := keyMap[seq.Token]
 			if i.isTap {
 				typer.KeyTap(i.val)
+				time.Sleep(lag)
 			} else {
 				typer.TypeStr(i.val, s.Keylag)
 			}

          
@@ 59,10 63,12 @@ func (s Sequence) Exec(ds Pairs, typer T
 						for _, m := range mods {
 							if str, ok := m.(string); ok {
 								typer.KeyTap(str)
+								time.Sleep(lag)
 							}
 						}
 					} else if len(mods) == 0 {
 						typer.KeyTap(typing)
+						time.Sleep(lag)
 					} else {
 						typer.KeyTap(typing, mods...)
 					}

          
M exec_test.go +2 -1
@@ 10,7 10,7 @@ import (
 func Test_Sequence_Exec(t *testing.T) {
 	parseKeyMap()
 	empty := []int32{}
-	args := map[string]string{"USERNAME": "user", "PASSWORD": "passw"}
+	args := map[string]string{"USERNAME": "user", "PASSWORD": "passw", "FIELD2": "=.2#'=<&):[.="}
 	tests := []struct {
 		name       string
 		seqEntries SeqEntries

          
@@ 54,6 54,7 @@ func Test_Sequence_Exec(t *testing.T) {
 		{"Complex raw", []SeqEntry{{"ab^+cdd", nil, RAW}}, []string{"ab", "dd"}, []string{"ctrl", "shift", "c"}, empty},
 		{"Embedded enter", []SeqEntry{{"ab~cd", nil, RAW}}, []string{"ab", "cd"}, []string{"enter"}, empty},
 		{"Modified enter", []SeqEntry{{"%~", nil, RAW}}, nil, []string{"alt", "enter"}, empty},
+		{"Funny characters", []SeqEntry{{"FIELD2", nil, FIELD}}, []string{"=.2#'=<&):[.="}, nil, empty},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {

          
M go.mod +3 -1
@@ 4,6 4,8 @@ go 1.16
 
 require (
 	github.com/gen2brain/beeep v0.0.0-20210529141713-5586760f0cc1 // indirect
-	github.com/go-vgo/robotgo v0.99.2
+	github.com/go-vgo/robotgo v0.100.0
+	github.com/otiai10/curr v1.0.0 // indirect
 	github.com/stretchr/testify v1.7.0 // indirect
+	gopkg.in/yaml.v2 v2.2.2 // indirect
 )

          
M go.sum +27 -0
@@ 2,6 2,8 @@ github.com/BurntSushi/freetype-go v0.0.0
 github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966/go.mod h1:Mid70uvE93zn9wgF92A/r5ixgnvX8Lh68fxp9KQBaI0=
 github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY=
 github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
+github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

          
@@ 17,6 19,8 @@ github.com/go-vgo/robotgo v0.99.1 h1:bjw
 github.com/go-vgo/robotgo v0.99.1/go.mod h1:0+i2QWRmZtbIF02RwmiGfFj33Judprukd8ls5J6Eajg=
 github.com/go-vgo/robotgo v0.99.2 h1:5Q1s0EIAb4y76U07Z6xOIKaTP8V37hIx3MW4FZVz2IQ=
 github.com/go-vgo/robotgo v0.99.2/go.mod h1:0+i2QWRmZtbIF02RwmiGfFj33Judprukd8ls5J6Eajg=
+github.com/go-vgo/robotgo v0.100.0 h1:tU03KeKVUfJhqyonxTaM5k28rsQeE0AzqMCplTj7kF0=
+github.com/go-vgo/robotgo v0.100.0/go.mod h1:GCjwxRFoUkuekzm02WexYBV0paDLCbrrlKEfxt/CB10=
 github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=

          
@@ 37,30 41,45 @@ github.com/pmezard/go-difflib v1.0.0 h1:
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/robotn/gohook v0.30.6 h1:hH2KzyRsThtOqQ4M2ZPiOaib4vx70pz1DqgrBm26vPk=
 github.com/robotn/gohook v0.30.6/go.mod h1:FXryR68cDIho8rjE7MKxt4n4aK2FBqp+d/0HK4oiGP8=
+github.com/robotn/gohook v0.31.2 h1:ADIppQ3T0Sd+kaDMb4Vnv6UeSmhNfK0H0HpPWCd8G5I=
+github.com/robotn/gohook v0.31.2/go.mod h1:0BQit8783ey63WXFau8TvoaTYfNtsAhqZ0RJaqlYi6E=
 github.com/robotn/xgb v0.0.0-20190912153532-2cb92d044934 h1:2lhSR8N3T6I30q096DT7/5AKEIcf1vvnnWAmS0wfnNY=
 github.com/robotn/xgb v0.0.0-20190912153532-2cb92d044934/go.mod h1:SxQhJskUJ4rleVU44YvnrdvxQr0tKy5SRSigBrCgyyQ=
 github.com/robotn/xgbutil v0.0.0-20190912154524-c861d6f87770 h1:2uX8QRLkkxn2EpAQ6I3KhA79BkdRZfvugJUzJadiJwk=
 github.com/robotn/xgbutil v0.0.0-20190912154524-c861d6f87770/go.mod h1:svkDXUDQjUiWzLrA0OZgHc4lbOts3C+uRfP6/yjwYnU=
 github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc=
 github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
+github.com/shirou/gopsutil v3.21.8+incompatible h1:sh0foI8tMRlCidUJR+KzqWYWxrkuuPIGiO6Vp+KXdCU=
+github.com/shirou/gopsutil v3.21.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
 github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
 github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4=
 github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
+github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
+github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
 github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
 github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
+github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
+github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
 github.com/vcaesar/gops v0.20.0 h1:SqP/rzDJh7Dy4/Obrjqv+ZftUig+b46BECj9cQ1mCTw=
 github.com/vcaesar/gops v0.20.0/go.mod h1:EvpOHW7re4rBSzI1B/Rly0CxfT7C9lLo7Rpgs6IVpF0=
+github.com/vcaesar/gops v0.21.2 h1:OwWoJR0zb+AK41TN2adhZUP9lAmaRMzkWwf7Ux5Mx00=
+github.com/vcaesar/gops v0.21.2/go.mod h1:BEJGigAc9GORbEegWX9rRon/qwibjDs8p50WYm2KlXw=
 github.com/vcaesar/imgo v0.12.1 h1:/mmZPL0CQwXtdr73ouoCu8Z9gmfmdgaFYoG0YSZ1gdU=
 github.com/vcaesar/imgo v0.12.1/go.mod h1:MwS4X0FXkLxshu8jMrHCLEM0S6JOQ6lU3drq5nO9AdE=
+github.com/vcaesar/imgo v0.30.0 h1:ODQVX0EFJEh+WkKahCBtE0SqcDCIjl/kjiOplR0Ouh8=
+github.com/vcaesar/imgo v0.30.0/go.mod h1:8TGnt5hjaMgwDByvMFIzUDSh5uSea4n1tAbSvnhvA6U=
 github.com/vcaesar/tt v0.11.0 h1:obQecjgbnAxxC6OYGY6yDvhGRW2PR5wD8Ma2uJH3WGA=
 github.com/vcaesar/tt v0.11.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg=
+github.com/vcaesar/tt v0.20.0 h1:9t2Ycb9RNHcP0WgQgIaRKJBB+FrRdejuaL6uWIHuoBA=
+github.com/vcaesar/tt v0.20.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg=
 github.com/xxxserxxx/robotgo v0.0.0-20210819184435-3f2f186d5669 h1:gKJL1Ti0ufLgE3rNXB9I9vAnlEfczLZwTvvDwlYFi5U=
 github.com/xxxserxxx/robotgo v0.0.0-20210819184435-3f2f186d5669/go.mod h1:0+i2QWRmZtbIF02RwmiGfFj33Judprukd8ls5J6Eajg=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

          
@@ 68,6 87,8 @@ golang.org/x/crypto v0.0.0-2020062221362
 golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 h1:D0iM1dTCbD5Dg1CbuvLC/v/agLc79efSj/L35Q3Vqhs=
 golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
+golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
+golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=

          
@@ 80,6 101,10 @@ golang.org/x/sys v0.0.0-20201018230417-e
 golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 h1:C+AwYEtBp/VQwoLntUmQ/yx3MS9vmZaKNdw5eOpoQe8=
 golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 h1:QOQNt6vCjMpXE7JSK5VvAzJC1byuN3FgTNSBwf+CJgI=
+golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

          
@@ 89,3 114,5 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lH
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=