Recognizing special keys without taking over the screen - c

In a program that uses the curses or ncurses library, the getch() function can recognize either an ordinary character (like 'x') or the escape sequence sent by the arrow and function keys. For example, typing the up arrow key might send the sequence (Escape, '[', 'A'), but getch() will return the int value KEY_UP. It even uses timing information to distinguish between an Escape character sent by itself and an Escape character that's part of a sequence.
To use getch() successfully, you first have to call initscr(), which clears the screen so that curses can control what's displayed.
Is there a convenient way to recognize special keys without taking over the screen? Ideally I'd like to call getch() without first calling initscr(), and have it return the same value it would have returned if I had called initscr(), but experiment indicates that doesn't work.
Here's a non-working example of what I'm trying to do. The program prompts the user to enter Up, Down, Escape, Left, Right, and conforms that the correct keys were entered.
Running the program with -c invokes initscr() and allows it to work correctly. Running it without -c calls getch() without first calling initscr(); all the getch() calls fail without waiting for input, returning ERR (-1).
// gcc arrows.c -o arrows -lncurses
#include <term.h>
#include <stdio.h>
#include <curses.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char **argv) {
int use_curses = 0;
if (argc == 2 && strcmp(argv[1], "-c") == 0) {
use_curses = 1;
}
else if (argc != 1) {
fprintf(stderr, "Usage: %s [-c]\n", argv[0]);
exit(EXIT_FAILURE);
}
WINDOW *win;
if (use_curses) {
win = initscr();
cbreak();
noecho();
nonl();
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE);
mvaddstr(0, 0, "Press UP, DOWN, Escape, LEFT, RIGHT");
}
else {
puts("Press UP, DOWN, Escape, LEFT, RIGHT");
}
int keys[5];
for (int i = 0; i < 5; i ++) {
keys[i] = getch();
}
int ok;
if (keys[0] == KEY_UP &&
keys[1] == KEY_DOWN &&
keys[2] == '\x1b' &&
keys[3] == KEY_LEFT &&
keys[4] == KEY_RIGHT)
{
ok = 1;
if (use_curses) {
mvaddstr(2, 0, "OK");
}
else {
puts("OK");
}
}
else {
ok = 0;
char message[100];
sprintf(message,
"Incorrect input: (%d, %d, %d, %d, %d)",
keys[0], keys[1], keys[2], keys[3], keys[4]);
if (use_curses) {
mvaddstr(2, 0, message);
}
else {
puts(message);
}
}
if (use_curses) {
mvaddstr(4, 0, "Press any key to quit ");
(void)getch();
endwin();
}
exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
}
The output without the -c option (on Ubuntu 17.04 x86_64) is:
Press UP, DOWN, Escape, LEFT, RIGHT
Incorrect input: (-1, -1, -1, -1, -1)
UPDATE: A solution using something other than curses would be fine. It would have to use terminfo, directly or indirectly, to know what input sequences to look for. (It seems to me that recognizing special key inputs should be straightforward and not intimately tied to taking over the entire screen.)

The filter function, called before initscr, tells curses to use a single line (rather than the whole screen). There is an example of its use (named "filter") in ncurses-examples, which consists of a command-prompt.
getch updates at least part of the screen, e.g., when it echoes the input. That update can affect either just a line (using filter), or the whole screen. If you're clever, you can turn off echo, and (since filter suppresses the screen erasure), pretend that it updates nothing at all. For example, you could redirect the output to /dev/null by initializing curses with newterm.
Echoing input is only part of the story: curses does initialize the screen, and the associated refresh done by getch would make that hard to avoid in a portable way. There's an ncurses-specific option in the sample program which works around unwanted switching between normal/alternate screens, which could occur during screen-initialization.
Here's a screenshot of "filter", demonstrating that it does not take over the whole screen. The inverse video is due to ncurses initializing colors (white-on-black), but as the help-message indicates, that's configurable. Without making a movie, it's hard to demonstrate that it handles special keys, but reading the source code should be enough.

Related

C - How to use halfdelay(i) in the ncurses library

