There is of course some level of detail here, related to terminal handling, e.g. do you want things like control-C to interrupt, or do you want to catch even that. CR vs LF. Backspace vs Delete. What do the cursor keys emit, likely VT- or ANSI-sequences.
In some instances it might be considered bad form to spin, testing for characters as it’ll potentially use 100% cpu cycles (of one core at least) but cores are cheap so …
Some time back I wanted to do this in my own BASIC interpreter but I was using the SDL library for it, and one of the things I does is handles ‘events’, so I asked it to poll for events and checked to see if there was a SDL_KEYDOWN event and if so, then process it…
It does mouse buttons too and all sorts of other stuff.
SDL is supposedly cross platform, so (famous last words) ought to “just work”.
To make it work you have to use conio to set the terminal to raw, and turn off echo. Unfortunately, this works differently in the Xcode console than it does in Terminal, so you can never be sure what you’re going to get.
Moreover, you can’t set the terminal in the INKEY code and then set it back, it happens too quickly (or something) and you won’t ever get a keypress before it’s gone back to normal mode and you will always get a null character.
So you have to do it when you start and then turn if off when you end. And when you do that, all your PRINT and INPUTs go wonky, so now you have to turn it OFF when you enter that code and then turn it back on again when you exit.
And if that were not enough, sometimes when you exit you app, the terminal is left in a weird state even though you did return it to normal mode, and from then on the line length is wonked and everything wraps wrong from then on.
Meanwhile, on the PC you simply call the keyboard driver and it gives you the code and everything works fine in three lines!
The first method at that link, getch, will stop and wait for one character. It doesn’t wait for return, but it does wait for a character.
You can, in theory, tell it to keep going without one character if you set newt.c_cc[VMIN] = 0. However, when I try this I get no characters at all, ch is always null.
This may be a macOS thing, it’s all I have to test on.
It seems to have something to do with timing, and perhaps that can be fixed by the appropriate value in newt.c_cc[VTIME], but I tried a number of different values here with no obvious result.
So the solution I used, with moderate results, was to call tcsetattr at program setup instead of on entering the getch method. Then you have to reset it at shutdown. Then when you call getch and it magically works. It seems as if setting the terminal settings and then doing the read (I tried getchar and read, no difference) immediately thereafter doesn’t work, but I can’t say why that might be.
So setting it early and just leaving it on works, but has some nasty side effects to the point where I’m thinking of removing it.
Rarely would I say this, but may I suggest moving the INKEY to a new thread?
INKEY and INSTR are very different functions and INKEY is hardly retro either - it’s really more a generic OS function. be it retro Unix, modern Linux or whatever.
Also, as someone who’s been using/programming with Unix/Linux for over 40 years now, it’s not something Unix was intended to do. It’s not just a matter of making the terminal (console, serial line, xwindow, etc.) ‘raw’ you need non-blocking too then you can use ioctl+FIONREAD and if you want cross-platform, then SDL is the way to go. It does all the hard work for you.