Is there a way to set a specified key delay between keystrokes?

536 views
Skip to first unread message

JC

unread,
Jul 23, 2021, 6:52:05 PM7/23/21
to autokey-users
I think AutoKey is typing too fast for the program I'm trying to have it work with, because it only gets the first character in and then that's it (tabbing and opening menus and stuff).

JC

unread,
Jul 23, 2021, 7:13:21 PM7/23/21
to autokey-users
To be clearer, I already know we can use time.sleep(#) but it would be nice to not have to do that and make individual keystrokes if possible.

Little Girl

unread,
Jul 23, 2021, 8:16:38 PM7/23/21
to autoke...@googlegroups.com
Hey there,
Yep. This waits two seconds and then prints hello world to the active
window:

```
import time
time.sleep(2)
keyboard.send_keys("hello world")
```

I have a feeling you'd really enjoy the AutoKey wiki:

https://github.com/autokey/autokey/wiki

Especially the "Scripting" page:

https://github.com/autokey/autokey/wiki/Scripting

--
Little Girl

There is no spoon.

JC

unread,
Jul 25, 2021, 12:43:40 AM7/25/21
to autokey-users
Thanks and I did see that, but what I mean is: is there a way to globally apply a per-key delay across all AutoKey phrases and scripts?
As if every keyboard.send_keys() character is trailed by something like time.sleep(0.001).

AutoHotkey can do this with no problem through SetKeyDelay.

jos...@main.nc.us

unread,
Jul 25, 2021, 2:35:03 AM7/25/21
to autoke...@googlegroups.com
Take a look at https://github.com/autokey/autokey/issues/566. This
proposed enhancement wouldn't be global, but it could be set for each
phrase from the GUI with no scripting required. (And you could bulk edit
it into a bunch of phrase json files if you know a little bash/sed/awk.)

We have had problems with this forever. Thomas, our former lead developer,
built a test version with extended delays in it, but it didn't fix all the
problems and it made AutoKey's overall performance awful to the point of
embarrassment. Any fix for something like this needs to be applied
surgically, not globally. Most things don't need it at all and those that
do may require different amounts of delay.

Joe

> Thanks and I did see that, but what I mean is: is there a way to globally
> apply a per-key delay across all AutoKey phrases and scripts?
> As if every keyboard.send_keys() character is trailed by something like
> time.sleep(0.001).
>
> AutoHotkey can do this with no problem through SetKeyDelay
> <https://www.autohotkey.com/docs/commands/SetKeyDelay.htm>.
> On Friday, July 23, 2021 at 7:16:38 PM UTC-5 Little Girl wrote:
>
>> Hey there,
>>
>> JC wrote:
>>
>> >I think AutoKey is typing too fast for the program I'm trying to
>> >have it work with, because it only gets the first character in and
>> >then that's it (tabbing and opening menus and stuff).
>>
>> Yep. This waits two seconds and then prints hello world to the active
>> window:
>>
>> ```
>> import time
>> time.sleep(2)
>> keyboard.send_keys("hello world")
>> ```
>>
>> I have a feeling you'd really enjoy the AutoKey wiki:
>>
>> https://github.com/autokey/autokey/wiki
>>
>> Especially the "Scripting" page:
>>
>> https://github.com/autokey/autokey/wiki/Scripting
>>
>> --
>> Little Girl
>>
>> There is no spoon.
>>
>
> --
> You received this message because you are subscribed to the Google Groups
> "autokey-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to autokey-user...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/autokey-users/68ed9feb-b761-4748-aac6-3b2fbb3f8733n%40googlegroups.com.
>


Little Girl

unread,
Jul 25, 2021, 10:07:04 AM7/25/21
to autoke...@googlegroups.com
Hey there,

JC wrote:

>Thanks and I did see that, but what I mean is: is there a way to
>globally apply a per-key delay across all AutoKey phrases and
>scripts? As if every keyboard.send_keys() character is trailed by
>something like time.sleep(0.001).

Not currently (see Joe's replies, too, for more details on the
possibilities). There are some work-arounds or approaches that can be
taken, though.

====================

Joe provided the "Function to Type Text Slowly" section in the
"Advanced Scripts" wiki page. This controls the speed with which each
character is printed:

https://github.com/autokey/autokey/wiki/Advanced-Scripts#function-to-type-text-slowly

====================

Another option, if you want a delay before printing, is to create a
script with a function in it that inserts a delay and then uses
keyboard.send_keys() to print the specified text. Then import that
script into any script you'd like to use it in and use it instead of
keyboard.send_keys("your text here").

For example, you could create the foo script with these contents,
editing the number in the delay to the length of time you'd like it
to wait before printing:

import time

def bar(message, delay=2):
time.sleep(delay)
keyboard.send_keys(message)

Then you can import the foo script in any other script and then use
its bar function to print your text:

import foo

bar("HELLO WORLD")

Since the delay is optional in that function, it doesn't have to be
specified, but you can specify one if you like. So, for example, if
you come across a specific situation in which you need a shorter or
longer delay than the default one specified in the foo script, you
can import the foo script into any other script and then add a custom
delay to the function call in that script. In this example, I
specified an 8-second delay that will override the default 2-second
one:

import foo

bar("HELLO WORLD", 8)

Of course, these would break if you ever rename or remove or
drastically change the code in the foo script, but as long as none
of those happen, it will work smoothly.

Little Girl

unread,
Jul 25, 2021, 10:59:50 AM7/25/21
to autoke...@googlegroups.com
Hey there,

JC wrote:

> To be clearer, I already know we can use time.sleep(#) but it would
> be nice to not have to do that and make individual keystrokes if
> possible.

I thought I'd respond to this separately. Note that this stuff below
is just my opinion and not necessarily that of the development team
and that I mean no offense by any of it. I'm just a heavy admirer of
AutoKey exactly as it is, so that's the direction I'll be coming from.

I realize that it would be nice if certain features or abilities were
available in the interface that aren't currently there. Some have
already been submitted as enhancement requests and you're always
welcome to do that for any features you'd like that aren't currently
available. To do so, follow the steps in this section of the FAQ:
https://github.com/autokey/autokey/wiki/FAQ#what-if-i-would-like-to-suggest-a-new-feature-for-autokey

There are, however, a few reasons why it may not be advisable to put
too many of those sorts of default features or abilities into a
program.

One is that the more of them that you add, the more difficult it
becomes to maintain and/or upgrade the program, because everything
has to work with everything else.

Another very important one is that the more of them that you want to
add, the more you have to find a way to get into the mind of your
potential users to determine which features and abilities they might
want to have available. Sadly, in the development world, you usually
don't hear from your users unless they're unhappy, so getting that
sort of information can be frustrating or difficult. You also always
run the risk of making someone happy and someone else unhappy since
you may inadvertently leave out the very feature or ability that some
users consider indispensable or you may include a feature or ability
in a certain way that some users disagree with, etc.

Last, but definitely not least, the beauty of AutoKey is that it
attempts to be a simplistic interface that stays out of your way,
doesn't make decisions for you, gives you a few goodies that you're
free to mess around with, and turns you loose to mess around with
those goodies if you like and/or do anything at all that your little
heart desires within only the limits of your ability to code or find
other peoples' code. That's sort of the opposite of a predefined
interface. You're literally being handed the keys to the kingdom.

JC

unread,
Jul 25, 2021, 2:48:36 PM7/25/21
to autokey-users
That all makes sense. I guess my bottom line is that I'm a bit taken aback that these features, even if they have to be custom-made user libraries, are not as readily discoverable as they seem like they should be (the literal mouse movement, etc.).

It would be great if there could be some sort of functions library here, not just a scripts library, I guess, then.

Little Girl

unread,
Jul 25, 2021, 4:38:36 PM7/25/21
to autoke...@googlegroups.com
Hey there,

JC wrote:

>That all makes sense. I guess my bottom line is that I'm a bit taken
>aback that these features, even if they have to be custom-made user
>libraries, are not as readily discoverable as they seem like they
>should be (the literal mouse movement, etc.).

I'm sorry about that. Everything in this project is done by
volunteers. Sometimes things like that can be overlooked. It's good
to have the "holes" pointed out, though, since that makes a nice
road-map for others to follow when deciding how to contribute. You're
already helping just by bringing this sort of thing up.

>It would be great if there could be some sort of functions library
>here, not just a scripts library, I guess, then.

AutoKey does come with a few phrases and scripts to start you off,
but maybe it would be a good idea to add in some functions as a
separate thing, or at least make sure some of them contain some more
specific functions that might be considered useful. You might want to
suggest that in an enhancement request. Meanwhile, you can also store
functions inside of scripts or you can create a new folder and keep
them in there.

jos...@main.nc.us

unread,
Jul 26, 2021, 8:03:36 AM7/26/21
to autoke...@googlegroups.com
:Thumbs up:

Joe
> --
> You received this message because you are subscribed to the Google Groups
> "autokey-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to autokey-user...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/autokey-users/60fd7c65.1c69fb81.5c102.4998%40mx.google.com.
>


jos...@main.nc.us

unread,
Jul 26, 2021, 9:38:22 AM7/26/21
to autoke...@googlegroups.com
Importing time is not necessary. (but it won't hurt anything if you do.)

However, what you said applies to any other modules that you do need to
import before using them.

AutoKey provides a Modules directory from which modules/functions can be
imported.

However, code imported in this way does not have access to the AutoKey API
which really limits its usefulness.

https://github.com/autokey/autokey/issues/248

will fix this if/when it gets implemented.

I do have a piece of code with a workaround for this problem if anyone
really needs it. It's a bit ugly and has to be included in each such
module.

Joe
> --
> You received this message because you are subscribed to the Google Groups
> "autokey-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to autokey-user...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/autokey-users/60fd7006.1c69fb81.72f5c.a31f%40mx.google.com.
>


jos...@main.nc.us

unread,
Jul 26, 2021, 9:46:46 AM7/26/21
to autoke...@googlegroups.com
That's a great idea and I have been lobbying for its creation.

One thing is holding it back from being really easy to use and that will
be fixed if/when this enhancement gets implemented.

https://github.com/autokey/autokey/issues/248

Joe
> --
> You received this message because you are subscribed to the Google Groups
> "autokey-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to autokey-user...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/autokey-users/e5891c16-4b35-43b7-a4ac-0758695fe638n%40googlegroups.com.
>


Little Girl

unread,
Jul 26, 2021, 3:50:41 PM7/26/21
to autoke...@googlegroups.com
Hey there,

jos...@main.nc.us wrote:

>Importing time is not necessary. (but it won't hurt anything if you
>do.)

Oh, gosh. I had no idea. You just now had me running around removing
it from a hundred million snippets of code that I have stashed in my
copy of AutoKey. Now I just need to remember not to add it to any new
scripts.

>However, what you said applies to any other modules that you do need
>to import before using them.
>
>AutoKey provides a Modules directory from which modules/functions
>can be imported.

Yep. It's specified under "Script Engine" in the AutoKey settings in
this copy of AutoKey.

>However, code imported in this way does not have access to the
>AutoKey API which really limits its usefulness.
>
>https://github.com/autokey/autokey/issues/248
>
>will fix this if/when it gets implemented.

Ah, okay.

>I do have a piece of code with a workaround for this problem if
>anyone really needs it. It's a bit ugly and has to be included in
>each such module.

Definitely. I grabbed it from the link you gave above. Is that it or
do you have a different one? Also, if that's it, I'm not clear on
what you mean in the implementation note, so if you could, I'd love a
simplified version of that or a real-world example of it in use
since this sounds like a handy tool to have in the toolbox in the
event that it takes a while to implement that fix.

By the way, I messed up in my earlier message. I didn't mean to
demonstrate importing another script, but instead meant to
demonstrate running one, which seems to give full access to the API
without having to put anything in the modules directory. You can just
work with your existing scripts as they're sitting in AutoKey,
running one from another at will. So, just to get this right, I'll
make your correction of removing the import of the time module and my
correction of replacing the other import statement.

Here are the updated contents of the example foo script that gets
used by other scripts:

def bar(message, delay=2):
time.sleep(delay)
keyboard.send_keys(message)

And here are the updated contents of the example bar script that runs
the foo script as a helper and uses its bar function to print a
message:

engine.run_script("foo")
bar("HELLO WORLD")

Last, but not least, here are the updated contents of the example bar
script that runs the foo script as a helper and uses its bar function
to print a message, with the difference being that this one specifies
an 8-second delay that overrides the default 2-second one:

engine.run_script("foo")
bar("HELLO WORLD", 8)

jos...@main.nc.us

unread,
Jul 27, 2021, 10:17:33 AM7/27/21
to autoke...@googlegroups.com
That's the same code I have.

I have never actually tried to use it (and Thomas is gone.)

Rereading it just now left me a bit confused. I'm not sure how to use it
correctly, but if you want to try, I'll help if I can.

Making an AutoKey script with the code to inject would be cool - after we
know what we're doing. It's doable, but emitting structured text is a PITA
because many apps (kate ...) tend to take liberties with it and Python
gets upset if your indentation isn't correct.

There was a thread about it somewhere recently. I thought I saved it, but
I don't see it now. The basic idea was to place the cursor at the start of
a properly indented line, count how many leading spaces there are and then
prefix each new emitted line with that many spaces.

I guess you can import time, but it cost you time. :)

Joe

Here's the whole note I had saved. Assume that the email system will screw
up the indentation.

Thomas Hess @luziferius 08:15
The API calls are injected into the script’s __globals__dictionary, before
execution starts. Everything in this dictionary can be used like the
normal builtins. If you import modules, those don’t get the API functions
injected automatically, thus can’t use the API. It is a bit unfortunate. I
think, we should override the import builtin somehow, so that it imports
the module and injects the API functionality. In the meanwhile, you can
add an injector function to your module, somewhat like this:

def load_api(api_keyboard, api_mouse, api_store, api_system, api_window,
api_clipboard, api_highlevel, api_dialog, api_engine):
global keyboard, mouse, store, system, window, clipboard, highlevel,
dialog, engine # Define the API class instances as globals
# then put the given instances into the script globals
keyboard = api_keyboard
mouse = api_mouse
store = api_store
system = api_system
window = api_window
clipboard = api_clipboard
highlevel = api_highlevel
dialog = api_dialog
engine = api_engine

Then in your script do this:

import my_module
my_module.load_api(keyboard, mouse, store, system, window, clipboard,
highlevel, dialog, engine)
You can’t use this with script style modules (that have executed code on
the top level that does API calls), only with modules that define
functions/classes for later usage by the importing script.

Thomas Hess @luziferius 08:21
For reference, the injection happens here:
https://github.com/autokey/autokey/blob/78570d02e8c5c57527e861c722056c1d8a73fc4a/lib/autokey/service.py#L439-L453
and
https://github.com/autokey/autokey/blob/78570d02e8c5c57527e861c722056c1d8a73fc4a/lib/autokey/service.py#L460
_

Thomas Hess @luziferius 05:00
You can also try: from autokey import scripting, scripting_highlevel
inside your module and then instantiate the desired API functions
yourself, like done in the first link above. I haven’t tried it, but
autokey is in the global Python module search path, so you can use it as a
library.
Well, I used it as a library to test stuff on the interactive prompt, so
it works that way.
If you get ImportError: cannot import name 'ConfigManager' or similar, you
run into circular dependencies that have to be broken by importing the
module containing the indicated name first. (Even if you don’t use it.)
Thomas Hess @luziferius 05:07
You then side-step the API provision mechanism and are on your own. It
breaks, if the code you are importing is moved during refactoring.
_
> --
> You received this message because you are subscribed to the Google Groups
> "autokey-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to autokey-user...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/autokey-users/60ff120f.1c69fb81.98c7b.4397%40mx.google.com.
>


jos...@main.nc.us

unread,
Jul 27, 2021, 10:29:40 AM7/27/21
to autoke...@googlegroups.com
I just reread this and noticed that Thomas' last comment seems to imply
that you can import live code, not just function definitions. That's
interesting by itself although not directly relevant here.

Joe
> https://groups.google.com/d/msgid/autokey-users/3a70dfd1bae65393fc8983168dd7ee99.squirrel%40main.nc.us.
>


Little Girl

unread,
Jul 28, 2021, 2:45:11 PM7/28/21
to autoke...@googlegroups.com
Hey there,

jos...@main.nc.us wrote:

>Rereading it just now left me a bit confused. I'm not sure how to
>use it correctly, but if you want to try, I'll help if I can.

Okay. I'm confused by it, too.

>Making an AutoKey script with the code to inject would be cool -
>after we know what we're doing. It's doable, but emitting structured
>text is a PITA because many apps (kate ...) tend to take liberties
>with it and Python gets upset if your indentation isn't correct.

At least indentation is within our easy control. We just have to go
through any script and verify that each line is as it should be.

>There was a thread about it somewhere recently. I thought I saved
>it, but I don't see it now. The basic idea was to place the cursor
>at the start of a properly indented line, count how many leading
>spaces there are and then prefix each new emitted line with that
>many spaces.

No idea, but I tend to clean up scripts by putting tabs into them
instead of spaces at my end.

>I guess you can import time, but it cost you time. :)

Yep. It doesn't seem to do any harm, but it's pointless. It's gone in
all my current scripts, but I'm not sure if I'll remember not to use
it in the future.

>Here's the whole note I had saved. Assume that the email system will
>screw up the indentation.
>
> Thomas Hess @luziferius 08:15
>The API calls are injected into the script’s __globals__dictionary,
>before execution starts. Everything in this dictionary can be used
>like the normal builtins. If you import modules, those don’t get the
>API functions injected automatically, thus can’t use the API. It is
>a bit unfortunate. I think, we should override the import builtin
>somehow, so that it imports the module and injects the API
>functionality. In the meanwhile, you can add an injector function to
>your module, somewhat like this:

Yep. I got that from the issue link you had provided.

>def load_api(api_keyboard, api_mouse, api_store, api_system,
>api_window, api_clipboard, api_highlevel, api_dialog, api_engine):
> global keyboard, mouse, store, system, window, clipboard,
> highlevel,
>dialog, engine # Define the API class instances as globals
> # then put the given instances into the script globals
> keyboard = api_keyboard
> mouse = api_mouse
> store = api_store
> system = api_system
> window = api_window
> clipboard = api_clipboard
> highlevel = api_highlevel
> dialog = api_dialog
> engine = api_engine

I got this, too, and used it with proper indentation (in my case,
with tabs instead of spaces for the indentation) in the my_module.py
file inside of my modules directory. And since the instructions
mentioned that you have to put anything that's in that file into a
class or function, I also added this line below it as a test:

def foobarbaz():
dialog.info_dialog(title='EXAMPLE', message="HELLO WORLD")

>Then in your script do this:
>
>import my_module
>my_module.load_api(keyboard, mouse, store, system, window, clipboard,
>highlevel, dialog, engine)

I did that and got this:

NameError: global name 'dialog' is not defined

I have no idea how to move forward from here, but am willing to mess
around with it.

>You can’t use this with script style modules (that have executed
>code on the top level that does API calls), only with modules that
>define functions/classes for later usage by the importing script.

Ah, this is what I referenced above.

>Thomas Hess @luziferius 05:00
>You can also try: from autokey import scripting, scripting_highlevel
>inside your module and then instantiate the desired API functions
>yourself, like done in the first link above. I haven’t tried it, but
>autokey is in the global Python module search path, so you can use
>it as a library.

No, I can't. The autokey module isn't available to me by default. I'll
need to figure out how to go about getting to it.

>Well, I used it as a library to test stuff on the interactive
>prompt, so it works that way.

I'd love a working example so I can see it in action.

>If you get ImportError: cannot import name 'ConfigManager' or
>similar, you run into circular dependencies that have to be broken
>by importing the module containing the indicated name first. (Even
>if you don’t use it.) Thomas Hess @luziferius 05:07

I'll face that when I come to it.

Little Girl

unread,
Jul 28, 2021, 8:53:10 PM7/28/21
to autoke...@googlegroups.com
Hey there,

jos...@main.nc.us wrote:

>I just reread this and noticed that Thomas' last comment seems to
>imply that you can import live code, not just function definitions.
>That's interesting by itself although not directly relevant here.

I look forward to seeing that happening, too. Meanwhile, I fixed my
code to use the module's namespace when making the call to the
function inside of my module:

mymodule.foobarbaz()

Even so, I'm still getting "NameError: name 'highlevel' is not
defined" as an error when I run it in my old buggy copy of AutoKey.
When I run it in the latest version of Kubuntu with the more current
version of AutoKey, I get "SyntaxError: name 'api_engine' is
parameter and global'
Reply all
Reply to author
Forward
0 new messages