How portable is the use of getch outside curses mode? - c

I'm working on a terminal program to recognize individual key presses, including keypad keys, but I'd rather not do it in curses/program mode if possible. Rather than reinvent the wheel using terminfo and some sort of mapping or tree structure for fast keypad key matching, I figured I might just leverage curses and use tcgetattr() and tcsetattr() to do what I want outside curses mode while still using curses I/O functions to do the translation of keypad keys for me. Much to my surprise, this works (Linux, ncurses 6.1.20180127):
/**
* Most error checking elided for brevity.
*/
#include <stdio.h> // printf
#include <string.h> // memcpy
#include <curses.h>
#include <termios.h> // tcgetattr, tcsetattr
int main(void)
{
struct termios curr, new_shell_mode;
int c, fd;
SCREEN *sp;
FILE *ttyf;
/*
* Initialize desired abilities in curses.
* This unfortunately clears the screen, so
* a refresh() is required, followed by
* endwin().
*/
ttyf = fopen("/dev/tty", "r+b");
fd = fileno(ttyf);
sp = newterm(NULL, ttyf, ttyf);
raw();
noecho();
nonl();
keypad(stdscr, TRUE);
refresh();
// Save the current curses mode TTY attributes for later use.
tcgetattr(fd, &curr);
endwin();
/*
* Set the shell/non-curses mode TTY attributes to
* match those of program/curses mode (3 attempts).
*/
memcpy(&new_shell_mode, &curr, sizeof curr);
for (c = 0; c < 3; c++) {
tcsetattr(fd, TCSADRAIN, &new_shell_mode);
tcgetattr(fd, &curr);
if (0 == memcmp(&new_shell_mode, &curr, sizeof curr))
break;
}
// If new shell mode could fully be set, get a key press.
if (c != 3)
c = getch();
reset_shell_mode();
delscreen(sp);
fclose(ttyf);
printf("%02X\n", c);
return 0;
}
However, given that I've exited curses mode, is it actually safe/portable to still use getch() in the manner shown?
Or do I need to take the more difficult path of using setupterm() to load the terminfo DB and loop through the strnames array, calling tigetstr() for each, plus set my own termios flags manually and deal with reading the keypress myself?
Nothing in the XSI Curses spec seems to forbid this, provided stdscr remains valid, which seems to be either until the program exits or delwin() is called, I can continue using it, and since stdscr is connected to my ttyf file, which is the terminal, I can use it to get a keypress without resorting to handling everything myself.

You initialized curses using newterm, and it doesn't matter that you called endwin: curses will resume full-screen mode if it does a refresh as a side-effect of calling getch.
That's not just ncurses, but any curses implementation (except for long-obsolete BSD versions from the 1980s). X/Open Curses notes
If the current or specified window is not a pad, and it has been moved or modified since the last refresh operation, then it will be refreshed before another character is read.
In your example, nothing was "moved or modified". But getch checks. (There's probably nothing to be gained by the endwin/termios stuff, since newterm doesn't do a clear-screen until the first refresh).

Related

Switching back to Standard output after using ncurses WINDOW [duplicate]

I have been trying to get getch to work in another program with no success. So I have made the most basic program I can using getch the way I want it to work in the main program.
I have researched the need for noecho, cbreak, initscr and nodelay, I have also tried using newscr() but to no success.
The problem I am having is that the chars aren't being printed to the screen till I hit "enter", when they should be put to the screen every loop. Why is this happening? Also the cursor doesn't return to the left of the screen at the new line. eg.
abc
def
ghi
I have looked for the answer but am stumped again...
#include <stdio.h>
#include <ncurses.h>
int main()
{
initscr();cbreak(); noecho();nodelay(stdscr,0);
char c ;
while((c=getch())!=EOF){
putchar(c);}
return 0;
}
You're not seeing the output because your stdout stream is line buffered.
Your program is getting the individual characters all right; but the output stream is buffering them.
Try fflush(stdout); or switching stdout to unbuffered mode with setbuf(stdout, NULL);.
The problem with disabling buffering is that it's inefficient for bulk data processing when the output isn't a terminal.
You can make it conditional on the standard output being a tty:
if (isatty(fileno(stdout))) /* #include <unistd.h> */
setbuf(stdout, NULL);
To return the cursor to the start of the line, you need to put out a carriage return \r. This is because curses' cbreak mode has disabled the ONLCR tty mode (on Output, when sending NL add CR).
If you unconditionally add \r, then it will appear in files when your output is redirected. So again you need some isatty hack.
A much better might be to learn how to use the tcgetattr and tcsetattr functions to precisely control specific tty parameters, if all you want is to do character-at-a-time input without echo, and not actually develop an interactive curses-based program.
Do you really want character-at-a-time input, or just to diable echo? It's easy to disable echo. Call tcgetattr to fill a struct termios with the current settings of file descriptor 0 (if it is a tty). Flip some flags to turn off echoing, then call tcsetattr to install the updated structure. When your program exits, be nice and put back the original one. Done.
Yes, ncurses is a good way to get character-by-character control.
And yes, you must call "initscr()" and "cbreak()".
SUGGESTIONS:
1) Compare your code with this ncurses "hello world":
#include <ncurses.h>
int main()
{
initscr(); /* Start curses mode */
printw("Hello World !!!"); /* Print Hello World */
refresh(); /* Print it on to the real screen */
getch(); /* Wait for user input */
endwin(); /* End curses mode */
return 0;
}
2) See what happens if you do a "refresh()" and/or remove the "noecho()".
3) This tutorial has lots of good info that might also help:
http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/