I am trying to create a snake game in C using the ncurses library. I would like my program to detect user input at a constant tick speed. If there is no input after a certain amount of time I would like my program to continue along (ie. update game).
This is what I wrote to test out the halfdelay(i) function:
#include <ncurses.h>
int main(void)
{
initscr();
halfdelay(5);
int user_input;
do
{
user_input = getch();
if (user_input != -1)
{
clear();
printw("Key pressed: %d\n", user_input);
}
else
{
//printf("Timeout.\n");
printw("Timeout.\n");
}
} while (user_input != ESC);
endwin();
return 0;
}
EDIT #1:
I would like to see
Timeout.
Timeout.
Timeout.
Code used:
#include <ncurses.h>
int main(void)
{
initscr();
halfdelay(5);
int in;
do
{
timeout(1);
in = getch();
if (in != -1)
{
clear();
printw("Key pressed: %d\n", in);
}
else
{
//printf("Timeout.\n");
printw("Timeout.\n");
}
refresh();
} while (in != 27);
endwin();
return 0;
}
Some history first.
Traditionally, the ESC key value has been used as the prefix for escape sequences, which denote special sequences of characters that can be interpreted as a non-graphic character (e.g., an arrow key, a function key, etc.)
Due to (n)curses focus on portability (and telecommunication), out of the box it supports this notion of escape sequences, and as such pressing the ESC key can have some side effects. Notably, when keypad is enabled, there is an inbuilt delay as the program waits to decide if the user simply pressed ESC or if it needs to wait for some more information to complete the escape sequence. This timing can be adjusted via the ESCDELAY environment variable, or the set_escdelay function.
All this is important as you work forward, as given its a game you may want to enable the functionality of the keypad eventually, which will create some extra steps when using the ESC key.
And because of all this, there is no ESC or KEY_ESC macro for for the escape key. Instead, its raw code is 27 (or the octal 033).
Your use of the halfdelay function seems perfectly fine to me, just know that the argument is in tenths of a second, so 5 is half a second. Tenths of a second may not achieve the desired effect in a game, so consider using the timeout function instead, which allows for higher precision.
A simple, working example:
#include <ncurses.h>
int main(void) {
initscr();
halfdelay(5);
int user_input;
while ((user_input = getch()) != 27) {
clear();
if (user_input == ERR)
printw("Timeout.");
else
printw("Key pressed: %d\n", user_input);
refresh();
}
endwin();
}
Your updated program "works", it just doesn't clear the screen properly. Note that you probably don't want to mix calls to printw and printf as it creates a strange mess of the screen.
Also, you should use halfdelay or timeout, but not both. Remember that timeout takes its argument in milliseconds, which is 1/1000 of a second, and it sets the blocking delay for a window (timeout for stdscr, wtimeout for specific windows). It's not a "sleep" style function.
Use one delay function at a time, move clear to outside the if statement, and use printw. This is functionally the same program to the one posted above, just with a do ... while loop.
#include <ncurses.h>
int main(void) {
initscr();
/*
noecho();
scrollok(stdscr, TRUE);
*/
timeout(500);
int in;
do {
in = getch();
clear();
if (in != ERR)
printw("Key pressed: %d\n", in);
else
printw("Timeout.\n");
refresh();
} while (in != 27);
endwin();
}
If you are expecting the display of this program to be something along the lines of
Timeout.
Timeout.
Key pressed: 65
Timeout.
...
like a more traditional terminal, then you simply want to remove clear all together, and un-comment noecho and scrollok.

ncurses only reporting mouse movement when bstate is non-zero

I'm following along with the code from
https://gist.github.com/sylt/93d3f7b77e7f3a881603
Here it is, in case of 404
#include <curses.h>
#include <stdio.h>
int main()
{
initscr();
cbreak();
noecho();
// Enables keypad mode. This makes (at least for me) mouse events getting
// reported as KEY_MOUSE, instead as of random letters.
keypad(stdscr, TRUE);
// Don't mask any mouse events
mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
printf("\033[?1003h\n"); // Makes the terminal report mouse movement events
fflush(stdout);
for (;;) {
int c = wgetch(stdscr);
// Exit the program on new line fed
if (c == '\n')
break;
char buffer[512];
size_t max_size = sizeof(buffer);
if (c == ERR) {
snprintf(buffer, max_size, "Nothing happened.");
}
else if (c == KEY_MOUSE) {
MEVENT event;
if (getmouse(&event) == OK) {
snprintf(buffer, max_size, "Mouse at row=%d, column=%d bstate=0x%08lx",
event.y, event.x, event.bstate);
}
else {
snprintf(buffer, max_size, "Got bad mouse event.");
}
}
else {
snprintf(buffer, max_size, "Pressed key %d (%s)", c, keyname(c));
}
move(0, 0);
insertln();
addstr(buffer);
clrtoeol();
move(0, 0);
}
printf("\033[?1003l\n"); // Disable mouse movement events, as l = low
endwin();
return 0;
}
When I put that into a file and compile it (gcc -o test test.c -lncurses), in xterm, mouse movements are reported without a button press. In gnome-terminal and Konsole, the movement events are only reported if a button on my mouse is pressed!
I would usually say it's just a compatibility/ standards thing BUT when I execute:
printf '\033[?1003h' on gnome-terminal or konsole, I can see all the mouse movements reported without any buttons pressed!
So what's going on here? How do I persuade gnome-terminal and konsole to behave like xterm? Or even to behave like themselves when they are in bash mode?
EDIT: Additional clues:
It appears this code works on all terminals if I scroll my mouse wheel. It's more than likely that this is what happened when I originally tested in xterm, as even xterm requires either a button pressed or the mouse to be scrolled for this to work.
Therefore: my question becomes why does the terminal only report mouse movement after the bstate has been modified in some way, even when the terminal reports escape sequences in bash mode?

