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