Reputation: 1450
I'd like to move the cursor forward and backwards in a C program. I'm reading the whole line in a loop, but i would like that if a cursor key gets pressed the cursor on the screen changes position, without blocking the loop. I tried getwch()
but it blocks the caller until enter it's pressed. What i'm looking for is a behavior similar to bash prompt. I'm reading with a code similar to this:
while (TRUE) {
printf("%s", PROMPT);
fgets(input, 1024, stdin);
do_something(input);
}
I'm trying that the function above works like readline(PROMPT)
on the readline.h
library
Upvotes: 21
Views: 27435
Reputation: 194
ANSI escape sequences allow you to move the cursor around the screen at will. This can be useful for full screen user interfaces generated by program running under shell, but can also be used in prompts. This will NOT work on the terminal emulators that don't accept the save and restore cursor position codes. More information of the escape sequences to move the cursor, please see cursor movement.
If you need a more portable solution, use curses, it's a terminal control library that manage an application's display on character-cell terminals for Unix-like systems. The curses library on the executing system sends the correct control characters based on the terminal type. Use int wmove(WINDOW* win, int y, int x)
in ncurses, to move the cursor to a new position. More information about how to program under ncurses, see NCurses-programing-howto.
Upvotes: 1
Reputation: 8286
Moves cursor right and left. stops input on newline or too many characters
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#define ESC 27
#define INSERT 50
#define DELETE 51
#define PGUP 53
#define PGDN 54
#define ARROWRIGHT 67
#define ARROWLEFT 68
#define END 70
#define HOME 72
#define OTHER 79
#define BRACKETLEFT 91
#define TILDE 126
#define BACKSPACE 127
#define SIZE 30
static const int STDIN = 0;
int kbhit(void)
{
int bytesWaiting;
ioctl(STDIN, FIONREAD, &bytesWaiting);
return bytesWaiting;
}
int main ( ) {
char input[SIZE] = {'\0'};
int insert = 0;
int each = 0;
int end = 0;
int to = 0;
int ch = 0;
int row = 0;
int col = 0;
struct termios oldattr, newattr;
//set terminal
tcgetattr( STDIN, &oldattr );
newattr = oldattr;
newattr.c_lflag &= ~( ICANON | ECHO );
tcsetattr( STDIN, TCSANOW, &newattr );
setbuf(stdin, NULL);
printf ( "\033[2J");//clear screen
printf ( "\033[25;1H");//move cursor to row 25 col 1
printf ( "OVW");
printf ( "\033[9;1H");//move cursor to row 9 col 1
printf ( "enter your text ");//prompt
//printf ( "%s", input);
printf ( "\033[9;17H");//move cursor to row 9 col 17
col = 17;
row = 9;
while ( ( ch = getchar ()) != '\n') {
if ( isprint( ch)) {
if ( insert && each < end && end < SIZE-3) {
//expand
end++;
for ( to = end; to >= each; to--) {
input[to + 1] = input[to];
}
printf ( "\033[9;17H");//move cursor to row 9 col 12
printf ( "\033[K");//erase to end of line
printf ( "%s", input);
}
printf ( "\033[%d;%dH", row, col);
printf ( "%c", ch);
input[each] = ch;
each++;
if ( each > end) {
end = each;
}
col++;
if ( each == end) {
input[each] = '\0';
}
if ( each >= SIZE-1) {
break;
}
continue;
}
if ( ch == BACKSPACE) {
if ( each) {
each--;
col--;
//contract
for ( to = each; to <= end; to++) {
input[to] = input[to + 1];
}
end--;
printf ( "\033[9;17H");//move cursor to row 1 col 7
printf ( "\033[K");//erase to end of line
printf ( "%s", input);
printf ( "\033[%d;%dH", row, col);
}
}
if ( ch == ESC) {
if ( !kbhit ( )) {
continue;
}
ch = getchar ( );
if ( ch == OTHER) {
ch = getchar ( );
if ( ch == HOME) {
col -= each;
each = 0;
printf ( "\033[%d;%dH", row, col);
ch = getchar ( );
}
if ( ch == END) {
col += end - each;
each = end;
printf ( "\033[%d;%dH", row, col);
ch = getchar ( );
}
}
if ( ch == BRACKETLEFT) {
ch = getchar ( );
if ( ch == INSERT) {
ch = getchar ( );
if ( ch == TILDE) {
insert = !insert;
printf ( "\033[25;1H");//move cursor to row 25 col 1
if ( insert) {
printf ( "INS");
}
else {
printf ( "OVW");
}
printf ( "\033[%d;%dH", row, col);
}
}
if ( ch == DELETE) {
ch = getchar ( );
if ( ch == TILDE) {
//contract
for ( to = each; to <= end; to++) {
input[to] = input[to + 1];
}
end--;
printf ( "\033[9;17H");//move cursor to row 10 col 1
printf ( "\033[K");//erase to end of line
printf ( "%s", input);
printf ( "\033[%d;%dH", row, col);
}
}
if ( ch == ARROWRIGHT) {
if ( each < end) {
printf ( "\033[C");//cursor right
each++;
col++;
}
}
if ( ch == ARROWLEFT) {
if ( each) {
printf ( "\033[D");//cursor left
each--;
col--;
}
}
}
else {
ungetc ( ch, stdin);
}
}
}
printf ( "\n\ninput was [%s]\n", input);
printf ( "\n\nbye\n");
//restore terminal
tcsetattr( STDIN, TCSANOW, &oldattr );
return 0;
}
Upvotes: 3
Reputation: 41017
Using termios
and console-codes (VT100 compatible - not portable):
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#define cursorforward(x) printf("\033[%dC", (x))
#define cursorbackward(x) printf("\033[%dD", (x))
#define KEY_ESCAPE 0x001b
#define KEY_ENTER 0x000a
#define KEY_UP 0x0105
#define KEY_DOWN 0x0106
#define KEY_LEFT 0x0107
#define KEY_RIGHT 0x0108
static struct termios term, oterm;
static int getch(void);
static int kbhit(void);
static int kbesc(void);
static int kbget(void);
static int getch(void)
{
int c = 0;
tcgetattr(0, &oterm);
memcpy(&term, &oterm, sizeof(term));
term.c_lflag &= ~(ICANON | ECHO);
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &term);
c = getchar();
tcsetattr(0, TCSANOW, &oterm);
return c;
}
static int kbhit(void)
{
int c = 0;
tcgetattr(0, &oterm);
memcpy(&term, &oterm, sizeof(term));
term.c_lflag &= ~(ICANON | ECHO);
term.c_cc[VMIN] = 0;
term.c_cc[VTIME] = 1;
tcsetattr(0, TCSANOW, &term);
c = getchar();
tcsetattr(0, TCSANOW, &oterm);
if (c != -1) ungetc(c, stdin);
return ((c != -1) ? 1 : 0);
}
static int kbesc(void)
{
int c;
if (!kbhit()) return KEY_ESCAPE;
c = getch();
if (c == '[') {
switch (getch()) {
case 'A':
c = KEY_UP;
break;
case 'B':
c = KEY_DOWN;
break;
case 'C':
c = KEY_LEFT;
break;
case 'D':
c = KEY_RIGHT;
break;
default:
c = 0;
break;
}
} else {
c = 0;
}
if (c == 0) while (kbhit()) getch();
return c;
}
static int kbget(void)
{
int c;
c = getch();
return (c == KEY_ESCAPE) ? kbesc() : c;
}
int main(void)
{
int c;
while (1) {
c = kbget();
if (c == KEY_ENTER || c == KEY_ESCAPE || c == KEY_UP || c == KEY_DOWN) {
break;
} else
if (c == KEY_RIGHT) {
cursorbackward(1);
} else
if (c == KEY_LEFT) {
cursorforward(1);
} else {
putchar(c);
}
}
printf("\n");
return 0;
}
Upvotes: 23
Reputation:
A simple example using ANSI escape sequences:
#include <stdio.h>
int main()
{
char *string = "this is a string";
char input[1024] = { 0 };
printf("%s", string);
/* move the cursor back 5 spaces */
printf("\033[D");
printf("\033[D");
printf("\033[D");
printf("\033[D");
printf("\033[D");
fgets(input, 1024, stdin);
return 0;
}
To do very much useful the terminal needs to be put into canonical mode with termios.h and/or curses.h/ncurses.h. This way the backspace key code can be caught and responded to immediately and the buffer drawn to screen accordingly. Here is an example of how to set the terminal into canonical mode with tcsetattr()
:
struct termios info;
tcgetattr(0, &info);
info.c_lflag &= ~ICANON;
info.c_cc[VMIN] = 1;
info.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &info);
Another option might be to use the readline()
or editline()
library. To use the readline library specify -lreadline to your compiler. The following code snippet can be compiled with
cc -lreadline some.c -o some
#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>
int main()
{
char *inpt;
int i = 0;
while ( i < 10 )
{
inpt = readline("Enter text: ");
add_history(inpt);
printf("%s", inpt);
printf("\n");
++i;
}
return 0;
}
Upvotes: 13