Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide keyboard input #22

Open
erikrose opened this issue Aug 2, 2012 · 29 comments
Open

Provide keyboard input #22

erikrose opened this issue Aug 2, 2012 · 29 comments

Comments

@erikrose
Copy link
Owner

erikrose commented Aug 2, 2012

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
Copy link
Owner Author

erikrose commented Aug 2, 2012

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

@erikrose
Copy link
Owner Author

erikrose commented Aug 2, 2012

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. ;-)

@ghost
Copy link

ghost commented Aug 25, 2012

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

@jquast
Copy link
Collaborator

jquast commented Sep 9, 2012

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

@davesque
Copy link

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.

@jquast
Copy link
Collaborator

jquast commented Nov 29, 2012

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
Copy link
Collaborator

jquast commented Nov 29, 2012

This version supplies trans_input and keyname:

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

no cooked mode in it, though.

@acertain
Copy link

acertain commented Mar 2, 2013

Is there an ETA for this?

@erikrose
Copy link
Owner Author

erikrose commented Mar 5, 2013

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

@sweenzor
Copy link

sweenzor commented Apr 8, 2013

This would be great!

@jquast
Copy link
Collaborator

jquast commented Apr 11, 2013

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.. #27

@jquast
Copy link
Collaborator

jquast commented Apr 12, 2013

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

@sweenzor
Copy link

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

@sweenzor
Copy link

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

@erikrose
Copy link
Owner Author

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

@jquast
Copy link
Collaborator

jquast commented Apr 12, 2013

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
Copy link
Collaborator

jquast commented Apr 13, 2013

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

http://ascii.io/a/2859

@erikrose
Copy link
Owner Author

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

@davesque
Copy link

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.

@jquast
Copy link
Collaborator

jquast commented May 17, 2013

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

@erikrose
Copy link
Owner Author

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

@medecau
Copy link

medecau commented Oct 8, 2013

@erikrose: do you have an update on this?

@jquast
Copy link
Collaborator

jquast commented Oct 8, 2013

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.

@medecau
Copy link

medecau commented Oct 8, 2013

From #travis:

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

@jquast
Copy link
Collaborator

jquast commented Oct 8, 2013

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
Copy link
Collaborator

jquast commented Oct 9, 2013

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
Copy link
Collaborator

jquast commented Mar 16, 2014

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

@jquast
Copy link
Collaborator

jquast commented Apr 8, 2015

This is resolved in 'blessed-integration' branch.

@jquast
Copy link
Collaborator

jquast commented Feb 2, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants