INKEY$ in cross-platform C

So it appears that supporting INKEY$ like functionality is non-trivial in C, as the console normally buffers. I’ve found a number of solutions that work on Windows OR Unix.

I’m wondering if anyone has implemented this in C before, and has any advice on the best solution?

In C in UNIX/POSIX, yes. The solutions in Windows will be completely different.

In UNIX you can either use the standard (n)curses library

or the lighter weight POSIX tc*

https://michaeldipperstein.github.io/keypress.html

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.

Windows: maybe kbhit? kbhit in C language - GeeksforGeeks

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”.

-Gordon

Yeah, SDL might work, but it’s a little heavy to pull in just for keypresses, SDL being full graphics framework.

But now I remembered the magic search term, these things are called these days “TUI” as opposed to “GUI”, and that unearthed e.g. this

which is include-file-only

for doing texty things.

1 Like

This looked perfect until I noticed that it blocks on input, which INKEY does not.

The Windows solution you linked to, on the other hand, uses getch, which appears to be exactly what I need.

Look at the rogueutil.h

Ok, INKEY on Unix is maddening!

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!

Which method of the many are you using in Unix? I would first try the https://github.com/sakhmatd/rogueutil/blob/bbbc1ef73e9df6d22a3459f92f7e16bd8be535f5/rogueutil.h#L110 (and the getch is just above it).

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.

Oh, bummer.

There is of course the path of curses library, but that will take over the whole screen, so it’s not like the usual “line printer dialogue” style of BASIC.

There is some elaborate logic in the max/timing: struct termios -- data structure containing terminal information

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.

-Gordon

Ha! My bad, I was trying to post to my thread on INKEY and this came up first in my recent posts list and I didn’t read it very well.

I’ve just pressed some buttons, and maybe now this (INKEY) thread is redisentangled from the other (INSTR) thread. In the spirit of helpful moderator action, of course.

2 Likes