blessings icon indicating copy to clipboard operation
blessings copied to clipboard

Provide keyboard input

Open erikrose opened this issue 12 years ago • 29 comments

It's really freaking hard to read the state of the keyboard without calling curses.initscr().getch(). Let's make it easier on people; I've had 2 requests about it. Here's one implementation, from http://love-python.blogspot.com/2010/03/getch-in-python-get-single-character.html:

import sys    
import termios
import fcntl

def myGetch():
    fd = sys.stdin.fileno()

    oldterm = termios.tcgetattr(fd)
    newattr = termios.tcgetattr(fd)
    newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
    termios.tcsetattr(fd, termios.TCSANOW, newattr)

    oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)

    try:        
        while 1:            
            try:
                c = sys.stdin.read(1)
                break
            except IOError: pass
    finally:
        termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
        fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)

As you can deduce from the above, you can do it with echo or without, async or blocking (see curses docs for that latter bit).

erikrose avatar Aug 02 '12 16:08 erikrose

Probably model it as a context manager, so we're sure to restore the terminal flags.

erikrose avatar Aug 02 '12 16:08 erikrose

Conway would even be a good place to demo it. Have a keystroke seed stable patterns or something. Or make it fully interactive, a la After Dark. ;-)

erikrose avatar Aug 02 '12 16:08 erikrose

I've been using getch from pager with blessings, might want to have a look at that code.

https://bitbucket.org/techtonik/python-pager/src

ghost avatar Aug 25 '12 04:08 ghost

I added a generator that given an input bytestring yields special curses KEY_LEFT & etc constants, or the bytestring/unicode as-is. https://github.com/jquast/blessings

jquast avatar Sep 09 '12 00:09 jquast

I tried to get both the original snippet and pager code working on os x. Os x is apparently unable to recognize when a read call should block (to read the first byte, for instance) and consequently getch() always returns immediately, even if no data is available in stdin.

Is there not a way to provide a wrapper to curses.initscr().getch()? That seems to work fine on os x. Is there no way to avoid having it hijack the terminal?

Adding this feature gets my vote, by the way.

davesque avatar Nov 29 '12 06:11 davesque

I don't like using curses.initscr().getch() at all. Hate it, in fact.

I wanted some curses features in blessings that required calling initscr() first, but calling initscr() has nasty consequences on the type of environments blessings can run in. I especially like the idea of force_styling=True ... I've been able to emulate other kinds of terminals and see their sequences, I'd also like to translate terminal sequence not of my own process... I'm currently decoding keycodes for telnet clients, for instance.

Placing the terminal in cooked isn't too bad.. I can help fix that.

Erik: if you could propose the interface, the names of the methods and their behaviors, I think I can propose a patch.

I can help with decoding multibyte sequences to numeric constants such as KEY_LEFT, which isn't mentioned here but I think the real issue, right? I've got code working for that. I propose an enable_keycodes=True to getch(), true by default? where getch() can return integer values that are compared to term.KEY_LEFT, etc..

Here's my proposal .. any comments welcomed. This is pseudo.

term = blessings.Terminal()
with term.cooked(enable_keycodes=True):
   print "your term is cooked"
   inp = None
   while inp != 'q':
     inp = term.getch()
     if inp in (term.KEY_LEFT, term.KEY_RIGHT, term.KEY_UP, term.KEY_DOWN):
        print "move: %s" % (term.keyname(inp), )
     else:
        print "Pressed: %s" % (inp,)

but hey, if you wanted to read until carriage return, (default / linemode) you'd use sys.stdin.readline()

... which is why i propose this additional method -- a generator which I'm using now and rather like:

for inp in term.trans_input(sys.stdin.readline().strip()):
   if type(inp) is int:
     print "detected multibyte keystroke: %s" % (term.keyname(inp))
   else:
     print "input character: %s" % (inp,))

jquast avatar Nov 29 '12 19:11 jquast

This version supplies trans_input and keyname:

https://github.com/jquast/x84/blob/master/x84/blessings.py

no cooked mode in it, though.

jquast avatar Nov 29 '12 19:11 jquast

Is there an ETA for this?

acertain avatar Mar 02 '13 02:03 acertain

I'm afraid I don't have an ETA, but it's the next thing I'm likely to add.

erikrose avatar Mar 05 '13 00:03 erikrose

This would be great!

sweenzor avatar Apr 08 '13 18:04 sweenzor

I've created a pull request to implement a _resolve_mulitbyte method, which is a generator for iterating over unicode strings that contain multibyte input sequences, such as \x1b[A translated to KEY_UP.

This does not implement the character-at-a-time cooked mode proposed in this issue. I guess you could say its half-way there.. https://github.com/erikrose/blessings/pull/27

jquast avatar Apr 11 '13 05:04 jquast

I've added win32 and posix termios magic bits for character-at-a-time processing in my tree. With any luck we can close this issue in the next few days. I think the interface i've outlined fits in line with the rest of the styling of blessings, that is, should be very easy to use.

with term.cbreak():
  inp = term.inkey(timeout=5.0)
  if inp is None:
    print 'timeout'
  elif inp.is_sequence:
    print 'This is a detected input sequence: %r' % (inp,)
    if inp.code == term.KEY_HOME:
      print 'no place like it!'
    else:
      print 'Pressed', repr(inp)
  else:
    print "%s? doesn't impress me." % (inp,)

edit: codefix per ms

jquast avatar Apr 12 '13 00:04 jquast

sounds awesome! I'll give it a spin right now!

sweenzor avatar Apr 12 '13 04:04 sweenzor

I think in the code above, do you mean: if inp.code == term.KEY_HOME:?

sweenzor avatar Apr 12 '13 05:04 sweenzor

I'll go learn what cooked mode is and take a look at your PR. :-) Thanks for your hard work!

erikrose avatar Apr 12 '13 05:04 erikrose

I've completed the implementation in the attached pull request (which also includes sequence formatting) with a human test case so far, test_keyboard.py.

I'll add automated tests for the parsers.

jquast avatar Apr 12 '13 21:04 jquast

This is too fun, I suppose we'll all be making games soon!

http://ascii.io/a/2859

jquast avatar Apr 13 '13 05:04 jquast

What a hoot! :-D Ended up spending tonight getting Parsimonious running on Python 3 again, but I'll catch up with this soon.

erikrose avatar Apr 13 '13 06:04 erikrose

Hey, so great to see this feature finally getting added. And I love the demo! Thanks, jquast! I think I'll need to re-visit my project for which I had hoped to use blessings.

davesque avatar Apr 14 '13 03:04 davesque

I found this gem on the python-tulip ML for windows keyboard input, "Example of non-blocking asyncronous console input using Windows API calls in Python."

https://bitbucket.org/techtonik/async-console-input/src/91faf1cbad38e239ea701d990d21dbab6adcb23d/asyncinput.py?at=default

jquast avatar May 17 '13 19:05 jquast

I haven't forgotten about this. I'm just buying a house at the moment and so am a little distracted. :-)

erikrose avatar May 17 '13 22:05 erikrose

@erikrose: do you have an update on this?

medecau avatar Oct 08 '13 11:10 medecau

I think Erik got his house. Now i'm the busy one, looking for a place myself. I've been piecemealing 1 large pull request as individual pulls, working towards keyboard again.

Currently stuck on why the Travis CI system is failing, I don't think Travis offers pseudo terminals. It's hard because the only way to fix it is to try random things, push it into a branch, and see what Travis has to say about it -- rather annoying since all of these tests pass fine locally.

jquast avatar Oct 08 '13 16:10 jquast

From #travis:

henrikhodne: We are running the build script in a pty, so one shoulde be available.

medecau avatar Oct 08 '13 19:10 medecau

I'll take another look soon... its not that travis runs in a pty, but that another pty can be spawned using pty.fork and behave the way I expect it to. I'm getting an I/O error when reading from the master_fd.

This is regarding pull #42 where you can find it failed, in blessings/tests.py decorator @as_subprocess I tried re-ordering some things with another branch but give up for the time being.

jquast avatar Oct 08 '13 19:10 jquast

I've resolved the pty issue, pull #42 is fully resolved.

Once Erik reviews & merges this I'll be able to build upon the fixes and improved test infrastructure and submit a request for my 'sequence-aware' branch, providing term.center, rjust, ljust and wrap() helpers.

The one following will be for keyboard support, I promise.

jquast avatar Oct 09 '13 16:10 jquast

keyboard support is implemented in my fork, https://github.com/jquast/blessed/ -- appreciate any feedback before it goes to pypi.

jquast avatar Mar 16 '14 12:03 jquast

This is resolved in 'blessed-integration' branch.

jquast avatar Apr 08 '15 00:04 jquast

The fork blessed contains full keyboard (even with windows!), with plenty of fun examples and successful use in downstream 3rd party apps, please enjoy.

https://blessed.readthedocs.io/en/latest/keyboard.html

jquast avatar Feb 02 '20 04:02 jquast