• Re: Does one have to use curses to read single characters from keyboard

    From Stefan Ram@21:1/5 to Chris Green on Sun Dec 11 11:12:13 2022
    Chris Green <cl@isbd.net> writes:
    Is the only way to read single characters from the keyboard to use >curses.cbreak() or curses.raw()? If so how do I then read characters,

    It seems that you want to detect keypresses and not read
    characters from a line-buffered console with editing
    features.

    Curses is not portable IIRC. A more portable means would
    be to use tkinter with the "bind" function to bind keys.

    All I actually want to do is get 'Y' or 'N' answers to questions on
    the command line.

    answer = input( 'Format drive C: (Y/N)?' )

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Stefan Ram on Sun Dec 11 11:32:03 2022
    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    Curses is not portable IIRC. A more portable means would
    be to use tkinter with the "bind" function to bind keys.

    import tkinter

    text = tkinter.Text()
    text.pack()
    text.bind\
    ( "<y>",
    lambda event:
    text.insert
    ( tkinter.END, "Y\nFormatting drive C:\n...\n" )or "break" )
    text.insert( tkinter.END, "Format drive C:?\n" )
    text.focus()

    tkinter.mainloop()

    Not allowing users to edit their keypresses before confirming
    them with [Return]. What could possibly go wrong?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From DFS@21:1/5 to Chris Green on Sun Dec 11 07:37:56 2022
    On 12/11/2022 5:09 AM, Chris Green wrote:
    Is the only way to read single characters from the keyboard to use curses.cbreak() or curses.raw()? If so how do I then read characters,
    it's not at all obvious from the curses documentation as that seems to
    think I'm using a GUI in some shape or form.

    All I actually want to do is get 'Y' or 'N' answers to questions on
    the command line.

    Searching for ways to do this produces what seem to me rather clumsy
    ways of doing it.


    resp = 'x'
    while resp.lower() not in 'yn':
    resp = input("Did you say Y or did you say N?: ")

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Green@21:1/5 to Stefan Ram on Sun Dec 11 13:59:27 2022
    Stefan Ram <ram@zedat.fu-berlin.de> wrote:
    Chris Green <cl@isbd.net> writes:
    Is the only way to read single characters from the keyboard to use >curses.cbreak() or curses.raw()? If so how do I then read characters,

    It seems that you want to detect keypresses and not read
    characters from a line-buffered console with editing
    features.

    Curses is not portable IIRC. A more portable means would
    be to use tkinter with the "bind" function to bind keys.

    All I actually want to do is get 'Y' or 'N' answers to questions on
    the command line.

    answer = input( 'Format drive C: (Y/N)?' )

    ... and therein lies the fundamental problem, you have to type Y or N
    followed by Return. See my own follow-up though.

    --
    Chris Green
    ·

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Stefan Ram on Sun Dec 11 15:10:10 2022
    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    import tkinter

    This two-liner allows to answer with just one keypress ([Y]/[N]) here.

    import tkinter.messagebox
    tkinter.messagebox.askyesno( "Question", "Format harddisk?" )

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Nyland@21:1/5 to ram@zedat.fu-berlin.de on Sun Dec 11 12:34:25 2022
    You should try the input function. I use it all the time. It does require
    the user to hit enter but that is pretty typical of that kind of interface.
    So I would write a loop like

    while True:
    answer = input("Please answer the question (y/n):")
    if answer == 'y':
    break

    Chris

    On Sun, Dec 11, 2022 at 11:03 AM Stefan Ram <ram@zedat.fu-berlin.de> wrote:

    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    import tkinter

    This two-liner allows to answer with just one keypress ([Y]/[N]) here.

    import tkinter.messagebox
    tkinter.messagebox.askyesno( "Question", "Format harddisk?" )


    --
    https://mail.python.org/mailman/listinfo/python-list


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Oscar Benjamin@21:1/5 to Chris Green on Sun Dec 11 17:19:53 2022
    On Sun, 11 Dec 2022 at 15:55, Chris Green <cl@isbd.net> wrote:

    Is the only way to read single characters from the keyboard to use curses.cbreak() or curses.raw()? If so how do I then read characters,
    it's not at all obvious from the curses documentation as that seems to
    think I'm using a GUI in some shape or form.

    All I actually want to do is get 'Y' or 'N' answers to questions on
    the command line.

    Searching for ways to do this produces what seem to me rather clumsy
    ways of doing it.

    What you are asking for is known as getch. On Windows Python's msvcrt
    module provides precisely this function. There are ways to do it on
    non-Windows platforms but nothing as simple as the getch function is
    in the stdlib. Some modules and recipes are available which I guess it
    what you've already seen:

    https://pypi.org/project/getch/
    https://github.com/joeyespo/py-getch https://stackoverflow.com/questions/510357/how-to-read-a-single-character-from-the-user

    This seems to be the most well maintained option: https://pypi.org/project/readchar/

    I've often thought that getch was a good candidate for the stdlib.
    There are plenty of recipes around but it would ideally just be
    available as a cross platform function. Using curses would have been
    overkill in any of my use cases where I really just wanted getch to
    make a more efficient interface for a terminal program having some
    limited interactivity. Actually slightly more than getch is readchar's
    readkey which also works for pressing non-character keys. There were
    times in the past where I might have used that if I'd known about it.

    --
    Oscar

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Antoon Pardon@21:1/5 to All on Sun Dec 11 19:00:36 2022
    Op 11/12/2022 om 12:32 schreef Stefan Ram:
    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    Curses is not portable IIRC. A more portable means would
    be to use tkinter with the "bind" function to bind keys.
    import tkinter

    text = tkinter.Text()
    text.pack()
    text.bind\
    ( "<y>",
    lambda event:
    text.insert
    ( tkinter.END, "Y\nFormatting drive C:\n...\n" )or "break" )
    text.insert( tkinter.END, "Format drive C:?\n" )
    text.focus()

    tkinter.mainloop()

    Not allowing users to edit their keypresses before confirming
    them with [Return]. What could possibly go wrong?

    Nothing that can't go wrong otherwise. It is my experience that
    when a [Return] is needed, people just type in a two key combination.
    They don't type one key, then check, then type [Return].

    So in practice the same things go wrong, either way.

    --
    Antoon Pardon.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Green@21:1/5 to All on Sun Dec 11 18:50:51 2022
    My solution in the end was copied from one I found that was much
    simpler and straightforward than most. I meant to post this earlier
    but it got lost somewhere:-

    import sys, termios, tty
    #
    #
    # Read a single character from teminal, specifically for 'Y/N'
    #
    fdInput = sys.stdin.fileno()
    termAttr = termios.tcgetattr(0)
    #
    #
    # Get a single character, setcbreak rather than setraw meands CTRL/C
    etc. still work
    #
    def getch():
    sys.stdout.flush()
    tty.setcbreak(fdInput)
    ch = sys.stdin.buffer.raw.read(1).decode(sys.stdin.encoding)
    termios.tcsetattr(fdInput, termios.TCSAFLUSH, termAttr)
    sys.stdout.write(ch)
    return ch
    #
    #
    # Get a y or n answer, ignore other characters
    #
    def getyn():
    ch = 'x'
    while ch != 'y' and ch != 'n':
    ch = getch().lower()
    return ch

    So getyn() reads a y or an n, ignores anything else and doesn't wait
    for a return key. Keyboard input operation is restored to normal
    after doing this. Using tty.setcbreak() rather than tty.setraw() means
    that CTRL/C etc. still work if things go really wrong.


    --
    Chris Green
    ·

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stefan Ram@21:1/5 to Chris Green on Sun Dec 11 19:10:13 2022
    Chris Green <cl@isbd.net> writes:
    import sys, termios, tty

    There might be some versions of Python and the Microsoft®
    Windows operating system where "termios" is not available.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From dn@21:1/5 to Chris Green on Mon Dec 12 10:07:37 2022
    On 11/12/2022 23.09, Chris Green wrote:
    Is the only way to read single characters from the keyboard to use curses.cbreak() or curses.raw()? If so how do I then read characters,
    it's not at all obvious from the curses documentation as that seems to
    think I'm using a GUI in some shape or form.

    All I actually want to do is get 'Y' or 'N' answers to questions on
    the command line.

    Searching for ways to do this produces what seem to me rather clumsy
    ways of doing it.

    You may like to re-ask this question over on the Python-Tutor list. The ListAdmin over there (literally) wrote the book on Python+curses...


    Did such research include the keyboard module? https://pypi.org/project/keyboard/

    This project includes an (Enter-free) "hot-key" feature which firstly
    detects the specific key or key-combination, and then calls an action
    if/when True.
    (amongst other functionality)

    Quick read tutorial: https://www.thepythoncode.com/article/control-keyboard-python

    Disclaimer: have had it on my 'radar', but never actually employed.
    (if you have considered, will be interested to hear conclusions...)
    --
    Regards,
    =dn

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Green@21:1/5 to Stefan Ram on Sun Dec 11 21:13:19 2022
    Stefan Ram <ram@zedat.fu-berlin.de> wrote:
    Chris Green <cl@isbd.net> writes:
    import sys, termios, tty

    There might be some versions of Python and the Microsoft®
    Windows operating system where "termios" is not available.


    Ah, I did originally say that this was a Unix/Linux only solution but
    that was in my first response that got lost somewhere.

    --
    Chris Green
    ·

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Angelico@21:1/5 to Barry Scott on Mon Dec 12 09:32:49 2022
    On Mon, 12 Dec 2022 at 09:24, Barry Scott <barry@barrys-emacs.org> wrote:
    You would need to have a loop that collected all the utf-8 bytes of a single code point.
    You can to look at the first byte of know if the utf-8 is 1, 2, 3 or 4 bytes for a code point.

    And cope with escape sequences too - if you press an arrow key, for
    instance, you'll get a multi-character string to signal that.

    This is why it's probably easier to let someone else do the work.

    ChrisA

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Cameron Simpson@21:1/5 to Barry Scott on Mon Dec 12 09:54:11 2022
    On 11Dec2022 22:22, Barry Scott <barry@barrys-emacs.org> wrote:
    # Get a single character, setcbreak rather than setraw meands
    CTRL/C
    etc. still work
    #
    def getch():
    sys.stdout.flush()
    tty.setcbreak(fdInput)
    ch = sys.stdin.buffer.raw.read(1).decode(sys.stdin.encoding)

    Will not work for uncode code points above 255.

    Aye. But one could write a little loop to collect bytes until a complete character was received. A little experiment:

    >>> try: bytes((0xf0,)).decode('utf8')
    ... except UnicodeDecodeError as e:
    ... e2=e
    ...
    >>> e2
    UnicodeDecodeError('utf-8', b'\xf0', 0, 1, 'unexpected end of data')
    >>> e2.reason
    'unexpected end of data'

    Keep collecting while you get `UnicodeDecodeError`s with a `.reason` of 'unexpected end of data'. Can be encoding agnostic (obviously you need
    to _choose_ an encoding, it is needn't be utf-8).

    (For the OP: `UnicodeDecodeError` doesn't necessarily mean you're
    decoding Unicode data, you're decoding _into_ a Python string which is a Unicode string.)

    Cheers,
    Cameron Simpson <cs@cskk.id.au>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Barry Scott@21:1/5 to All on Sun Dec 11 22:22:45 2022
    On 11 Dec 2022, at 18:50, Chris Green <cl@isbd.net> wrote:

    My solution in the end was copied from one I found that was much
    simpler and straightforward than most. I meant to post this earlier
    but it got lost somewhere:-

    import sys, termios, tty
    #
    #
    # Read a single character from teminal, specifically for 'Y/N'
    #
    fdInput = sys.stdin.fileno()
    termAttr = termios.tcgetattr(0)
    #
    #
    # Get a single character, setcbreak rather than setraw meands CTRL/C
    etc. still work
    #
    def getch():
    sys.stdout.flush()
    tty.setcbreak(fdInput)
    ch = sys.stdin.buffer.raw.read(1).decode(sys.stdin.encoding)

    Will not work for uncode code points above 255.

    This is what happened when I typed € key:

    a.getch()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "/private/var/folders/ll/08dwwqkx6v9bcd15sh06x14w0000gn/T/a.py", line 15, in getch
    ch = sys.stdin.buffer.raw.read(1).decode(sys.stdin.encoding) UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe2 in position 0: unexpected end of data

    You would need to have a loop that collected all the utf-8 bytes of a single code point.
    You can to look at the first byte of know if the utf-8 is 1, 2, 3 or 4 bytes for a code point.

    Barry

    termios.tcsetattr(fdInput, termios.TCSAFLUSH, termAttr)
    sys.stdout.write(ch)
    return ch
    #
    #
    # Get a y or n answer, ignore other characters
    #
    def getyn():
    ch = 'x'
    while ch != 'y' and ch != 'n':
    ch = getch().lower()
    return ch

    So getyn() reads a y or an n, ignores anything else and doesn't wait
    for a return key. Keyboard input operation is restored to normal
    after doing this. Using tty.setcbreak() rather than tty.setraw() means
    that CTRL/C etc. still work if things go really wrong.



    --
    Chris Green
    ·
    --
    https://mail.python.org/mailman/listinfo/python-list

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Chris Green@21:1/5 to Barry Scott on Mon Dec 12 11:12:12 2022
    Barry Scott <barry@barrys-emacs.org> wrote:


    On 11 Dec 2022, at 18:50, Chris Green <cl@isbd.net> wrote:

    My solution in the end was copied from one I found that was much
    simpler and straightforward than most. I meant to post this earlier
    but it got lost somewhere:-

    import sys, termios, tty
    #
    #
    # Read a single character from teminal, specifically for 'Y/N'
    #
    fdInput = sys.stdin.fileno()
    termAttr = termios.tcgetattr(0)
    #
    #
    # Get a single character, setcbreak rather than setraw meands CTRL/C
    etc. still work
    #
    def getch():
    sys.stdout.flush()
    tty.setcbreak(fdInput)
    ch = sys.stdin.buffer.raw.read(1).decode(sys.stdin.encoding)

    Will not work for uncode code points above 255.

    This is what happened when I typed € key:

    a.getch()
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "/private/var/folders/ll/08dwwqkx6v9bcd15sh06x14w0000gn/T/a.py", line 15, in getch
    ch = sys.stdin.buffer.raw.read(1).decode(sys.stdin.encoding) UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe2 in position 0: unexpected end of data

    Since it's just for me to use the above doesn't worry me! :-) I'd
    have to try quite hard to enter a multi-byte character from my
    keyboard so it's very unlikely to happen by mistake.

    --
    Chris Green
    ·

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Alan Gauld@21:1/5 to All on Mon Dec 12 17:45:21 2022
    On 11/12/2022 21:07, dn wrote:
    On 11/12/2022 23.09, Chris Green wrote:
    Is the only way to read single characters from the keyboard to use
    curses.cbreak() or curses.raw()? If so how do I then read characters,
    it's not at all obvious from the curses documentation as that seems to
    think I'm using a GUI in some shape or form.

    All I actually want to do is get 'Y' or 'N' answers to questions on
    the command line.

    Searching for ways to do this produces what seem to me rather clumsy
    ways of doing it.

    You may like to re-ask this question over on the Python-Tutor list. The ListAdmin over there (literally) wrote the book on Python+curses...


    Did such research include the keyboard module? https://pypi.org/project/keyboard/

    This project includes an (Enter-free) "hot-key" feature which firstly
    detects the specific key or key-combination, and then calls an action
    if/when True.
    (amongst other functionality)

    Quick read tutorial: https://www.thepythoncode.com/article/control-keyboard-python

    Disclaimer: have had it on my 'radar', but never actually employed.
    (if you have considered, will be interested to hear conclusions...)

    --
    Alan G
    Author of the Learn to Program web site
    http://www.alan-g.me.uk/
    http://www.amazon.com/author/alan_gauld
    Follow my photo-blog on Flickr at:
    http://www.flickr.com/photos/alangauldphotos

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Grant Edwards@21:1/5 to Chris Green on Mon Dec 12 10:47:13 2022
    On 2022-12-11, Chris Green <cl@isbd.net> wrote:

    Is the only way to read single characters from the keyboard to use curses.cbreak() or curses.raw()?

    No.

    If so how do I then read characters,

    Use a termios.tcsetattr() to put fd 0 into raw mode and then use
    os.read().

    Recent versions of Python include a "tty" module that has conveniece
    functions to handle that:

    --
    Grant

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Alan Gauld@21:1/5 to All on Tue Dec 13 08:46:48 2022
    On 12/12/2022 17:45, Alan Gauld wrote:

    Absolutely nothing apparently!

    But in practce I did pen some esponses to Davids post. However
    this list seems to strip out what I've written, its happened
    a few times now. Not every time but enough that I rarely post
    here.

    But I'll try once more...

    On 11/12/2022 21:07, dn wrote:
    On 11/12/2022 23.09, Chris Green wrote:
    Is the only way to read single characters from the keyboard to use
    curses.cbreak() or curses.raw()?

    You may like to re-ask this question over on the Python-Tutor list. The
    ListAdmin over there (literally) wrote the book on Python+curses...

    Thanks for the plug David, but...

    While my book is, I think, the only one specifically for curses with
    Python, it's hardly a definitive tome on the subject, rather a
    beginner's tutorial. An expanded HowTo if you like..

    Did such research include the keyboard module?
    https://pypi.org/project/keyboard/

    There are several such modules, including a good one by Fred Lundh.
    They are cross platform and probably the best solution for the
    OP if he doesn't mind using a third party module. I think Fred's
    was called terminal? But its been a while I normally just use curses
    for anything terminal related.

    --
    Alan G
    Author of the Learn to Program web site
    http://www.alan-g.me.uk/
    http://www.amazon.com/author/alan_gauld
    Follow my photo-blog on Flickr at:
    http://www.flickr.com/photos/alangauldphotos

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From moi@21:1/5 to All on Fri Dec 16 02:33:25 2022
    I do not want to be rude, but...

    Can a language be considered usable if you can not code
    this ?

    "Press Control + ö to continue..."

    You read correctly. It is o-Umlaut (U+00F6), not (U+006F).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From moi@21:1/5 to All on Mon Dec 19 00:12:29 2022
    PS C:\humour> # This is why it's probably easier to let someone else do the work.

    PS C:\humour>
    PS C:\humour> # In one sense, this is correct.

    PS C:\humour> write-host "[$£€¥]? " -nonewline; [console]::readkey("noecho").keychar
    [$£€¥]? $
    PS C:\humour> write-host "[$£€¥]? " -nonewline; [console]::readkey("noecho").keychar
    [$£€¥]? £
    PS C:\humour> write-host "[$£€¥]? " -nonewline; [console]::readkey("noecho").keychar
    [$£€¥]? €
    PS C:\humour> write-host "[$£€¥]? " -nonewline; [console]::readkey("noecho").keychar
    [$£€¥]? ¥
    PS C:\humour>
    PS C:\humour> # I entered all these chars by pressing keys on my keyboard.
    PS C:\humour>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From moi@21:1/5 to All on Tue Dec 20 01:49:39 2022
    Keyboard handling and IO streams are two differents things.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)