Autokey: Include more sophisticated regex/exclusion features

Created on 30 Jun 2018  ·  35Comments  ·  Source: autokey/autokey

Classification:

Feature

Reproducibility:

Always

Version

AutoKey-Py3 version 0.90.4 / 3.6

Installed via: sudo apt install autokey-gtk

Distro: Ubuntu GNOME 17.10

Summary

When creating shortcuts, you can provide a regular expression to make sure the shortcut only works on applications whose class names match. However, I have had much difficulty coming up with a regular expression that excludes applications based on their class name.

For example, I have shortcuts for copy and paste that send the <ctrl>+c and <ctrl>+v keys, respectively, to any application I'm on. However, I don't want these shortcuts to be enabled for the terminal (URxvt), since it doesn't register those keybindings as copy and paste and it performs a completely irrelevant action upon using those shortcuts.

It would be nice if I could simply provide a regular expression that excludes certain class names or, better yet, have the ability to simply click on a particular window to exclude it, just like how I can use the detect properties feature to include an application window.

Or maybe I can make it so that I can use one shortcut that sends different keys depending on the application I'm currently on. So <alt>+d would send <ctrl>+c to my browser while <alt>+d would send <ctrl>+<alt>+c to my terminal.

What I've Tried

I've tried using the regexes (?!^Terminal$)(^.*$) and ^((?!Terminal).)*$, to no avail. It doesn't seem like any exclusion filters work, from what I've searched on the internet. I don't even know which type of regex autokey uses.

I eventually decided on creating a shortcut that bound <alt>+d to the following script:

 if window.get_active_class() == 'urxvt.URxvt':
        keyboard.send_keys("<ctrl>+<alt>+c")
 else:
        keyboard.send_keys("<ctrl>+c")

I did the same for paste (replacing the c with v) and now copying and pasting works everywhere. However, I feel like for functionality as simple as this, it should be a standard feature and not something the user has to code in.

enhancement

Most helpful comment

@josephj11, you're right about the regex. It was working but moving the * in is what I was after to convey _anything else_ like ^((?!gnome-terminal).*)$

Also, window title not being negated was the root issue. Thank you for pointing that out! The fact that I want this phrase _not_ applied in a terminal window where the title changes based on dir/git repo/whatever I setup further complicates things. At least in Ubuntu 19.04, the default title starts with your username and hostname. So that's predictable for my new install. I'll have to figure something out on another Arch install that has pretty fancy titles.

For the moment I have the following regex which is working:

^((?!(gnome-terminal|[USER]@[HOSTNAME]:)).*)$ <- replace with your user/hostname

Ultimately, I think a new option to restrict matching to _only_ the window class would be really helpful.

"filter": {
  "regex": "^((?!gnome-terminal).)*$", 
  "isRecursive": false,
  "matchWindowClassOnly": true // defaults to false
}

:point_up: Something like that would be great.

All 35 comments

First of all, this should work, so it's a valid issue. I have never seen an example of a successful exclusion pattern. This is something a number of users (including me) want.

Problem: The version and version number of AutoKey you are reporting is impossible.