How to display asterisks for password input only after two consecutive colons?

I want to read input from user. After the user typed the sequence ::, the rest of the input should be asterisks.
For example: let's say user typed: Alex::vn800. On the screen, the output should be: Alex::*****.
I have a function that reads input from user and display * on screen, but I didn't managed to use it in a middle of reading line.
I tried to manipulate functions getchar() and scanf() to stop reading line after detecting a sequence of ::, and then call the function but nothing worked.
What can I do?
Update: Hey! thanks for the answers.
I fainlly solved the problem by using the library conio.h - like in any other simple get-password code, just that I saprated it for cases according to what I want the screen will show and not just '*' for any case.
If it's not strictly necessary to have both username and password in the same line, I would suggest simply getting the username first and then using the getpass() function, like here.
I've tried ataman's method, but it didn't work on OSX 10.9.
Here's a modified version, following goldPseudo's approach:
#include <stdio.h>
#include <stdlib.h>
int main() {
int readChar;
int status = 0;
int semicolonCount = 0;
system ("/bin/stty raw"); // disable buffering and other stuff
while ((readChar = getchar()) && (readChar != 13 /* ENTER keycode */))
{
if (status == 0)
{
printf("%c", readChar);
if (readChar == ':')
{
semicolonCount++;
} else {
semicolonCount = 0;
}
if (semicolonCount == 2)
{
status = 1;
}
} else {
printf("*");
}
}
printf("\r\n"); // print new line
system ("/bin/stty cooked"); // reenable buffering, might not be the original mode the terminal was in
return 0;
}
The problem with this approach is that, since you are in "raw mode", special characters, like BACKSPACE, ENTER, Ctrl+D and even Ctrl+C, are not processed.
You would have to implement the behaviour for those characters yourself.

Non-Blocking i/o in c? (windows)

