How to read a single keypress?

7,629 views
Skip to first unread message

garsh

unread,
Apr 13, 2011, 4:52:13 AM4/13/11
to golang-nuts
Sorry for the newbie question.
I'm trying to figure out how to read a single keypress.
My initial attempt was:


var buffer [1]byte
num, err := os.Stdin.Read(buffer[:])

But that still waits for the user to press return before responding.

bjarne holen

unread,
Apr 13, 2011, 8:33:04 AM4/13/11
to garsh, golang-nuts
hi

i don't think you can do that by reading from stdin, i might be wrong though :-)

you can do this very easily with ncurses,  and there are golang wrappers for it,
here is a minimal example in C..

<code>
#include <ncurses.h>

// modified halloworld from ncurses tutorial..
// to compile: gcc -o halloinput halloinput.c -lncurses

int main(int argc, char** argv)
{
    char c;

    initscr();
    refresh();
    printw("press a key: ");
    c = getch();
    printw("\nkeycode: %d\n", (int) c);
    printw("press any key to quit..");
    getch();
    endwin();
    return 0;
}

</code>

this wrapper library has included a larger example which should get you going:


not sure what platform you're on, but on *nix i guess you should be ok

bjarneh


--
add a little class to your gmail


Jiang Zemin kibo Comirex Crypto AG afsatcom AMEMB ISEC BCCI USCOI
strategic COSCO basement MD5 spy fissionable
LABLINK Steve Case supercomputer BATF broadside Vince Foster lynch
embassy White Water AMEMB Comirex halcon Chobetsu Dateline sniper
virus BATF genetic import codes BCCI mindwar Steve Case security JPL
Ft. Meade basement Lexis-Nexis defense information warfare NATO
BATF basement domestic disruption Saddam Hussein Capricorn Marxist
wire transfer president e-bomb Kh-11 SDI Rule
Psix Honduras domestic disruption EuroFed MILSATCOM JSOFC3IP kibo
military

David Roundy

unread,
Apr 13, 2011, 9:54:57 PM4/13/11
to garsh, golang-nuts
You need to set the terminal to be uncooked, otherwise the terminal
won't relay the characters to your program until it encounters a
newline. Here is an example of code (not necessarily very good) to
manage the cooked/raw state of using the /bin/stty program:

https://github.com/droundy/iolaus/blob/master/src/util/cook.go

It's a bit ugly, because if you don't set the terminal back to cooked,
the shell will be left in a weird state. It's designed to be used
like:

defer cook.Undo(cook.SetRaw())

so you can be sure that the tty is set back to cooked. If I were to
rewrite this, I'd do it differently, as I wrote this particular code a
long while back (maybe a year?).

David

--
David Roundy

Dave Cheney

unread,
Apr 13, 2011, 10:04:25 PM4/13/11
to David Roundy, garsh, golang-nuts
> defer cook.Undo(cook.SetRaw())

This is a neat trick. I'm guessing the arguments to cook.Undo() are
evaluated when the function is executed, not when the defer is
executed.

Dave

Andrew Gerrand

unread,
Apr 13, 2011, 10:52:59 PM4/13/11
to Dave Cheney, David Roundy, garsh, golang-nuts

I like that trick. If you make Undo a method of the secret type, you
can express it a different way:

package main

import "fmt"

type T int

func Start() T {
fmt.Println("Start")
return T(0)
}

func (t T) End() {
fmt.Println("End")
}

func main() {
defer Start().End()
fmt.Println("During")
}

Andrew

David Roundy

unread,
Apr 14, 2011, 5:15:18 PM4/14/11
to Andrew Gerrand, Dave Cheney, garsh, golang-nuts
On Wed, Apr 13, 2011 at 7:52 PM, Andrew Gerrand <a...@golang.org> wrote:
> On 14 April 2011 12:04, Dave Cheney <da...@cheney.net> wrote:
>>> defer cook.Undo(cook.SetRaw())
>>
>> This is a neat trick. I'm guessing the arguments to cook.Undo() are
>> evaluated when the function is executed, not when the defer is
>> executed.
>
> I like that trick. If you make Undo a method of the secret type, you
> can express it a different way:
...
>        defer Start().End()