determing if a user pressed tab [duplicate]

This question already has answers here:
How to detect that arrow key is pressed using C under Linux or Solaris?
(3 answers)
Closed 8 years ago.
So I am writing a program, and of the the tasks is to determine if a user has pressed tab. So when he presses tab, I should print something to the console(or do tab completition etc). My problem is how do I do it without the user pressing enter. I tried looking into ncurses but I couldn't find a simple example that would teach me how to do it with tab.
Edit:
Using Linux
You can act on input using ncurses and the getch() function. It's going to return you an int value for the key pressed, you can check for a tab via looking to see if the return was 9. This code will loop displaying what was pressed until it was a tab then it exits.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <ncurses.h>
int main() {
int c;
initscr(); /* Start curses mode */
cbreak();
noecho();
while(9 != (c = getch())) {
printw("%c\n", c);
if(halfdelay(5) != ERR) { /* getch function waits 5 tenths of a second */
while(getch() == c)
if(halfdelay(1) == ERR) /* getch function waits 1 tenth of a second */
break;
}
printw("Got a %d\n", c);
cbreak();
}
endwin();
return 0;
}
Technically this is not a C language question but a matter of operating system or runtime environment. On POSIX systems you must set, at least, your terminal in non-canonical mode.
The canonical mode buffers keyboard inputs to process them further if needed (for example, this lets you erase chars before your application see them).
There is many ways to switch to non-canonical mode. Of course you can use many different libraries ncurses, etc. But the trick behind is a set of system calls called termios. What you have to do is to read current attributes of the POSIX terminal and modify them accordingly to your needs. For example :
struct termios old, new;
/* read terminal attributes */
tcgetattr(STDIN_FILENO,&old);
/* get old settings */
new=old;
/* modify the current settings (common: switch CANON and ECHO) */
new.c_lflag &=(~ICANON & ~ECHO);
/* push the settings on the terminal */
tcsetattr(STDIN_FILENO,TCSANOW,&new);
do_what_you_want_and_read_every_pressed_char();
/* ok, restore initial behavior */
tcsetattr(STDIN_FILENO,TCSANOW,&old);

Creating custom keyboard shortcuts in a linux c application

