1

At the moment I'm using AutoHotKey for triggering scripts via keyboard shortcuts. I like programming in Python much more than dealing with AutoHotKey and every time I touch my AutoHotKey scripts I wish I could simply write clean AutoHotkey code.

Let's take the simply AutoHotKey script that prints hello world in whatever window I am when I press the insert key:

foo(){
    send, "hello world"
}
Insert:: foo()

How would I do the same thing in Python3 on Windows?

Christian
  • 25,249
  • 40
  • 134
  • 225
  • 1
    You probably can't without rewriting AutoHotKey. The major problem is focus, AHK appears to scan all input for its hotkeys, this is something that a pure Python program can't do because it only receives input when its own window has focus. You'd have to rely on special (but different) system hooks under macOS or linux, also. Put another way, AHK is doing some non-trivial work to get all input activity sent through it before the system sees it. – msw Dec 24 '17 at 02:59
  • @msw : I accept that I can't yet do [`import antigravity`](https://xkcd.com/353/) but this task doesn't seem to me exotic enough that I'm the only person who desires to be able to do it, so it would be strange if there's no library for it. – Christian Dec 24 '17 at 12:37
  • Maybe write a parser then that compiles from Python into AHK’s scripting language? – poke Dec 24 '17 at 12:51
  • or you can use a Autohotkey script that run macroexamples.pyw - .pyw extensions can run python scripts without focus the windows self. insert:: run "c:\macro1.pyw" return - and than you can make to send any text or keyboard shortcuts macros movements with [pyautogui packages](http://pyautogui.readthedocs.io/en/latest/introduction.html#examples) and [pywinauto packages](https://pywinauto.github.io/) – stevecody Dec 24 '17 at 13:10

2 Answers2

1

You will have to hook into the gizzards of windows to achieve this. You'd probably have to do that via the ctypes or CFFI module, because the necessary API's don't seem to exist in pywin32.

According to this page, you will need to use three windows API calls:

  • SetWindowsHookEx, using the idHook WH_KEYBOARD_LL to set up a keyboard hook; a function that peeks at keyboard events.
  • UnhookWindowsHookEx to eventually remove the hook.
  • And if you're not interested in a particular keypress, CallNextHookEx to pass it on to the next hook.
Roland Smith
  • 42,427
  • 3
  • 64
  • 94
0

You can combine two answers from StackOverflow to (almost) solve this issue.

  1. Use this answer (by tehvan) to create a getch() like method to read in one character from the user without the need for a \n. (repeated below from the answer)
  2. Use the Python3 version of this answer (by Barafu Albino) to call the previously defined _Getch() class in a separate process.

Please note that the following code works for Python3 only and uses any key to stop the process, not just the insert key.

# This code is a combination of two StackOverflow answers
# (links given in the answer)

# ------- Answer 1 by tehvan -----------------------------------
class _Getch:
    """Gets a single character from standard input.  
       Does not echo to the screen."""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            self.impl = _GetchUnix()

    def __call__(self): return self.impl()

class _GetchUnix:
    def __init__(self):
        import tty, sys

    def __call__(self):
        import sys, tty, termios
        fd = sys.stdin.fileno()
        old_settings = termios.tcgetattr(fd)
        try:
            tty.setraw(sys.stdin.fileno())
            ch = sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
        return ch

class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()

getch = _Getch()

# -------- Answer 2 by Barafu Albino (modified) -------
import _thread

def input_thread(a_list):
    _Getch().__call__()
    a_list.append(True)

def do_stuff():
    a_list = []
    _thread.start_new_thread(input_thread, (a_list,))
    print('Press any key to stop.')
    while not a_list:
        pass  
        # This is where you can put the stuff 
        # you want to do until the key is pressed
    print('Stopped.')

do_stuff()
  • When I start the script via the command line it doesn't do anything when it isn't in focus. – Christian Dec 24 '17 at 12:48
  • Oops. That’s true. This won’t work out of focus. I’ll keep this answer up for the time being while I try to get a workaround. –  Dec 24 '17 at 18:18