The old AutoKey went as far as 0.91.x. The new one (which used to be called autokey-py3 - and is now just autokey) started out somewhere around 0.93 and is currently (or about to be) 0.95 - depending on how you install it. If your version is reporting 0.90.4, it is approximately four years old and very out of date. (We don't control what versions of AutoKey are packaged by any distributions.)

Please backup all your macro and phrase definitions then completely uninstall AutoKey.
Next, you will have to install from source. See: Installation Instructions.

Once the new version is up and running, you can restore your phrases and macros from your backup. Don't forget to backup and restore the hidden files as well. There's one for each macro and phrase.

We currently only have PPAs for 16.04 and 18.04 and those probably won't work for you.

The manual install works fine, but sometimes there are issues with getting all the dependencies installed properly. If you get stuck during installation, the place to go for help is on Gitter.

You'll end up with a very much improved version of AutoKey, but it will probably still have your issue.

Fixes (if any) for this issue will only be applied to this new version, so you'll need it anyway.

AutoKey uses the regular expressions built into the Python standard library: https://docs.python.org/3/library/re.html
I tried it and it works for me.
I tried using 2 simple scripts (each with one keyboard.send_keys() line), and assigned the same hotkey to both.
I set one window filter to konsole.Konsole, matching my terminals window class, and set the other to ^((?!konsole\.Konsole).)*$, the negation. (This is equivalent to your second tried regular expression.)
Now I get different output depending on the currently active application…

@josephj11
Thanks for your informative response. It's true that I had an old version of autokey, and I recently figured that out. I have already gone through the process of uninstalling it and installing the newer version.

@luziferius
Thanks for letting me know more about how regular expressions are handled.

Is it possible that Autokey "swallows up" key triggers for phrases when the window filter excludes the current window?
I'm using the regex to exclude some apps, and the phrase is not entered. But, the original key is also not forwarded on to the app. Or at least that's how it appears.

@dogweather Can you please create a new issue for this? That is something else.
It makes tracking easier.

@luziferius Sure, will do.

This is an issue I also had. I dug into the code and found these lines to be the cause: https://github.com/autokey/autokey/blob/master/lib/autokey/model.py#L331-L337.

The logic here is that it tries to match the regular expression with either window title or class. This wouldn't work for exclusion because the regular expression must negate both title and class at the same time! Consider this example: we want to exclude window that has title dynamic_title_cannot_be_used and class specific_class, we would like to use re ^((?!specific_class).)*$ to exclude this window, but it _does_ match the title, making it useless.

@zefei :100: Thank you for digging up the location. So @dogweather’s issue is indeed related.
By judging the old test code, WM_CLASS matching was an after-thought and hacked in later. At this location, it breaks.
The code also breaks, if some application uses a window title that by chance is equal to another program’s WM_CLASS content.
This looks like the title/class matching should be fully de-coupled, so that only one property can be matched.

Uncoupling them would be an improvement anyway - you were already thinking about it. I just hope it's not too much work.

Is there any gut feeling about one of those being used more than the other? I.e., is it possible that nobody uses window class, and we can simply stop testing it, or vice-versa?

You need both. If you want to allow the script to run on any window owned by an application, you look at the class which is relatively invariant, but if you want a particular window from that application, you need to look at the title too - which varies arbitrarily and often does not include the application name.
E.g. Several windows might have a title like "Log In", but one might be a store on the web and another might be a local database. Both of these would also involve other possible windows from the same applications where you were already logged in and would need to take completely different actions (if any).
For now, once a script is running, it can get the title and the class from the AutoKey API , but that approach limits other AutoKey functionality and makes the script code more complicated.

Any update on this issue?

I've got the following for a phrase and it's still executing in gnome terminal.

"filter": {
  "regex": "^((?!gnome-terminal).)*$", 
  "isRecursive": false
}

This is in AutoKey 0.95.6 and the actual terminal window class is gnome-terminal-server.Gnome-terminal.

The above should _not_ match a string with anything following the regex portion but I tried
^((?!gnome-terminal-server\.Gnome-terminal).)*$ anyway which made no difference.

Am I doing this wrong? This seems to match the konsole.Konsole example above which sounded like it was working.

@kjs3 I'm not that familiar with Python regexes, so I may be wrong.

It appears that you are (correctly) attempting to match the entire window title/class by bracketing your pattern with ^ and $. If that's the case, shouldn't the trailing * be preceded by a . ? Otherwise, it seems to be saying zero or more occurrences of the whole preceding construction.

Also, since AFAIK, AutoKey looks at both the window title and class, getting negative conditions to work is particularly tricky because what works for the title may not work for the class and the reverse - as discussed above.

@josephj11, you're right about the regex. It was working but moving the * in is what I was after to convey _anything else_ like ^((?!gnome-terminal).*)$

Also, window title not being negated was the root issue. Thank you for pointing that out! The fact that I want this phrase _not_ applied in a terminal window where the title changes based on dir/git repo/whatever I setup further complicates things. At least in Ubuntu 19.04, the default title starts with your username and hostname. So that's predictable for my new install. I'll have to figure something out on another Arch install that has pretty fancy titles.

For the moment I have the following regex which is working:

^((?!(gnome-terminal|[USER]@[HOSTNAME]:)).*)$ <- replace with your user/hostname

Ultimately, I think a new option to restrict matching to _only_ the window class would be really helpful.

"filter": {
  "regex": "^((?!gnome-terminal).)*$", 
  "isRecursive": false,
  "matchWindowClassOnly": true // defaults to false
}

:point_up: Something like that would be great.

I did struggle with this as well – including @kjs3's example in the Wiki would be neat.

For me (Kubuntu 18.04, Autokey 0.95) the code of @kjs3 didn't work. I don't find any way to exclude a window (/program) from an autokey's phrase...

Ubuntu 18.04, Autokey 0.95.4 - @kj3's code didn't work for me either.

See issue #305 which will help with this if/when it is implemented.

Hi all,

Whilst having working regexp, including negation, would certainly be a step in the right direction, it's hardly user friendly, as the expressions are rather obtuse, even for those handy with regexps.

In AutoHotKey, it's as simple as saying IF !(WinInClass('Terminal) or WinInClass('Emacs'))
or whatever. But surely we can do better than this in the gui. At a bare minimum, a list of regexps with a checkbox for negation. If more ambitious, then an iTunes style interface where you can graphicly construct IF, NOT, AND, OR expressions for what windows you want it in.

