Rolando Urquiza
Rolando Urquiza

Reputation: 1450

Move the cursor in a C program

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

Answers (4)

Jack47
Jack47

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

user3121023
user3121023

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

David Ranieri
David Ranieri

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

user4832408
user4832408

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

Related Questions