How to read a single key input with a timeout in python3? - python

This is my attempt:
from inputimeout import inputimeout, TimeoutOccurred
import time
import sys
import os
def get_choice(maxtimeout=9):
try:
for remaining in range(maxtimeout, 0, -1):
sys.stdout.write("\r")
sys.stdout.write(
"Want to continue to next item(Y/N)? Defaults to Y in {:2d} seconds...".format(
remaining + 1
)
)
sys.stdout.flush()
time.sleep(1)
c = inputimeout(prompt="", timeout=1)
except TimeoutOccurred:
c = "Y"
return c
The issues with my approach are:
The inputed key is not visible until the timeout completes.
The user has to press enter after his choice.
The user has to wait till timeout completes irrespective of when he entered the value.

There are two problems to solve here to meet your preferred requirements.
First, here's a simple example of how you can use a signal timer to give the user some amount of time to input an answer:
import signal
def input_timeout(*ignore):
raise TimeoutError
signal.signal(signal.SIGALRM, input_timeout)
signal.setitimer(signal.ITIMER_REAL, 10)
try:
inp = input("enter something: ")
signal.setitimer(signal.ITIMER_REAL, 0)
except TimeoutError:
print("you took too long!")
What this does is set a timer to go off after 10 seconds (you can use fractional time if you want, say, 2.5 seconds). If the user hasn't input anything before the timer goes off, it raises the TimeoutError exception, breaking the input prompt. If the user does enter something, it disables the timer by setting it to zero. If the timer does fire and calls input_timeout(), it passes two arguments (the signal number and the interrupted stack frame) but you can usually ignore them, so I've set those arguments as *ignore.
Here's a question that covers this approach in more detail: Python timeout decorator. The reason I didn't just point you to that is so that you could see the bare essence of the process and decide for yourself how to use it. Plus, well, when I pasted the code and tried to use it, it didn't work. You can decide how to proceed as meets your needs.
The second issue is you mention the problem with your approach is the user having to press enter. Here's a question that covers that topic: raw_input in python without pressing enter. In short, there's not a simple built-in solution for that.

Related

Prevent keystrokes

It is necessary to make sure that during a certain period of time they are not available for pressing a certain key, for example Space. So that during a break, user clicks are not displayed in any way in the input(). Please suggest a module or a complete solution. Example:
import time
time.sleep(5) # So that during this period of time nothing could be written.
enter = input()
welcome to SO!
Your description is very vague, but assuming you want to prevent a specific keystroke from affecting your program for 5 seconds, this might just work:
import keyboard
import time
time_end = time.time() + 5
while time.time() < time_end:
if keyboard.is_pressed('q'):
pass
replace 'q' with the key you don't want to affect your program. Wherever "pass" is is what will happen if q is pressed within those seconds. I think q will still show up in your program when it's pressed, but wont affect any of your other functions unless you have threading. Sorry if this doesn't help, this is the only thing I could think of.

Python: interrupt the builtin input() to set a new prompt (before user input)