I am trying to handle keyboard shortcuts, I already know how to do it with signals but the problem is that the signal list doesn't offer a lot of choices.
So I was wondering if it was possible to handle shortcuts like CTRL+'key'
and key can be any keyboard key like A Z E R T Y.
Here is an example of using GNU readline. You can capture key sequences Ctrl+P, Ctrl+G, etc.
int keyPressed(int count, int key) {
printf("key pressed: %d\n",key);
rl_on_new_line();
return 0;
}
int main() {
rl_catch_signals = 0;
rl_bind_keyseq("\\C-g",keyPressed);
rl_bind_keyseq("\\C-p",keyPressed);
rl_bind_keyseq("\\C-z",keyPressed);
while(1) {
char *line = readline("rl> ");
}
For special characters such as signal characters, Ctrl+C,Ctrl+Z you will need rl_catch_signals=0. This way, you can define your own signal handlers.
One thing I found is that rl_bind_keyseq("\\C-z",keyPressed) will not be called, even if you put the terminal in raw mode before calling readline. Instead the terminal will still interpret Ctrl+Z as SIGTSTP.
Looking through the source code, it's apparent that every time readline() is called, the terminal settings are reset.
//rltty.c
#if defined (HANDLE_SIGNALS)
tiop->c_lflag &= ~ISIG;
#else
tiop->c_lflag |= ISIG;
#endif
Unless you want to modify readline, I suggest defining signal handlers for special characters.

How do I use getch from curses without clearing the screen?

I'm learning to program in C and want to be able to type characters into the terminal while my code is running without pressing return. My program works, however when I call initscr(), the screen is cleared - even after calling filter(). The documentation for filter suggests it should disable clearing - however this is not the case for me.
#include <stdio.h>
#include <curses.h>
#include <term.h>
int main(void) {
int ch;
filter();
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);
while((ch = getch()) != EOF);
endwin();
return 0;
}
Why does the above code still clearr the screen, and what could be done to fix it?
I'm using Debian Lenny (stable) and gnome-terminal if that helps.
You would see your screen cleared in a curses application for one of these reasons:
your program calls initscr (which clears the screen) or newterm without first calling filter, or
the terminal initialization clears the screen (or makes it appear to clear, by switching to the alternate screen).
In the latter case, you can suppress the alternate screen feature in ncurses by resetting the enter_ca_mode and exit_ca_mode pointers to NULL as done in dialog. Better yet, choose a terminal description which does what you want.
Further reading:
Why doesn't the screen clear when running vi? (xterm FAQ)
Extending the answer by mike.dld, this works for me on MacOS X 10.6.6 (GCC 4.5.2) with the system curses library - without clearing the screen. I added the ability to record the characters typed (logged to a file "x"), and the ability to type CONTROL-D and stop the program rather than forcing the user to interrupt.
#include <stdio.h>
#include <curses.h>
#include <term.h>
#define CONTROL(x) ((x) & 0x1F)
int main(void)
{
FILE *fp = fopen("x", "w");
if (fp == 0)
return(-1);
SCREEN *s = newterm(NULL, stdin, stdout);
if (s == 0)
return(-1);
cbreak();
noecho();
keypad(stdscr, TRUE);
int ch;
while ((ch = getch()) != EOF && ch != CONTROL('d'))
fprintf(fp, "%d\n", ch);
endwin();
return 0;
}
Basically, curses is designed to take over the screen (or window, in the case of a windowed terminal). You can't really mix curses with stdio, and you can't really use curses to just input or output something without messing with the rest of the screen. There are partial workarounds, but you're never really going to be able to make it work the way that it sounds like you want to. Sorry.
I'd suggest either rewriting your program to use curses throughout, or investigating alternatives like readline.
Use newterm() instead of initscr(), you should be fine then. And don't forget about delscreen() if you follow this advice.

Ignore backspace key from stdin

I want to make a program that forces it's user to input text but doesn't allow him to erase any of it, what's a simple way of doing it in C?
The only thing I've got is (c = getchar()) != EOF && c != '\b' which doesn't work. Any ideas?
POSIX - unix version
#include <sys/types.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
int
main()
{
int fd=fileno(stdin);
struct termios oldtio,newtio;
tcgetattr(fd,&oldtio); /* save current settings */
memcpy(&newtio, &oldtio, sizeof(oldtio));
newtio.c_lflag = ICANON;
newtio.c_cc[VERASE] = 0; /* turn off del */
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
/* process user input here */
tcsetattr(fd,TCSANOW,&oldtio); /* restore setting */
return 0;
}
You can't do it with portable code -- essentially every OS has some sort of minimal buffering/editing built into the standard input stream.
Depending on the OSes you need to target, there's a good change you'll have a getch available that will do unbuffered reading. On Windows, you include <conio.h> and go for it. On most Unix, you'll need to include (and link to) curses (or ncurses) for it.
This is likely more complicated than you imagine. To do this, you'll presumably need to take over control of echoing the characters the user is typing etc.
Have a look at the curses library. The wgetch function should be what you need, but first you'll need to initialise curses etc. Have a read of the man pages - if you're lucky you'll find ncurses or curses-intro man pages. Here's a snippet:
To initialize the routines, the routine initscr or newterm must be
called before any of the other routines that deal with windows and
screens are used. The routine endwin must be called before exiting.
To get character-at-a-time input without echoing (most interactive,
screen oriented programs want this), the following sequence should be
used:
initscr(); cbreak(); noecho();
Most programs would additionally use the sequence:
nonl();
intrflush(stdscr, FALSE);
keypad(stdscr, TRUE);
If you've not got that manpage / for further info, look up the individual function man pages.

Resources