mooz / xkeysnail

Yet another keyboard remapping tool for X environment
890 stars 112 forks source link

Seeking working launch() and arbitrary (python) command examples #149

Closed RedBearAK closed 1 year ago

RedBearAK commented 2 years ago

The example config.py has this line, but it doesn't seem to work as expected:

    K("C-o"): [K("C-a"), K("C-c"), launch(["gedit"]), sleep(0.5), K("C-v")]

I just noticed a strange phenomenon when I run xkeysnail with a script in a terminal, this line in my config causes the startup to pause for 5 seconds (this needs import time):

    K("RC-Shift-M-l"):      [launch(["gedit"]), time.sleep(5), K("Enter"), K("t"), K("e"), K("x"), K("t"), K("Enter")],

So it's like the time.sleep(5) function in this example is actually running once during the startup of xkeysnail, rather than running when I trigger the shortcut.

If I change it to this:

    K("RC-Shift-M-l"):      [time.sleep(5), K("Enter"), K("t"), K("e"), K("x"), K("t"), K("Enter")],

This will just instantly insert [Enter, "text", Enter] into an app like Gedit. It's as if the time.sleep(5) isn't even there inside the macro.

I also can't get anything to happen after a launch() command, if I try to use it in a multi-keystroke macro, like in the config.py example. The line below will just launch Gedit and then do nothing else. No insertion of characters.

    K("RC-Shift-M-l"):      [launch(["gedit"]), K("Enter"), K("t"), K("e"), K("x"), K("t"), K("Enter")],

Adding the time.sleep() function, since it is being ignored (or executed in a different context?), doesn't do anything different. The only thing that happens is the Gedit launch:

    K("RC-Shift-M-l"):      [launch(["gedit"]), time.sleep(5), K("Enter"), K("t"), K("e"), K("x"), K("t"), K("Enter")],

Putting another launch() command after the first one results in only the first one working and nothing else:

    K("RC-Shift-M-l"):      [launch(["gedit"]), launch(["transmission"]), time.sleep(5), K("Enter"), K("t"), K("e"), K("x"), K("t"), K("Enter")],

Anybody have working examples of other launch() commands, especially in a macro with other things after it, or examples of running arbitrary python commands successfully?

@mooz @rbreaves @Lenbok

Lenbok commented 2 years ago

I use launch() but not as part of a macro sequence.

Your config file is evaluated at startup, so those entries in your keymap get executed then. Note that launch() (and sleep()) return a function to be called to do the work, see https://github.com/mooz/xkeysnail/blob/master/xkeysnail/transform.py#L114 (whereas your time.sleep(5) just executes immediately when your config is evaluated).

The actual triggered processing of keymap entries looks to be executed at https://github.com/mooz/xkeysnail/blob/master/xkeysnail/transform.py#L490 and there are a few of those that look like they can break out of the loop, including the processing of callables (such as that returned by launch and sleep) if they don't return something that can subsequently be processed itself by handle_commands in a way that makes it return False/None. However it looks as though a callable command could just return a list of commands to execute, so you may be able to make custom launch and sleep functions in your config that take the next set of commands to do, e.g (untested):

def launch_and_then(command, next):
    """Launch command"""
    def launcher():
        from subprocess import Popen
        Popen(command)
        return next
    return launcher

def sleep_and_then(sec, next):
    """Sleep sec in commands"""
    def sleeper():
        import time
        time.sleep(sec)
        return next
    return sleeper

with a keymap entry like:

    K("RC-Shift-M-l"):      [launch_and_then(["gedit"], sleep_and_then(5, [K("Enter"), K("t"), K("e"), K("x"), K("t"), K("Enter")]))],
RedBearAK commented 2 years ago

@Lenbok

Oh. Wow. I'm going to have to try this. Very interesting.

I barely understand what is happening here so if this is not already a working example I probably won't get very far getting it working. But I'll sure try it when I get a chance.

Thanks for the pointers to the code responsible for making this kind of thing work. That's also part of what I was looking for.

RedBearAK commented 2 years ago

@Lenbok

Hey, guess what? It worked. No more pausing at xkeysnail startup. The delay is part of the macro now.

Kind of awkward syntax, with everything being nested inside of the launch_and_then, but it really works.

This still only allows, basically, launch() and sleep() as actual functions, if I understand. So if I want to do anything else, or even be able to use these in a different order, I'll have to figure out how to modify the def without making anything crash.

Edit: Nevermind, they work in any order. This also works (wait 3 sec, open Gedit, wait 3 sec, insert text):

[sleep_and_then(3, launch_and_then(["gedit"], sleep_and_then(3, [K("Enter"), K("t"), K("e"), K("x"), K("t"), K("Enter")])))],
Lenbok commented 2 years ago

Yep, you just can't have anything after one of the callables in your binding. Agree on the syntax, but it's more a proof of concept, you could make a nicer "command sequence builder" function, or probably the handle_commands should just be changed to work how it seems to be intended.

RedBearAK commented 2 years ago

@Lenbok

So it's just not set up, in its current form, for launch() and sleep() to be used sequentially like all the individual K() functions. But it could be. That would be a great improvement. Kind of beyond me.

But I can definitely work with this already. Hugely helpful.