That's definitely nicer. If I wrote it again, I'd be tempted to just
return a function so you'd end up with

defer Start()()

but that seems a bit uglier than your suggestion, as the second
parentheses look weird, and you could easily omit them and not notice
that you aren't ending at all.
--
David Roundy

David Roundy

unread,
Apr 14, 2011, 5:16:11 PM4/14/11
to Dave Cheney, garsh, golang-nuts

Yes, that is the behavior of defer, which does make this sort of
trickery possible. :)
--
David Roundy

Chip Camden

unread,
Apr 14, 2011, 6:02:52 PM4/14/11
to golang-nuts
Quoth David Roundy on Thursday, 14 April 2011:

Elegant IMHO, but it deserves a comment:

defer Start()() // Keep your paws off my code unless you grok this

--
.O. | Sterling (Chip) Camden | http://camdensoftware.com
..O | ster...@camdensoftware.com | http://chipsquips.com
OOO | 2048R/D6DBAF91 | http://chipstips.com

Andrew Gerrand

unread,
Apr 15, 2011, 12:14:03 AM4/15/11
to David Roundy, Dave Cheney, garsh, golang-nuts

Yeah, that's heading into dangerous territory. It's a neat trick, but
I wouldn't use it in code I expected others to use. It's much more
readable (and idiomatic) split over two lines:

t := Start()
defer t.End()

Andrew

garsh

unread,
Apr 21, 2011, 4:51:08 AM4/21/11
to golang-nuts
Thanks David! Your code worked great for me.

And thanks to everybody for the discussion. I should have thought
about the terminal mode, but it didn't occur to me.

On Apr 13, 9:54 pm, David Roundy <roun...@physics.oregonstate.edu>
wrote:
> You need to set the terminal to be uncooked, otherwise the terminal
> won't relay the characters to your program until it encounters a
> newline.  Here is an example of code (not necessarily very good) to
> manage the cooked/raw state of using the /bin/stty program:
>
> https://github.com/droundy/iolaus/blob/master/src/util/cook.go
>
> It's a bit ugly, because if you don't set the terminal back to cooked,
> the shell will be left in a weird state.  It's designed to be used
> like:
>
> defer cook.Undo(cook.SetRaw())
>
> so you can be sure that the tty is set back to cooked.  If I were to
> rewrite this, I'd do it differently, as I wrote this particular code a
> long while back (maybe a year?).
>
> David
>

Hotei

unread,
Apr 23, 2013, 11:36:18 PM4/23/13
to golan...@googlegroups.com
I would suggest a look at code.google.com/p/go.crypto/ssh/terminal/.  The MakeRaw function specifically.

Archos

unread,
Apr 24, 2013, 3:21:56 AM4/24/13
to golan...@googlegroups.com, alber...@gmail.com
You can read the comments on my package which comments all stuff very well.
https://github.com/kless/terminal/blob/master/terminal_unix.go

El miércoles, 24 de abril de 2013 00:54:25 UTC+1, Zellyn Hunter escribió:
I cobbled together a (unix-specific, syscall-using) solution based on the stack overflow answer to the same question in python, and a previous post on this list. References inline.

Note: this seems to work, but I haven't taken the time to really understand what I'm doing, and I welcome any suggestions or fixes.


func getTermios() (result syscall.Termios, err error) {
r1, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdin), syscall.TCGETS, uintptr(unsafe.Pointer(&result)))
if errno != 0 {
return result, os.NewSyscallError("SYS_IOCTL", errno)
}
if r1 != 0 {
return result, fmt.Errorf("Error: expected first syscall result to be 0, got %d", r1)
}
return result, nil
}

func setTermios(t syscall.Termios) error {
r1, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdin), syscall.TCSETS, uintptr(unsafe.Pointer(&t)))
if errno != 0 {
return os.NewSyscallError("SYS_IOCTL", errno)
}
if r1 != 0 {
return fmt.Errorf("Error: expected first syscall result to be 0, got %d", r1)
}
return nil
}