I have a function that has a continual loop asking the user for their input using python's builtin input(prompt). I also have a separate thread doing some work, and when a certain condition is met in that thread, the input prompt should change.
So, say at startup the prompt is "Input: " but then in the middle of the thread's work, a condition is met, so the prompt should be switched to "Go Ahead, Type: ". Now if the user doesn't enter anything at that first prompt, but the thread reaches the point where the prompt is switched, then we are still stuck on that first blocking input call that has "Input: " as it's prompt.
# Start the thread that does some work and eventually changes the prompt
myThread.start()
#Start looping to ask for user input, get the prompt from the thread
while True:
userInput = input(myThread.get_prompt())
<do something with input>
I know I can accomplish this with select([stdin],[],[],0.0) to poll stdin before doing a stdin.readline() and then just print the prompt again if 1) we got user input or 2) if the prompt changed.
However, I'd like to find a solution that uses Python's builtin input() function so that I can set tab completion with python's readline module.
I tried playing around with a signal that would basically interrupt input() every couple seconds. With this I would need to make it appear seamless that a new input() call was made without it reprinting the prompt. So something like:
myThread.start()
while True:
userInput = None
signal(SIGALRM, handler)
signal.alarm(3)
try:
userInput = input("\r" + myThread.get_prompt())
except TimeoutError:
pass
finally:
signal.alarm(0)
<do something with input>
def handler(signum, frame):
raise TimeoutError
Which is messy, but then when it times out, and the new input() is called, the current line buffer gets printed but the cursor is at the front of it. So if I type "aaa" then it reprints the "aaa" but the cursor is not at the end of that buffer, it is at the beginning.
Any suggestions?
Update: I can certainly try to play around more with the signal option. It seems like that might be my best option so far. I just can't seem to get the cursor to move to the end of the input buffer if the user has already started typing something. I don't want the user to be aware of the call timing out and being called again.
Ok I figured it out. And it's way better than using the signal alarm to timeout.
In case anyone in the future stumbles across this specific problem, here is essentially what I did:
import threading
import time
import readline
import sys
class MyThread(threading.Thread):
def __init__(self):
super(MyThread, self).__init__()
# Set the initial prompt
self.prompt = "Input: "
def run(self):
time.sleep(6)
# Now we want to change the prompt so that next time input loop asks for prompt it is correct
self.set_prompt("Go ahead, type: ")
# Now overwrite what was there in the prompt
sys.stdout.write("\r" + self.get_prompt())
sys.stdout.flush()
# Get the current line buffer and reprint it, in case some input had started to be entered when the prompt was switched
sys.stdout.write(readline.get_line_buffer())
sys.stdout.flush()
def get_prompt(self):
return self.prompt
def set_prompt(self, new_prompt):
self.prompt = new_prompt
# Create and start thread
myThread = MyThread()
myThread.start()
# Input loop
while True:
userInput = input(myThread.get_prompt())
print("Got: " + userInput)
If you are using tab complete (which I didn't show here), you may notice that if you try to tab complete after the first time that the prompt switches (without accepting any input from the first prompt), then tab complete still thinks we are reading from the first prompt and will reprint that prompt. In that case, you will need to do the following:
readline.set_completion_display_matches_hook(display)
Where display is a function that does something like what our thread had to do to rewrite the correct prompt:
def display(substitution, matches, longest_match_length):
# Print the matches
print('')
buffer = ''
for match in matches:
buffer += match + " "
print(buffer)
# Rewrite the NEW prompt
sys.stdout.write(<prompt>)
sys.stdout.flush()
sys.stdout.write(readline.get_line_buffer())
sys.stdout.flush()

How to add a delay or wait to a print() . (python)

I am currently working on a text-based game and I would like to add a delay in the appearance of a text. The text where I want the delay is the third descendant of an if-else command.
I've tried time.sleep() function but that doesn't seem to work.
if path.lower().strip() == "yes":
print("Then let your journey begin")
else:
print("\nWell,")
print("You don't have much of a choice do you.")
# the text I need to delay ^^^
So what I'm hoping to do is add a few seconds of delay to the entrance of the commented text.
import time
# ...
if path.lower().strip() == "yes":
print("Then let your journey begin")
else:
print("\nWell,")
time.sleep(3) # parameter used here is in seconds
print("You don't have much of a choice do you.")
# the text I need to delay ^^^
time.sleep(secs)
secs - The number of seconds the Python program should pause execution. This argument should be either an int or a float.
Suspend execution of the calling thread for the given number of seconds. The argument may be a floating point number to indicate a more precise sleep time. The actual suspension time may be less than that requested because any caught signal will terminate the sleep() following execution of that signal’s catching routine. Also, the suspension time may be longer than requested by an arbitrary amount because of the scheduling of other activity in the system.
Changed in version 3.5: The function now sleeps at least secs even if the sleep is interrupted by a signal, except if the signal handler raises an exception (see PEP 475 for the rationale).

Python3 prompt user input for a limited time, then enter a default if user doesn't respond

Python3: I'm trying to allow a user a given amount of time to enter a response into an input, but after a given amount of time if they haven't entered anything, I want to abort the input and assign a default value to the variable that is storing the input, or otherwise feed default values into the input statement after the given time period.
I've tried this:
from threading import Timer
timeout = 2
t = Timer(timeout, print, ["\nSorry, time is up"])
t.start()
answer = input("You have 2 seconds to answer:")
t.cancel()
print(answer)
from a different stack overflow post, but the problem is that the interpreter still prompts the user for input even after the final line is executed and answer is printed, and this won't work for what I'm trying to do (essentially, a command line game that needs to keep going when the player isn't giving it input but update when it does receive input).
What is the best way to do this? I know python doesn't really have a timeout function or something like that, but is there any way to achieve this via system commands or a module?
There are several plausible approaches (some of which are probably Unix-specific):
Read the input in a subprocess that can be killed after the timeout. (Many functions, including subprocess.run, accept a timeout parameter to automate this.)
Use alarm or similar to send your own process a signal (and install a signal handler that throws an exception).
Have another thread close the descriptor being read after the timeout; this sounds drastic but is generally said to work, so long as you don’t accidentally open a file on the closed descriptor before restoring it with dup2.
Read the input with lower-level facilities like non-blocking read and select—which will unfortunately disable nice things like readline.
In any case, you have to decide what to do with incomplete but non-empty input entered before the timeout. The terminal driver will likely transfer it invisibly to the next input prompt by default.
Using a select call may be a easier way.
import sys, select
print "You have ten seconds to answer!"
i, o, e = select.select( [sys.stdin], [], [], 10 )
if (i):
print "You said", sys.stdin.readline().strip()
else:
print "You said nothing!"
Refer to Keyboard input with timeout?

Unix Python - Get terminal input without pressing enter after timeout

I'd like to get input on the terminal and give only a few seconds to respond. When the timeout is reached I'd like to read whatever has been typed in an accept that as the given input. Optionally I'd like the user to be able to press "enter" to submit their answer early.
I have the code below which works ok, but requires the user to press "enter" to submit. It has a bug: Entering text and then waiting for the timeout keeps the text in the "buffer". Then when you get prompted again you enter different text, press enter and then both strings are printed (see output). When the timeout is reached I'd like to accept whatever has been typed as "the answer." I'd like the user to still be able to submit an answer faster by pressing "enter".
Is there a way to achieve the desired behavior?
Note: I'm using Mac OS X
import sys
from select import select
def getResponse(timeout):
print "Enter something:"
rlist, wlist, xlist = select([sys.stdin], [], [], timeout)
if rlist:
result = sys.stdin.readline()
return result
else:
return ''
while True:
response = getResponse(3)
print "Your input is:", response
Output:
Enter something:
pythonYour input is:
Enter something:
dangit
Your input is: pythondangit
You want to read user input from terminal in a non-blocking manner and also be able to timeout the reading and then get what has been written without needing to press enter. That is problematic at least, there isn't going to be an easy answer for that. As a starting point, you might consider using the following ncurses code. The user better not need to erase his input! The code can be adjusted to handle character deletion, and many other "fancy" features, in the end you might end with a mini-terminal.
What you are asking for is typically done using event loops, including the reading part (where raw_input is a bad choice). A GUI toolkit could solve that more easily, and more robustly (but of course it has some thousands lines behind it).
import time
import curses
ENTER_KEY = (curses.KEY_ENTER, ord('\n'), ord('\r'))
def run(win, timeout=3): # timeout in seconds
curses.echo()
win.timeout(0) # Non-block read.
line = 0
while True:
win.addstr(line, 0, "Enter something: ")
s = []
start = time.time()
run = True
while run:
c = win.getch()
time_taken = time.time() - start
if c < 0:
pass
elif c in ENTER_KEY:
break
else:
s.append(chr(c))
if time_taken >= timeout:
# Out of time.
s.append(-1)
run = False
if len(s) == 0:
break
if s[-1] == -1:
s.pop()
answer = ''.join(s)
win.addstr(line + 1, 0, "Your input was: %s" % answer)
line += 2
curses.wrapper(run)
To end gracefully press enter without typing anything. If the time runs out, it continues asking for input. I didn't test this code at all, I'm just aware of its many limitations.
This is not normal console behavior, but here is a post that discusses a few options:
Keyboard input with timeout in Python

Categories