-2

I've been using a class like this. It has been working well for me so far, but since I am polling input from an outside source, my program is not taking in every key. Here is a rough example of the code and its output.

while True:
    ch = getch()
    print "Queue: " + ch
    # Do something #

With the output being

Queue: 0
Queue: 01
Queue: 012
3Queue: 012
Queue: 0124

I know there may be a couple of solutions, mainly those that require to make the code more efficient under the #Do Something# part. However, I've narrowed it down to the point where the space between any two getch() calls is minimized the best it can be. Any suggestions?

Community
  • 1
  • 1
user2125538
  • 147
  • 2
  • 4
  • 15

1 Answers1

1

You've deliberately chosen a getch implementation that avoids queueing and only grabs the latest character.

You can service input faster by moving the # Do Something # part to a background thread, or use an event-loop-like implementation where you queue up all available characters before trying to process the first one off the queue, etc. But none of that will guarantee you get all characters.

If you want to allow characters to queue up to make sure you don't miss any, just… don't use an implementation that's designed not to let them queue up.


If you don't need getch to timeout after 2 seconds, you can just do this by just not calling select. That also means you don't need TCSADRAIN. Also, instead of calling setraw, try turning off just the flags you care about. Disabling ICANON is enough to make it read character by character.

Here's a complete test:

import select
import sys
import termios
import time
import tty

def getch():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    new_settings = old_settings[:]
    new_settings[3] ~= ~termios.ICANON
    try:
        termios.tcsetattr(fd, termios.TCSANOW, new_settings)
        ch=sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSANOW, old_settings)
    return ch

while True:
    ch = getch()
    if ch == '\x03':
        break
    print "Queue: " + ch
    time.sleep(1)

And here's the output:

aQueue: a
bcdefgQueue: b
Queue: c
Queue: d
Queue: e
Queue: f
Queue: g
^CTraceback (most recent call last):
  File "getch.py", line 24, in <module>
    ch = getch()
  File "getch.py", line 16, in getch
    ch=sys.stdin.read(1)
KeyboardInterrupt

If you do want a timeout, but don't want to throw away extra characters, that's easy; just use termios to set it, instead of making an end-run around it:

new_settings[-1][termios.VTIME] = 20
new_settings[-1][termios.VMIN] = 0

Now the output looks like this:

aQueue: a
bcdQueue: b
Queue: c
Queue: d
Queue: 
Queue: 
^CTraceback (most recent call last):
  File "getch.py", line 30, in <module>
    time.sleep(1)
KeyboardInterrupt

There are many more options in termios—if you wanted the no-echo functionality, ^C-swallowing, etc. of the implementation you were using, read the man page.

Also, if you want to see exactly what setraw does, just run this:

import sys, termios, tty

fd = sys.stdin.fileno()
o = termios.tcgetattr(fd)
tty.setraw(fd)
n = termios.tcgetattr(fd)
tcsetattr(fd, termios.TCSANOW, o)

print('iflag : {:10x} {:10x}'.format(o[0], n[0]))
print('oflag : {:10x} {:10x}'.format(o[1], n[1]))
print('cflag : {:10x} {:10x}'.format(o[2], n[2]))
print('lflag : {:10x} {:10x}'.format(o[3], n[3]))
print('ispeed: {:10d} {:10d}'.format(o[4], n[4]))
print('ospeed: {:10d} {:10d}'.format(o[5], n[5]))
print('cc    :')
for i, (oo, nn) in enumerate(zip(o[6], n[6])):
    print('  {:4d}: {:>10} {:>10}'.format(i, oo, nn))

Then you can look at the termios Python module, manpage, and C header (/usr/include/sys/termios.h) to see what each of the different bit mean in each field.

abarnert
  • 354,177
  • 51
  • 601
  • 671