none stop while loop responsive to user key press - python

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

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)

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

Related

Real-time capture and processing of keypresses (e.g. keypress event)

Note: I want to do this without using any external packages, like PyGame, etc.
I am attempting to capture individual keypresses as they arrive and perform an action for specific characters, whether I simply want to "re-echo" the character, or not display it at all and do something else.
I have found a cross-platform (though not sure about OS X) getch() implementation because I do not want to read a whole line like input() does:
# http://code.activestate.com/recipes/134892/
def getch():
try:
import termios
except ImportError:
# Non-POSIX. Return msvcrt's (Windows') getch.
import msvcrt
return msvcrt.getch
# POSIX system. Create and return a getch that manipulates the tty.
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
[Attempt 1]
I first tried a simple while-true loop to poll getch, but if I type too fast, characters go missing. Reducing the sleep time makes it worse. The debug statements only print on press of the enter key and not consistently in time nor position. (It appears there might be some line buffering going on?) Taking out the loop and sleep lets it work once but perfectly.
#!/usr/bin/env python3
import sys
import tty
import time
def main():
while True:
time.sleep(1)
sys.stdout.write(" DEBUG:Before ")
sys.stdout.write(getch())
sys.stdout.write(" DEBUG:After ")
if __name__ == "__main__":
main()
[Attempt 2]
I got an example for using a threaded approach (https://stackoverflow.com/a/14043979/2752206) but it "locks up" and won't accept any input (including Ctrl-C, and etc)..
#!/usr/bin/env python3
import sys
import tty
import time
import threading
key = 'Z'
def main():
threading.Thread(target=getchThread).start()
while True:
time.sleep(1)
sys.stdout.write(" DEBUG:Before ")
sys.stdout.write(key)
sys.stdout.write(" DEBUG:After ")
def getchThread():
global key
lock = threading.Lock()
while True:
with lock:
key = getch()
if __name__ == "__main__":
main()
Does anyone have any advice or guidance? Or more importantly, can someone explain why the two attempts do not work? Thanks.
First off, I don't really thing you need multithreading. You'd need that if you, for example, wanted to do some tasks like drawing on screen or whatever and capturing keys while you do this.
Let's consider a case where you only want to capture keys and after each keypress execute some action: Exit, if x was pressed, otherwise just print the character. All you need for this case is simple while loop
def process(key):
if key == 'x':
exit('exitting')
else:
print(key, end="", flush=True)
if __name__ == "__main__":
while True:
key = getch()
process(key)
Notice absence of sleep(). I am assuming you thought getch() won't wait for user input so you set 1s sleep time. However, your getch() waits for one entry and then returns it. In this case, global variable is not really useful, so you might as well just call process(getch()) inside the loop.
print(key, end="", flush=True) => the extra arguments will ensure pressed keys stay on one line, not appending newline character every time you print something.
The other case, where you'd want to execute different stuff simultaneously, should use threading.
Consider this code:
n = 0
quit = False
def process(key):
if key == 'x':
global quit
quit = True
exit('exitting')
elif key == 'n':
global n
print(n)
else:
print(key, end="", flush=True)
def key_capturing():
while True:
process(getch())
if __name__ == "__main__":
threading.Thread(target=key_capturing).start()
while not quit:
n += 1
time.sleep(0.1)
This will create global variable n and increment it 10 times a second in main thread. Simultaneously, key_capturing method listens to keys pressed and does the same thing as in previous example + when you press n on your keyboard, current value of the global variable n will be printed.
Closing note: as #zondo noted, you really missed braces in the getch() definition. return msvcrt.getch should most likely be return msvcrt.getch()

Run a logging filter in a separate thread - Python

I have a logging filter that checks for an environment variable to change and I want it to run (in the background) in a thread separate from the process that is setting the environment variable.
What I'm trying to do: every time logging.ERROR is called in my code, the user is alerted to the error and prompted on whether or not they want to continue. Separately the filter and the prompt work correctly however, when I put them together I have a problem. I need to have the filter running in the background so the code to prompt the user can run simultaneously (right now, the filter executes first and the prompt shows up after the while loop in the filter times out, at which point it is useless).
My filter code:
class ErrorFilter(logging.Filter):
def __init__(self,level):
self.level = level
thread = threading.Thread(target=self.filter,args=())
thread.daemon = True
thread.start()
def filter(self,record):
if record.levelno == self.level:
os.environ["ERROR_FLAG"] = "True"
timeout = time.time() + 60*1 #set the timeout to 1 minute
while True:
print "waiting..."
keep_going = os.environ.get("CONTINUE_FLAG")
#wait for user to respond
if keep_going == "False" or time.time() > timeout:
print "oops there's a problem, quitting."
break
if keep_going == "True":
print "Continuing"
break
os.environ["CONTINUE_FLAG"] = "None"
I have another short method that "listens" for ERROR_FLAG and then asks for input using:
def continueAsk(message, title="Warning! Continue?", yn=("Yes","No")):
yes = set(['yes','y', 'ye', '', 'canhaz'])
no = set(['no','n', 'lolzno'])
tryLimit = 0
while tryLimit < 100:
sys.stdout.write(message + ": ")
choice = raw_input().lower()
if choice in yes:
return True
elif choice in no:
return False
else:
tryLimit+=1
sys.stdout.write("Please respond with 'yes' or 'no'.")
EDIT
I've also tried using multiprocessing in my filter like this:
from multiprocessing import Process, Queue
def __init__(self,level):
self.level = level
queue = Queue()
p = Process(target=self.filter,args=("hi"))
p.start()
p.join()
I've tried setting up my filter so it runs in a different thread, but I've not had any luck so far (the filter still runs first, followed by the prompt) and I've never used multithreading before. I know this is not a traditional use of the logger, but I appreciate any input on this.
Looking at the subprocess and multiprocess documentation, I think one of those might work as well but am not sure.

Retrieving intermediate values for raw_input [duplicate]

How can I poll the keyboard from a console python app? Specifically, I would like to do something akin to this in the midst of a lot of other I/O activities (socket selects, serial port access, etc.):
while True:
# doing amazing pythonic embedded stuff
# ...
# periodically do a non-blocking check to see if
# we are being told to do something else
x = keyboard.read(1000, timeout = 0)
if len(x):
# ok, some key got pressed
# do something
What is the correct pythonic way to do this on Windows? Also, portability to Linux wouldn't be bad, though it's not required.
The standard approach is to use the select module.
However, this doesn't work on Windows. For that, you can use the msvcrt module's keyboard polling.
Often, this is done with multiple threads -- one per device being "watched" plus the background processes that might need to be interrupted by the device.
A solution using the curses module. Printing a numeric value corresponding to each key pressed:
import curses
def main(stdscr):
# do not wait for input when calling getch
stdscr.nodelay(1)
while True:
# get keyboard input, returns -1 if none available
c = stdscr.getch()
if c != -1:
# print numeric value
stdscr.addstr(str(c) + ' ')
stdscr.refresh()
# return curser to start position
stdscr.move(0, 0)
if __name__ == '__main__':
curses.wrapper(main)
Ok, since my attempt to post my solution in a comment failed, here's what I was trying to say. I could do exactly what I wanted from native Python (on Windows, not anywhere else though) with the following code:
import msvcrt
def kbfunc():
x = msvcrt.kbhit()
if x:
ret = ord(msvcrt.getch())
else:
ret = 0
return ret
None of these answers worked well for me. This package, pynput, does exactly what I need.
https://pypi.python.org/pypi/pynput
from pynput.keyboard import Key, Listener
def on_press(key):
print('{0} pressed'.format(
key))
def on_release(key):
print('{0} release'.format(
key))
if key == Key.esc:
# Stop listener
return False
# Collect events until released
with Listener(
on_press=on_press,
on_release=on_release) as listener:
listener.join()
import sys
import select
def heardEnter():
i,o,e = select.select([sys.stdin],[],[],0.0001)
for s in i:
if s == sys.stdin:
input = sys.stdin.readline()
return True
return False
From the comments:
import msvcrt # built-in module
def kbfunc():
return ord(msvcrt.getch()) if msvcrt.kbhit() else 0
Thanks for the help. I ended up writing a C DLL called PyKeyboardAccess.dll and accessing the crt conio functions, exporting this routine:
#include <conio.h>
int kb_inkey () {
int rc;
int key;
key = _kbhit();
if (key == 0) {
rc = 0;
} else {
rc = _getch();
}
return rc;
}
And I access it in python using the ctypes module (built into python 2.5):
import ctypes
import time
# first, load the DLL
try:
kblib = ctypes.CDLL("PyKeyboardAccess.dll")
except:
raise ("Error Loading PyKeyboardAccess.dll")
# now, find our function
try:
kbfunc = kblib.kb_inkey
except:
raise ("Could not find the kb_inkey function in the dll!")
# Ok, now let's demo the capability
while True:
x = kbfunc()
if x != 0:
print "Got key: %d" % x
else:
time.sleep(.01)
I've come across a cross-platform implementation of kbhit at http://home.wlu.edu/~levys/software/kbhit.py (made edits to remove irrelevant code):
import os
if os.name == 'nt':
import msvcrt
else:
import sys, select
def kbhit():
''' Returns True if a keypress is waiting to be read in stdin, False otherwise.
'''
if os.name == 'nt':
return msvcrt.kbhit()
else:
dr,dw,de = select.select([sys.stdin], [], [], 0)
return dr != []
Make sure to read() the waiting character(s) -- the function will keep returning True until you do!
You might look at how pygame handles this to steal some ideas.
I am using this for checking for key presses, can't get much simpler:
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
import curses, time
def main(stdscr):
"""checking for keypress"""
stdscr.nodelay(True) # do not wait for input when calling getch
return stdscr.getch()
while True:
print("key:", curses.wrapper(main)) # prints: 'key: 97' for 'a' pressed
# '-1' on no presses
time.sleep(1)
While curses is not working on windows, there is a 'unicurses' version, supposedly working on Linux, Windows, Mac but I could not get this to work
One more option would be to use sshkeyboard library to enable reacting to key presses instead of polling them periodically, and potentially missing the key press:
from sshkeyboard import listen_keyboard, stop_listening
def press(key):
print(f"'{key}' pressed")
if key == "z":
stop_listening()
listen_keyboard(on_press=press)
Simply pip install sshkeyboard to use it.
This can be done using 'pynput' module in python,
You press a key and it gets printed It's that easy!
PIP Install the module in command prompt, write following text and press enter
pip install pynput
Run the following code:
from pynput.keyboard import Key, Listener
def pressed(key):
print('Pressed:',key)
def released(key):
print('Released:',key)
if key == Key.enter:
# Stop detecting when enter key is pressed
return False
# Below loop for Detcting keys runs until enter key is pressed
with Listener(on_press=pressed, on_release=released) as detector:
detector.join()
You can end the loop with any key you want by changing Key.enter to some other key in the 8th line of the code.
If you combine time.sleep, threading.Thread, and sys.stdin.read you can easily wait for a specified amount of time for input and then continue,
also this should be cross-platform compatible.
t = threading.Thread(target=sys.stdin.read(1) args=(1,))
t.start()
time.sleep(5)
t.join()
You could also place this into a function like so
def timed_getch(self, bytes=1, timeout=1):
t = threading.Thread(target=sys.stdin.read, args=(bytes,))
t.start()
time.sleep(timeout)
t.join()
del t
Although this will not return anything so instead you should use the multiprocessing pool module you can find that here: how to get the return value from a thread in python?

Python event loop -- multithreading -- How to run two bits of code simultaneously?

So I'm trying to utilize msvcrt.getch() to make an option to quit(without using KeyBoardInterrupt) anywhere in the program.
My code currently looks like this:
import msvcrt
import sys
print("Press q at any time to quit")
while True:
pressedKey = msvcrt.getch()
if pressedKey == 'q':
sys.exit()
else:
# do some setup
if myvar == "string":
try:
# do stuff
except:
# do stuff
else:
#do stuff
How do I run the while loop to detect the keypress of q at the same time as I'm running the other (the # do stuff blocks)?
That way, if the user goes ahead with the program, they it'll only run it once. But if they hit q, then the program will quit.
You could read keys in a separate thread or (better) use msvcrt.kbhit() as #martineau suggested:
#!/usr/bin/env python
import msvcrt
from Queue import Empty, Queue
from threading import Thread
def read_keys(queue):
for key in iter(msvcrt.getch, 'q'): # until `q`
queue.put(key)
queue.put(None) # signal the end
q = Queue()
t = Thread(target=read_keys, args=[q])
t.daemon = True # die if the program exits
t.start()
while True:
try:
key = q.get_nowait() # doesn't block
except Empty:
key = Empty
else:
if key is None: # end
break
# do stuff
If I wanted to do something in the main code when the second thread detected a certain keypress, how would I act on that?
You do not react to the key press in the main thread until code reaches q.get_nowait() again i.e., you won't notice the key press until "do stuff" finishes the current iteration of the loop. If you need to do something that may take a long time then you might need to run it in yet another thread (start new thread or use a thread pool if blocking at some point is acceptable).

Python in Linux: Put user input asynchronously into queue

I am trying to run a program that takes in input as a job is getting done. I have looked through several forms, and looked into the documentation. I'm running this in Debian, and I understand that I can use this getch function to receive characters without hitting the return key. To break it down, this is what I am trying to implement in my infinite while loop
Take in input (threading didn't work here for me
Put input into Queue
If there are no running jobs, start the job with the item in front of the queue as a variable
I am also running the threading module to execute another instruction. Is there any way I can do this?
Update: This is what I have tried so far:
First, I tried using a timer from the threading module to stop it from waiting, which went something like this.
def getchnow():
def time_up():
answer= None
print 'time up...'
wait = Timer(5,time_up) # x is amount of time in seconds
wait.start()
try:
print "enter answer below"
answer = getch()
except Exception:
print 'pass\n'
answer = None
if answer != True: # it means if variable have somthing
wait.cancel() # time_up will not execute(so, no skip)
return answer
line = getchnow()
#Add line variable to queue
#Do stuff with queue
The problem here is that it still waited for user input.
I then tried to put the getch function into another thread.
q = Queue.Queue
q.put(getch())
if q.get() != True: # it means if variable have somthing
line = q.get()
#Add line variable to queue
#Do stuff with queue
This attempt doesn't let me do anything.
I read more of this link, and there was an implementation of what I wanted at the bottom.
I used the select module for a Non-Blocking implementation on Linux.
This times out in (5 seconds here) if no input is received.
Particularly useful when used in a thread, so that the getch call is
non-blocking and will allow the thread to exit cleanly
# This class gets a single character input from the keyboard
class _GetchUnix:
def __init__(self):
import tty, sys
from select import select
def __call__(self):
import sys, tty, termios
from select import select
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
[i, o, e] = select([sys.stdin.fileno()], [], [], 2)
if i:
ch=sys.stdin.read(1)
else:
ch=''
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
getch = _GetchUnix()
# End Class
I've also used [i, o, e] = select([sys.stdin.fileno()], [], [], 2), but I've heard it might not work on windows. If anyone still needs a multi-threaded, non-blocking input example:
import threading
import sys
import time
bufferLock=threading.Lock()
inputBuffer=[]
class InputThread(threading.Thread):
def run(self):
global inputBuffer
print("starting input")
while True:
line=sys.stdin.readline()
bufferLock.acquire()
inputBuffer.insert(0,line)
bufferLock.release()
input_thread=InputThread()
input_thread.start()
while True:
time.sleep(4)
bufferLock.acquire()
if len(inputBuffer)>0:
print("Popping: "+inputBuffer.pop())
bufferLock.release()

Categories

Resources