Why doesn't Python include non-blocking keyboard input function?
Terry Reedy
tjreedy at udel.edu
Fri Oct 28 08:12:00 EDT 2016
On 10/28/2016 1:05 AM, Michael Torrie wrote:
> Sure you can't get a keyboard scancode when you're in terminal.
As you note in your followup, Marko and Bart want to be able to respond,
for instance, to left and right shift, separately and differently.
Ascii terminals only send and receive ascii characters, including
control 'characters'. Enhanced ('smart') terminals also receive and, I
presume, send escape sequences representing editing actions Last I knew
a couple of decades ago, there were no ansi code sequences for shift key
presses. So responding to these requires that something in the process
have access to them.
> But you can get "keystrokes" as it were, without having to read an
entire line
> from standard in. ...
> Is this not what BartC is talking about? A way of reading in
> "keystrokes" in a terminal.
The only specification he has given is reference to the BASIC INKEY$
variable. I don't know how consistent this was across different BASICs.
I looked in Microsoft's GW-BASIC reference and it says that it returns
'', 'x', or '0x'. This latter represents an extended code "described in
Appendix C'. However, Appendix C only lists the standard ASCII codes
000 to 128. So I do not know what else was available and would not know
from this book how to emulate GW-BASIC INKEY$.
With tk, everything is available that is standard on any of the major
systems. A key event includes widget name, key name (a single char for
char keys, a capitalized name otherwise), keycode, and x,y pixel
position of the mouse relative to the widget. A tkinter inkey()
function might work as follows:
#------------------------------------------------------------
"""tk_inkey.py, 2016 Oct 26
(C) Terry Jan Reedy
Emulate BASIC INKEY$ function/variable as Python inkey() function.
"""
# Create inkey function
from collections import deque
import tkinter as tk
root = tk.Tk()
def setup_inkey(widget):
# Key values must be stored so inkey can access them.
# If inkey were always called faster than a person can type,
# a single nonlocal would suffice. However, since update()
# processes all pending keys, a queue is needed.
q = deque()
def storekeyname(event):
q.append(event.keysym) # or other key description
return 'break' # swallow the event
widget.bind('<Key>', storekeyname)
def inkey():
widget.update() # process all pending keys
return q.popleft() if q else ''
return inkey
inkey = setup_inkey(root)
# Test inkey in simulated use.
import time
display = tk.Label(root, text='No Key', width=30)
display.pack()
root.update()
while True:
time.sleep(.3) # 'do some calculation'
try:
c = inkey()
except tk.TclError:
break
if c:
display['text'] = c
#---------------------------------------------------------------
This runs *without* blocking root.mainloop(), but rather with
root.update() within inkey(). So it can be used within normal code. As
I said before, this could be coupled with a tk Text that emulates a
console, with custom print and input functions, so it would look like
and work like a console application that responds to keypresses.
The use model of INKEY$ is that the programmer must branch to code
blocks or functions according to the value returned. An alternative
'non-blocking' use model is that the programmer binds functions to
particular events and lets the framework do the dispatching. One
difference is when the function is called. A generalized version of the
key handler above could triage key presses into those handled
immediately, those enqueued for later handling, and those ignored. The
decision whether to swallow or pass on events could be different for
different events.
--
Terry Jan Reedy
More information about the Python-list
mailing list