Is anyone further along in doing this?

Thomas hasn't commented on this recently. He was gone for the summer and is back now. Since this seems to be a relatively important enhancement, it's likely that he will get to it (but I can't speak for him.) We don't know how hard it is to do yet.

Any update on this issue? I'm loving using Autokey, but can't come up with a Regex to exclude Auotkey of working in VS Code which class name is code.Code

Hi hakantakiri. I've been working pretty hard on a major revamp of autokey to support Emacs key overloading, and one of the things needed for that is solving this issue. I thought about what small revamp of regex I could do to fix this, and concluded the real way to solve it is the python way, to make the matching a mini script...

if re.match("google-chrome", window.active_class):
window.match = True
if re.match("autokey/autokey.* Google Chrome", window.active_title):
window.match = True
As such you would have complete control. I've been testing it on my machine, and it works pretty well, but I'm not quite ready to release it. I don't know if the maintainers here will be on board with it, if not I'll have to fork, because this is a really important enhancement. I've got other enhancements in there that support key combinations ala Emacs as well. You can preview it below, and if you're brave, run it.

https://github.com/xpusostomos/autokey/tree/issue305

@hakantakiri
I've found the next workaround to avoid regex using

I use python scripts to exclude VScode and Jetbrains IDEA:

ignore_list = ['jetbrains', 'code.Code']
if any(ignore in window.get_active_class() for ignore in ignore_list):
    keyboard.send_keys("<super>+i") 
else:
    keyboard.send_keys("<ctrl>+i")

@katoquro That sort of logic works, but the script still gets triggered on windows where it isn't appropriate and that stops other scripts/phrases from being triggered instead. That's why we need the root cause fixed.

@katoquro Thanks for your workaround, but I noticed it caused some misbehavior for some scripts and phrases I have for other use cases. Finally I found a Regex that properly excludes VS Code without messing with the source code, but it requires certain conditions:

"filter": {
        "regex": "^((?!Code$).)*$",
        "isRecursive": true
}

Notice that isRecursive is set to true otherwise it wont work. Also, all scripts or phrases you want to be excluded must be inside a folder and this filter must be set to that folder's json file: .folder.json.
The last problem I found, but it might be just an issue of mine, is that whenever I open a new VS Code window, the new window doesn't exclude Autokey until I restart it. So what I did is to build another script inside Autokey to reload itself whenever I need it. I mapped it to <ctrl>+F6 for easy access.

The python script I run to kill and restart Autokey is the following:

import os
os.system(''' pgrep autokey-gtk | xargs -I {} kill {} ; autokey-gtk ''')

@hakantakiri

In theory, the isRecursive just allows you to not have to add the same regex to each key combo in the folder

I'm not sure what you mean by new VS Code windows "doesn't exclude autokey". Maybe you mean autokey doesn't activate in a new window? Maybe you'd be better off running my version of the code, because it works fine for that scenario it seems. I've been fixing bugs along the way, and maybe there was one there I fixed. I think my version of the code is stable, but I haven't done a QT update, so as long as you're using GTK, you'd be fine. And I'm planning a menu item to install Emacs keybindings which I haven't done yet.

@xpusostomos I'll give more context; what I want to achieve is to avoid some Autokey scripts or phrases to work when I'm inside VS Code, this is because they misbehave horrendously because of conflicts with native VS Code keybindings. I thought that maybe a Regex filter could help me EXCLUDE my phrases and scripts to work in VS Code, and in my last response I explain that I somehow achieve that but at the cost of restarting Autokey every time after opening a new VS Code window.

Putting VS Code aside, the thing is that, as shown in this issue and others, there are some users as myself that need a way to exclude AutoKey to work on a given program. The opposite of what filter is meant to do. And is my understanding that they are considering this to be a feature, or at least provide a way to achieve it in the future, hopefully.

@hakantakiri Yes. Attached is a screen shot of the filter window in my version of the code where I exclude Emacs, Intellij, the terminal and VS Code from my emacs keybindings. And yes, it seems to work fine for newly created Code windows. Because its python you've got complete control.
Screenshot_2020-08-31_13-34-50

305 is where the code needs some attention. It will probably solve this issue as well. I'm glad see someone else working on our codebase.

Could one or both of you come over to Gitter and explain this "isRecursive" thing to me. I've never seen anyone even mention it before although I wondered what it was.

@xpusostomos Where we can find this fork? It looks OK for my cases (exclusion for some apps). For Idea and VS Code you can use more simple construction than regex like if with window_class.startswith('jetbrains')

@katoquro This would be in @xpusostomos 's personal repo. We'd love to get this into our develop branch, but our previous lead developer left the project and, currently, I'm the only one with full access and I'm not a Python/AutoKey dev, so I'm only merging trivial/isolated changes. As soon as someone qualified takes over as lead developer, we'll start merging the more substantial changes like this one into develop and working toward getting new releases out the door again.

@katoquro , my repository is here: https://github.com/xpusostomos/autokey As you point out, with this scheme you can use startswith, or regex or whatever you like. @josephj11 Do you want me to be the lead developer?

@xpusostomos Thanks for the offer!

Propose that on Gitter and see what feedback you get. It's not up to me. I just have the "keys".

When you do, tell us a bit about your qualifications. How much do you know about building releases for us and working with other developers to review and merge PRs? How much time do you have for AutoKey? And anything else you want to share.

There aren't any requirements to be lead developer per se, but I would want to know you plan to stick around for awhile and have the time and interest to do it.

We currently have at least two other developers and I would hope they would be good with you taking the lead. I certainly can't do it.

Totally aside: Can you give me a clue as to how to pronounce your name/handle? I'd also like to know what time zone you're in. It's not that important, but it gives us an idea of when you might be likely to respond to something when a discussion requires some back and forth.

@xpusostomos, could you explain a bit more? What the branch contains that feature? I've tried to install this branch

pip3 install --user https://github.com/xpusostomos/autokey/archive/issue305.zip

but it doesn't start

autokey-gtk --verbose                      
Traceback (most recent call last):
  File "/home/katoquro/.local/bin/autokey-gtk", line 11, in <module>
    load_entry_point('autokey==0.95.11', 'console_scripts', 'autokey-gtk')()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 480, in load_entry_point
    return get_distribution(dist).load_entry_point(group, name)
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2693, in load_entry_point
    return ep.load()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2324, in load
    return self.resolve()
  File "/usr/lib/python3/dist-packages/pkg_resources/__init__.py", line 2330, in resolve
    module = __import__(self.module_name, fromlist=['__name__'], level=0)
  File "/home/katoquro/.local/lib/python3.6/site-packages/autokey/gtkui/__main__.py", line 4, in <module>
    from autokey.gtkapp import Application
  File "/home/katoquro/.local/lib/python3.6/site-packages/autokey/gtkapp.py", line 18, in <module>
    import autokey.model.script
ModuleNotFoundError: No module named 'autokey.model'
Was this page helpful?
0 / 5 - 0 ratings