I'm trying to get a non-blocking I/O on a Windows terminal application (windows only, sorry!).
What if I want to have a short input time in wich the user can press a button, but if he doesn't the input stops and the program continues?
For example:
A timer that counts from 1 to whatever that stops when the user presses a certain key:
I should have a while loop, but if I do a getch or a getchar function it will stop the program, right?
I know I could use kbhit(); , but for the "program" I'm trying to make I need to know the input, not just IF THERE IS input!
Are there any simple functions that would allow me to read like the last key in the keyboard buffer?
From the documentation for _kbhit():
The _kbhit function checks the console for a recent keystroke. If the function returns a nonzero value, a keystroke is waiting in the buffer. The program can then call _getch or _getche to get the keystroke.
So, in your loop:
while (true) {
// ...
if (_kbhit()) {
char c = _getch();
// act on character c in whatever way you want
}
}
So, you can still use _getch(), but limit its use to only after _kbhit() says there is something waiting. That way it won't block.
Here is how to make a non blocking call to stdin in windows by using the right API :
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <windows.h>
void ErrorExit(LPSTR);
void KeyEventProc(KEY_EVENT_RECORD ker);
// Global variables are here for example, avoid that.
DWORD fdwSaveOldMode;
HANDLE hStdin;
void printToCoordinates(int x, int y, char* text)
{
printf("\033[%d;%dH%s", y, x, text);
}
int main()
{
printf("\033[H\033[J");
int i = 0;
char* s = "*";
DWORD fdwMode, cNumRead;
INPUT_RECORD irInBuf[128];
DWORD bufferSize = 0;
hStdin = GetStdHandle(STD_INPUT_HANDLE);
// Just a check to ensure everything is fine at this state
if (hStdin==INVALID_HANDLE_VALUE){
printf("Invalid handle value.\n");
exit(EXIT_FAILURE);
}
// Just a check to ensure everything is fine at this state
if (! GetConsoleMode(hStdin, &fdwSaveOldMode) )
ErrorExit("GetConsoleMode");
// Those constants are documented on Microsoft doc
// ENABLE_PROCESSED_INPUT allows you to use CTRL+C
// (so it's not catched by ReadConsoleInput here)
fdwMode = ENABLE_WINDOW_INPUT | ENABLE_PROCESSED_INPUT;
if (! SetConsoleMode(hStdin, fdwMode) )
ErrorExit("SetConsoleMode");
while (i < 60) {
// The goal of this program is to print a line of stars
printToCoordinates(i, 5, s);
i++;
GetNumberOfConsoleInputEvents(hStdin, &bufferSize);
// ReadConsoleInput block if the buffer is empty
if (bufferSize > 0) {
if (! ReadConsoleInput(
hStdin, // input buffer handle
irInBuf, // buffer to read into
128, // size of read buffer
&cNumRead) ) // number of records read
ErrorExit("ReadConsoleInput");
// This code is not rock solid, you should iterate over
// irInBuf to get what you want, the last event may not contain what you expect
// Once again you'll find an event constant list on Microsoft documentation
if (irInBuf[cNumRead-1].EventType == KEY_EVENT) {
KeyEventProc(irInBuf[cNumRead-1].Event.KeyEvent);
Sleep(2000);
}
}
Sleep(100);
}
// Setting the console back to normal
SetConsoleMode(hStdin, fdwSaveOldMode);
CloseHandle(hStdin);
printf("\nFIN\n");
return 0;
}
void ErrorExit (LPSTR lpszMessage)
{
fprintf(stderr, "%s\n", lpszMessage);
// Restore input mode on exit.
SetConsoleMode(hStdin, fdwSaveOldMode);
ExitProcess(0);
}
void KeyEventProc(KEY_EVENT_RECORD ker)
{
printf("Key event: \"%c\" ", ker.uChar.AsciiChar);
if(ker.bKeyDown)
printf("key pressed\n");
else printf("key released\n");
}
Please notice this work in brand new Terminal application but not in CMD (due to termcaps used in the code) but it will compile and you can run it anyway.

Variable randomly becomes null before input is even taken

So, here is my problem. I am using ncurses, and when I press the up or down button, it says up arrow, and so I expect to move on in to my while loop, and take further input. The problem is, the variable input somehow in this process becomes NULL and it exists. Why is that? What I want is for it print up or down arrow, and the program to proceed normally.
int ch;
char input[100];
initscr();
raw();
keypad(stdscr, TRUE);
noecho();
ch = getch();
if(ch== KEY_UP)
printf("\nUp Arrow"); fflush(stdout);
if(ch== KEY_DOWN)
printf("\ndown Arrow");fflush(stdout);
endwin();
Here is an SSCCE (Short, Self-Contained, Complete Example) based on your code:
#include <stdio.h>
#include <stdlib.h>
#include <curses.h>
int main(void)
{
int ch;
char input[100];
initscr();
raw();
keypad(stdscr, TRUE);
noecho();
ch = getch();
endwin();
if (ch== KEY_UP)
printf("\nUp Arrow\n");
if (ch== KEY_DOWN)
printf("\ndown Arrow\n");
fflush(stdout);
while(1)
{
if (fgets(input, sizeof (input), stdin) == NULL)
{
printf("Early exit\n");
exit(0);
}
printf("Read: %s", input);
fflush(stdout);
}
printf("Late exit\n");
return 0;
}
When I run this, it correctly detects if I use the up or down arrow keys, reporting it after I've called endwin() rather than before. The fflush() calls would not normally be necessary, but it seems that the standard output is fully buffered rather than line buffered after you finish with curses, which is a little surprising. (I'm testing on Mac OS X 10.8.2.)
The other trick I've used is diagnostic prints so I know what's happening where. I called the program curses, and when I ran it, the screen cleared; then I typed an up arrow (in this case), then sssddd and return, then Control-D to indicate end of input (EOF), and the rest of the output was:
$ ./curses
Up Arrow
sssddd
Read: sssddd
Early exit
$
It is all behaving as I'd expect except for the buffering on standard output. There isn't a way to get to the Late exit print statement in this program.
As stated input is undefined. Also, fgets is used for file input streams, not for reading the std input stream. (Use gets instead)

Resources