1

I have a while that do something in an infinite, nonstop loop.

It should not be stopped or wait for user input. But I need user can stop while with specific key press. For example if user press f do someting new or p something else.

How should I get user key press in a nonstop while?

n = 1
while True:
    # do somthing 
     n += 1
    if <press p >
        # do task 1
    if <press f >
        # exit while


## do somthing else

I can't use keyboard library because need sudo privilege on Linux

JACK
  • 65
  • 6
  • [This question](https://stackoverflow.com/questions/3762881) might be helpful – Marat Sep 15 '22 at 13:55
  • You can solve it in a simpler way with threads, start this loop in a thread, run another loop in mian for user input waiting. Yes, this will need a key + Enter combination to work . If they key is not valid, continue the main loop. – Kris Sep 15 '22 at 14:00
  • Can you give me a code example for this method? – JACK Sep 15 '22 at 14:18
  • `msvcrt` is a windows specific library I need to run on both windows and Linux. – JACK Sep 15 '22 at 14:38

2 Answers2

0

As @Kris said in comment, threading + queue can do a trick. Here is just an example, it needs a few fixes (messes up terminal look) but should be a great start for you (this won't work on windows in this form, but just to give you example how to use it). Also, before using threading please read docs of threading, especially parts about global interpreter lock

import sys
import tty
import threading
import queue
import termios

def watcher(q: queue.Queue):
    # To bring back terminal look. More info here https://docs.python.org/3/library/termios.html
    fd = sys.stdin.fileno()
    old_Settings = termios.tcgetattr(fd) 

    while True:
        # Reads 1 char form sdin without need of newline
        tty.setraw(sys.stdin.fileno())
        i = sys.stdin.read(1)
        if i == "p":
            q.put_nowait("p")
            termios.tcsetattr(fd,termios.TCSADRAIN,old_Settings)
        elif i == "f":
            q.put_nowait("f")
            termios.tcsetattr(fd,termios.TCSADRAIN,old_Settings)
            break


def runner(q: queue.Queue):
    n = 1
    while True:
        n += 1
        # You need to check if q is empty, or It will throw an empty err
        if not q.empty():
            key = q.get_nowait()
            if  key == "f":
                print("Got exit event after {} loops".format(n))
                break
            if key == "p":
                print("Got p after {} loops".format(n))

if __name__ == "__main__":
    # Queue setup and 2 threads.
    q = queue.Queue()
    x = threading.Thread(target=watcher, args=(q,))
    x.start()
    y = threading.Thread(target=runner, args=(q,))
    y.start()

Output afrer running:

python3 ../../test.py
Got p after 1055953 loops
                         Got exit event after 4369605 loops
How about nope
  • 752
  • 4
  • 13
  • 1
    this code is really impressive. terminedia's development is paused for a while, as I am the sole contributor - but if you want to take a look there, and contribute with some ideas or code, you'd be mostly welcome. – jsbueno Sep 15 '22 at 15:25
0

As stated on "How about nope"'s answer, that is not straightforward. One can either go into the low level of the input system and change some settings to allow for non-blocking keyboard reading, or makeuse of a thirdy party library that will do that and provide some friendly interface.

terminedia is one such 3rdy party project - among other niceties, it implements the inkey() call which is similar to old-time BASIC function with the same name: it returns the key currently pressed, if there is one, or an empty string.

You just have to call it inside a context block using the terminedia keyboard: with terminedia.keyboard:

So, you have to first install terminedia in your Python environment with pip install terminedia. Then your code could be like this:

import terminedia as TM

n = 1
with TM.keyboard:
    while True:
        # do something 
        n += 1
        if (pressed:=TM.inkey()) == "p":
            # do task 1
        if pressed == "f":
            # exit while
            break

Another advantage over writing the code to set stdin settings is that terminedia keyboard input is multiplatform and will work in windows (though much of the other functionalities in the lib will be Unix only)

(disclaimer: I am the project author)

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Thanks it is work as I want still has one problem. If you send command (that take time) to shell it is not responsive enough, but work with multiple time key press. Terminal not show text in correct position that I think something not related to terminedia – JACK Sep 15 '22 at 17:33
  • Actually, for the keyboard check to work, the terminal have to be reconfigured, and that might interefere with other uses of the terminal. One workaround is to use the `with TM.keyboard:` block only for the line that call `inkey()`, the endof the with block will restore the terminal properties. – jsbueno Sep 15 '22 at 21:39
  • Or, if you are in Unix (Linux/MacOS) you can use the other capabilities of terminedia to create a nice output as well, with color, positioned text, input fields, etc... – jsbueno Sep 15 '22 at 21:40