How to exit a loop with hotkeys

4

I have an application that runs in the background in an infinite loop. How do I stop the loop using hotkeys ( Ctrl + F1 ~ F12 )?     

asked by anonymous 21.06.2015 / 04:01

1 answer

5

First, it takes a means of detecting that the user is trying to press a key without actually reading that key (otherwise the loop would stop and wait for user input). According to this question in SOen , this is done through the select " or - as the relevant function is not supported in Windows - msvcrt.kbhit .

Windows

An example in Windows would be:

>>> indice = 0
>>> while not msvcrt.kbhit() and indice < 1000000:
...   indice = indice + 1
...
_

(cursor blinks, but loop continues to execute, until user enters any key)

>>> indice
401247

As for detecting hotkeys , what I noticed in my tests (I do not know if this is the best way to do it) is that if you try to read the user input using msvcrt.getch " (still in Windows) after such a sequence, it will give \x00 (F1 to F10) or \xe0 (F11 and F12):

>>> msvcrt.getch()
'\x00'

And if you try to read again , then it will generate a second character, depending on the sequence you typed:

Ctrl + F1

>>> msvcrt.getch()
'\x00'
>>> msvcrt.getch()
'^'    

Ctrl + F10

>>> msvcrt.getch()
'\x00'
>>> msvcrt.getch()
'g'    

Ctrl + F12

>>> msvcrt.getch()
'\xe0'
>>> msvcrt.getch()
'\x8a'

Etc

F1      F2      F3      F4      F5      F6
'\x00^' '\x00_' '\x00'' '\x00a' '\x00b' '\x00c'

F7      F8      F9      F10     F11        F12
'\x00d' '\x00e' '\x00f' '\x00g' '\xe0\x89' '\xe0\x8a'

The complete code would then be:

while True:
    # Faz alguma coisa

    if msvcrt.kbhit():
        a = msvcrt.getch()
        b = msvcrt.getch() if msvcrt.kbhit() else None
        if a == '\x00' and b == '^': # Ctrl + F1
            break

POSIX

On Linux (and presumably also on Mac) the solution involves select to do polling on the keyboard, and termios to get the keys pressed (such as detailed my answer to a related question):

>>> import sys, tty, termios, select
>>> def getch():
...   fd = sys.stdin.fileno()
...   old_settings = termios.tcgetattr(fd)
...   try:
...     tty.setraw(fd)
...     ch = sys.stdin.read(1)
...   finally:
...     termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
...   return ch
... 
>>> def kbhit():
...   fd = sys.stdin.fileno()
...   old_settings = termios.tcgetattr(fd)
...   try:
...     tty.setraw(fd)
...     i,o,e = select.select([sys.stdin],[],[],0.0001)
...     for s in i:
...       if s == sys.stdin:
...         return True
...     return False
...   finally:
...     termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
... 
>>> while True:
...   # Faz alguma coisa
...   if kbhit():
...     if getch() == '\x1b':
...       a, b, c, d = getch(), getch(), getch(), getch()
...       if a == '[' and b == '1' and c == '1' and d == '~': # Ctrl + F1
...         break
... 
>>>

In my tests it generated 5 characters, the first one always \x1b and the following 4 in this way:

F1:  [ 1 1 ~
F2:  [ 1 2 ~
F3:  [ 1 3 ~
F4:  [ 1 4 ~
F5:  [ 1 5 ~
F6:  [ 1 7 ~
F7:  [ 1 8 ~
F8:  [ 1 9 ~
F9:  [ 2 0 ~
F10: [ 2 1 ~
F11: [ 2 3 ~
F12: [ 2 4 ~

Reiterating, I do not know if this is the best way, or if it is portable. But it worked successfully in my tests on Windows 7 and Ubuntu.

    
21.06.2015 / 05:14