func getFileStatusFlags() (int32, error) {
r1, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(syscall.Stdin), syscall.F_GETFL, 0)
if errno != 0 {
return 0, os.NewSyscallError("SYS_FCNTL", errno)
}
r := int32(r1)
if r < 0 {
return 0, fmt.Errorf("Error: expected first syscall result to be >= 0, got %d", r)
}
return r, nil
}

func setFileStatusFlags(f int32) error {
r1, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(syscall.Stdin), syscall.F_SETFL, uintptr(f))
if errno != 0 {
return os.NewSyscallError("SYS_FCNTL", errno)
}
if r1 != 0 {
return fmt.Errorf("Error: expected first syscall result to be 0, got %d", r1)
}
return nil
}


func readSingleKeypress() (byte, error) {
oldFl, err := getFileStatusFlags()
if err != nil {
return 0, err
}
oldTermios, err := getTermios()
if err != nil {
return 0, err
}

defer setFileStatusFlags(oldFl)
defer setTermios(oldTermios)

newFl, newTermios := oldFl, oldTermios

newTermios.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP)
newTermios.Iflag &^= (syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
newTermios.Oflag &^= syscall.OPOST
newTermios.Cflag &^= (syscall.CSIZE | syscall.PARENB)
newTermios.Cflag |= syscall.CS8
newTermios.Lflag &^= (syscall.ECHONL | syscall.ECHO | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
newTermios.Cc[syscall.VMIN] = 1
newTermios.Cc[syscall.VTIME] = 0
if err = setTermios(newTermios); err != nil {
return 0, err
}

newFl &^= syscall.O_NONBLOCK
if err = setFileStatusFlags(newFl); err != nil {
return 0, err
}

keys := []byte{0}
n, err := syscall.Read(syscall.Stdin, keys)
if err != nil {
return 0, err
}
if n != 1 {
return 0, fmt.Errorf("Expected to read 1 byte, got %d", n)
}

return keys[0], nil
}



On Tuesday, December 18, 2012 10:21:55 AM UTC-8, alber...@gmail.com wrote:
Hi everybody!

I'm also trying to do this but the code in cook.go seems outdated.
Does anyone have any suggestion?

Thank you,
Alberto

Jan Mercl

unread,
Apr 24, 2013, 4:43:21 AM4/24/13
to Archos, golang-nuts, alber...@gmail.com
On Wed, Apr 24, 2013 at 9:21 AM, Archos <raul...@sent.com> wrote:
> You can read the comments on my package which comments all stuff very well.

ReadPassword[1] steals signals w/o any coordination whatsoever with
the rest of the process (signals are global) and also without any
restoration of the captured signal. It also leaks the signal handling
goroutine.

[1]: https://github.com/kless/terminal/blob/master/util_unix.go#L80

-j

Archos

unread,
Apr 24, 2013, 5:12:07 AM4/24/13
to golan...@googlegroups.com
What I'm doing on the next goroutine is to trap both signals of CTRL-Z and CTRL-C; the rest of the process does not needs know about it. Once the function ends, that goroutine also is finished.

     sig := make(chan os.Signal, 1)
     signal.Notify(sig, syscall.SIGINT, syscall.SIGTSTP)
     go func() {
          for {
               select {
               case <-sig: // ignore
               }
          }
     }()

http://golang.org/pkg/os/signal/
http://golang.org/pkg/os/#Signal

Jan Mercl

unread,
Apr 24, 2013, 11:16:00 AM4/24/13
to Archos, golang-nuts
On Wed, Apr 24, 2013 at 11:12 AM, Archos <raul...@sent.com> wrote:
> What I'm doing on the next goroutine is to trap both signals of CTRL-Z and
> CTRL-C; the rest of the process does not needs know about it.

Doesn't need to know about it? What if the process already installed a
signal trap? Then the signal handler in your library cripples the
previously existing one. You're carelessly mutating global state.

> Once the
> function ends, that goroutine also is finished.
>
> sig := make(chan os.Signal, 1)
> signal.Notify(sig, syscall.SIGINT, syscall.SIGTSTP)
> go func() {
> for {
> select {
> case <-sig: // ignore
> }
> }
> }()

What makes you think so (wrt "... is finished.")? The signal silencing
goroutine never returns and thus leaks on every call to ReadPassword
at least one stack segment.

-j

Archos

unread,
Apr 24, 2013, 12:07:04 PM4/24/13
to golan...@googlegroups.com


El miércoles, 24 de abril de 2013 16:16:00 UTC+1, Jan Mercl escribió:
On Wed, Apr 24, 2013 at 11:12 AM, Archos <raul...@sent.com> wrote:
> What I'm doing on the next goroutine is to trap both signals of CTRL-Z and
> CTRL-C; the rest of the process does not needs know about it.

Doesn't need to know about it? What if the process already installed a
signal trap? Then the signal handler in your library cripples the
previously existing one. You're carelessly mutating global state.
Ok , I'll fix it, thanks.

> Once the
> function ends, that goroutine also is finished.
>
>      sig := make(chan os.Signal, 1)
>      signal.Notify(sig, syscall.SIGINT, syscall.SIGTSTP)
>      go func() {
>           for {
>                select {
>                case <-sig: // ignore
>                }
>           }
>      }()

What makes you think so (wrt "... is finished.")? The signal silencing
goroutine never returns and thus leaks on every call to ReadPassword
at least one stack segment.
 This is a problem related to the package os/signal; luckly, signal.stop will be added to Go 1.1

Archos

unread,
May 1, 2013, 5:14:59 PM5/1/13
to golan...@googlegroups.com
El miércoles, 24 de abril de 2013 16:16:00 UTC+1, Jan Mercl escribió:
On Wed, Apr 24, 2013 at 11:12 AM, Archos <raul...@sent.com> wrote:
> What I'm doing on the next goroutine is to trap both signals of CTRL-Z and
> CTRL-C; the rest of the process does not needs know about it.

Doesn't need to know about it? What if the process already installed a
signal trap? Then the signal handler in your library cripples the
previously existing one. You're carelessly mutating global state.

You were worng. Wheter signal.Notify is called several times with different channels for the same signal, the signal package sends notifications about incoming signals to every channel.

Archos

unread,
May 1, 2013, 5:30:58 PM5/1/13
to golan...@googlegroups.com

El miércoles, 24 de abril de 2013 16:16:00 UTC+1, Jan Mercl escribió:
> Once the function ends, that goroutine also is finished.

What makes you think so (wrt "... is finished.")? The signal silencing
goroutine never returns and thus leaks on every call to ReadPassword
at least one stack segment.

You don't need to clean up goroutines. The entire program runs in a single address space; the OS will clean all of it up when it exits.

Jan Mercl

unread,
May 1, 2013, 5:36:54 PM5/1/13
to Archos, golang-nuts
"You don't need to free allocated memory. The entire program runs in a single
address space; the OS will clean all of it up when it exits."

What the whatever you're trying to say???

The funny part is that stopping those leaking goroutines (yes, that
_is_ leaking memory), is trivial. Just fix the code instead of arguing
over it in more lines than the fix would require.

-j

PS: You're right about the signal (previous post of yours) and I was wrong.

Ahmet Alp Balkan

unread,
Oct 9, 2013, 11:28:42 PM10/9/13
to golan...@googlegroups.com, alber...@gmail.com
doesn't work anymore.

emanuele...@gmail.com

unread,
Oct 25, 2015, 4:31:27 PM10/25/15
to golang-nuts, bradg...@gmail.com
Hi, if you are on Windows OS you can use my library to get keystrokes from keyboard:

mila...@gmail.com

unread,
Mar 29, 2016, 3:00:39 PM3/29/16
to golang-nuts, bradg...@gmail.com, emanuele...@gmail.com
That works nice! Thank you...
Reply all
Reply to author
Forward
0 new messages