LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   get cursor position in c (https://www.linuxquestions.org/questions/programming-9/get-cursor-position-in-c-947833/)

esmn 05-31-2012 03:04 PM

get cursor position in c
 
Is there any way to get cursor position in c?(no ncurses please)
this code report the cursor position:
Code:

echo -e "\033[6n"
how can I use it in c?

BeaverusIV 05-31-2012 04:59 PM

I would have a look at how curses does it. You might be able to pick a few commands out.

hydraMax 06-01-2012 01:21 AM

Quote:

Originally Posted by esmn (Post 4692293)
Is there any way to get cursor position in c?(no ncurses please)
this code report the cursor position:
Code:

echo -e "\033[6n"
how can I use it in c?

The ideal approach is to use the nice API from a toolkit, such as GTK+ or one of the many others available. However, you can do this more directly with the XLIB functions. One approach, at least, is to grab the pointer, and then catch the next button press event. From some code I wrote a while back:


Code:

            Window rootw = XDefaultRootWindow(disp);
            XEvent evt;
            int err = XGrabPointer(disp,
                                  rootw,
                                  False,
                                  ButtonPressMask,
                                  GrabModeAsync,
                                  GrabModeAsync,
                                  None,
                                  csr,
                                  CurrentTime);
            switch(err) {
            // ...
            }
            XNextEvent(disp, &evt);
            // ...
            printf("Absolute coordinates: %d,%d\n", evt.xbutton.x_root, evt.xbutton.y_root);

All the commands used above have manual pages: XGrabPointer(3), XNextEvent(3), etc.

hydraMax 06-01-2012 01:24 AM

ERm... You probably meant the "terminal" cursor, didn't you? :o

esmn 06-01-2012 02:55 AM

Quote:

ERm... You probably meant the "terminal" cursor, didn't you?
yes. It's a terminal program.
but thanks for your reply.

NevemTeve 06-01-2012 03:47 AM

Use write(1,buf,len) for writing, read(0,buf,len) for reading... (but first use isatty to check you aren't redirected)

Edit: plus you may want to use termios, if you haven't done it yet (example: http://dtelnet.sourceforge.net/shkeys.c)

esmn 06-01-2012 11:43 AM

I use write() read() function:
Code:

/* get_cur_pos.c */
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
int main()
{

        char buf[8];
        int curline;
        char cmd[]="echo -e '\033[6n'";
        struct termios save,raw;
        tcgetattr(0,&save);
        cfmakeraw(&raw); tcsetattr(0,TCSANOW,&raw);
        if (isatty(fileno(stdin)))
        {
                write(1,cmd,sizeof(cmd));
                read (0 ,buf ,sizeof(buf));

                /* It doesn't work!!?
                sscanf(buf,"%d",curline);
                printf("\n\rCurrent Line: %d\n\r" , curline);
                */

                printf("\n\rCurrent Line: %c%c\n\r" , buf[2] ,buf[3] );
        }
        tcsetattr(0,TCSANOW,&save);
        return 0;
}

but there are some problems:
-write() function print out "echo -e ' '" in the output:
Code:

esmn@deblin:$ ./get_cur_pos
echo -e ''
Current Line: 2;
esmn@deblin:$

-sscanf doesn't work properly. so how can I use the result in the program?

-Is there any termios function to get cursor position?

NevemTeve 06-01-2012 01:16 PM

You don't write 'echo' to terminal, 'echo' means write in shell... Correction:

Code:

char cmd[]="\033[6n";
Also it's not 'scanf', but 'read'

Nominal Animal 06-01-2012 01:56 PM

Quote:

Originally Posted by esmn (Post 4693170)
there are some problems:
-write() function print out "echo -e ' '" in the output
-sscanf doesn't work properly
-Is there any termios function to get cursor position?

No, there is no termios function to get cursor position.

What you need to do is to use tcsetattr to disable echo (ECHO, fixing your write() problem), set noncanonical input mode (disabling ICANON, fixing your sscanf() problem), and I believe temporarily disable the receiver (disabling CREAD) while conversing with the terminal so that you do not mix actual standard input and console responses.

It is best to first use tcgetattr to get the current settings, save them so that you can restore them later (easiest is just to call it twice, saving the results to different structures), and edit only the relevant bits in the results.

You are going to have very little success with stdio.h, because of the internal caching (especially of input) it does, though. It works fine with unistd.h low-level I/O, though; you just have to check for the exit statuses carefully.

Here is a proof-of-concept shell script:
Code:

#!/bin/bash

# Get current settings.
if ! termios="$(stty -g 2>/dev/null)" ; then
    echo "Not running in a terminal." >&2
    exit 1
fi

# Restore terminal settings when the script exits.
trap "stty '$termios'" EXIT

# Disable ICANON ECHO. Should probably also disable CREAD.
stty -icanon -echo

# Request cursor coordinates
printf '\033[6n'

# Read response from standard input; note, it ends at R, not at newline
read -d "R" rowscols

# Clean up the rowscols (from \033[rows;cols -- the R at end was eaten)
rowscols="${rowscols//[^0-9;]/}"
rowscols=("${rowscols//;/ }")
printf '(row %d, column %d) ' ${rowscols[0]} ${rowscols[1]}

# Reset original terminal settings.
stty "$termios"

I threw together a proof-of-concept C code, too:
Code:

#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>

#define  RD_EOF  -1
#define  RD_EIO  -2

static inline int rd(const int fd)
{
    unsigned char  buffer[4];
    ssize_t        n;

    while (1) {

        n = read(fd, buffer, 1);
        if (n > (ssize_t)0)
            return buffer[0];

        else
        if (n == (ssize_t)0)
            return RD_EOF;

        else
        if (n != (ssize_t)-1)
            return RD_EIO;

        else
        if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
            return RD_EIO;
    }
}

static inline int wr(const int fd, const char *const data, const size_t bytes)
{
    const char      *head = data;
    const char *const tail = data + bytes;
    ssize_t          n;

    while (head < tail) {

        n = write(fd, head, (size_t)(tail - head));
        if (n > (ssize_t)0)
            head += n;

        else
        if (n != (ssize_t)-1)
            return EIO;

        else
        if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
            return errno;
    }

    return 0;
}

/* Return a new file descriptor to the current TTY.
*/
int current_tty(void)
{
    const char *dev;
    int        fd;

    dev = ttyname(STDIN_FILENO);
    if (!dev)
        dev = ttyname(STDOUT_FILENO);
    if (!dev)
        dev = ttyname(STDERR_FILENO);
    if (!dev) {
        errno = ENOTTY;
        return -1;
    }

    do {
        fd = open(dev, O_RDWR | O_NOCTTY);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1)
        return -1;

    return fd;
}

/* As the tty for current cursor position.
 * This function returns 0 if success, errno code otherwise.
 * Actual errno will be unchanged.
*/
int cursor_position(const int tty, int *const rowptr, int *const colptr)
{
    struct termios  saved, temporary;
    int            retval, result, rows, cols, saved_errno;

    /* Bad tty? */
    if (tty == -1)
        return ENOTTY;

    saved_errno = errno;

    /* Save current terminal settings. */
    do {
        result = tcgetattr(tty, &saved);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        retval = errno;
        errno = saved_errno;
        return retval;
    }

    /* Get current terminal settings for basis, too. */
    do {
        result = tcgetattr(tty, &temporary);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        retval = errno;
        errno = saved_errno;
        return retval;
    }

    /* Disable ICANON, ECHO, and CREAD. */
    temporary.c_lflag &= ~ICANON;
    temporary.c_lflag &= ~ECHO;
    temporary.c_cflag &= ~CREAD;

    /* This loop is only executed once. When broken out,
    * the terminal settings will be restored, and the function
    * will return retval to caller. It's better than goto.
    */
    do {

        /* Set modified settings. */
        do {
            result = tcsetattr(tty, TCSANOW, &temporary);
        } while (result == -1 && errno == EINTR);
        if (result == -1) {
            retval = errno;
            break;
        }

        /* Request cursor coordinates from the terminal. */
        retval = wr(tty, "\033[6n", 4);
        if (retval)
            break;

        /* Assume coordinate reponse parsing fails. */
        retval = EIO;

        /* Expect an ESC. */
        result = rd(tty);
        if (result != 27)
            break;

        /* Expect [ after the ESC. */
        result = rd(tty);
        if (result != '[')
            break;

        /* Parse rows. */
        rows = 0;
        result = rd(tty);
        while (result >= '0' && result <= '9') {
            rows = 10 * rows + result - '0';
            result = rd(tty);
        }

        if (result != ';')
            break;

        /* Parse cols. */
        cols = 0;
        result = rd(tty);
        while (result >= '0' && result <= '9') {
            cols = 10 * cols + result - '0';
            result = rd(tty);
        }

        if (result != 'R')
            break;

        /* Success! */

        if (rowptr)
            *rowptr = rows;

        if (colptr)
            *colptr = cols;

        retval = 0;

    } while (0);

    /* Restore saved terminal settings. */
    do {
        result = tcsetattr(tty, TCSANOW, &saved);
    } while (result == -1 && errno == EINTR);
    if (result == -1 && !retval)
        retval = errno;

    /* Done. */
    return retval;
}

int main(void)
{
    int        fd, row, col;
    char        buffer[64];
    char *const tail = buffer + sizeof(buffer);
    char      *head = buffer + sizeof(buffer);

    fd = current_tty();
    if (fd == -1)
        return 1;

    row = 0;
    col = 0;
    if (cursor_position(fd, &row, &col))
        return 2;

    if (row < 1 || col < 1)
        return 3;

    /* Construct response "(row, col) " from right to left,
    * then output it to standard error, and exit.
    */

    *(--head) = ' ';
    *(--head) = ')';

    {  unsigned int    u = col;
        do {
            *(--head) = '0' + (u % 10U);
            u /= 10U;
        } while (u);
    }

    *(--head) = ' ';
    *(--head) = ',';

    {  unsigned int    u = row;
        do {
            *(--head) = '0' + (u % 10U);
            u /= 10U;
        } while (u);
    }

    *(--head) = '(';

    if (wr(STDERR_FILENO, head, (size_t)(tail - head)))
        return 4;

    return 0;
}

I am not at all sure if disabling CREAD is sufficient to avoid mixing input with terminal output. The code seems to work even when there is pending input in standard input, but I don't know if that behaviour is Linux-specific or standard.

This is why a terminal library like ncurses is so useful: it handles both the input processing, and the conversation with the terminal. If you only need a subset of the functionality, I'm sure you could quite easily write your own, but you'd have to use low-level I/O like I did above. In that case, I'd keep the original terminal settings somewhere, and just flip the current settings when necessary; that saves a lot of unnecessary tcsetattr()/tcgetattr() calls. Also, when reading the terminal response, I'd use longer reads, and everything not a response moved to an input cache; it would be much faster, and would not discard proper input even if interspersed with the terminal responses.

Finally, in noncanonical mode, you can read individual keypresses, not just completed lines.

esmn 06-02-2012 03:34 AM

Thanks for complete guidance.

Of course ncurses is very useful but I like to handle terminal with
escape sequences and linux low level function.

irancplusplus 09-02-2013 08:12 AM

to be deleted

irancplusplus 09-03-2013 12:19 AM

to be deletedd


All times are GMT -5. The time now is 02:27 AM.