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
CodePudding user response:
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)
CodePudding user response:
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