Sending curses input to another terminal in C via

2019-07-18 15:47发布

My C program run multiple thread in the terminal that print messages asynchronously. I'd like a thread to show curses output, but as it is asynchronous it must be in another terminal.

My idea is to write the curses output to a fifo and open a terminal with cat fifo.

But how could I recover the ouput the curses out and output it to a file?

Thank you.

3条回答
姐就是有狂的资本
2楼-- · 2019-07-18 15:55

curses requires direct access to the terminal in order lookup escape codes, detect resolution etc. You should probably try to have one thread dedicated for curses output in the current (main) terminal, and redirect all debug messages from other threads to either a logfile/pipe/logging facility (and present them inside the curses if you want to)

查看更多
Explosion°爆炸
3楼-- · 2019-07-18 16:12

ncurses needs a terminal because it initializes the I/O connection. A FIFO would not be suitable, since it is one-way and (see for example Perform action if input is redirected) probably cannot be initialized as a terminal. ncurses uses the TERM environment variable to lookup the terminal description (the actual terminal is not consulted on the matter).

There is a simple example, ditto in ncurses-examples which uses xterm for multiple input/output screens. That uses the pty interface, e.g.,

#ifdef USE_XTERM_PTY
    int amaster;
    int aslave;
    char slave_name[1024];
    char s_option[sizeof(slave_name) + 80];
    const char *xterm_prog = 0;

    if ((xterm_prog = getenv("XTERM_PROG")) == 0)
        xterm_prog = "xterm";

    if (openpty(&amaster, &aslave, slave_name, 0, 0) != 0
        || strlen(slave_name) > sizeof(slave_name) - 1)
        failed("openpty");
    if (strrchr(slave_name, '/') == 0) {
        errno = EISDIR;
        failed(slave_name);
    }
    sprintf(s_option, "-S%s/%d", slave_name, aslave);
    if (fork()) {
        execlp(xterm_prog, xterm_prog, s_option, "-title", path, (char *) 0);
        _exit(0);
    }
    fp = fdopen(amaster, "r+");
    if (fp == 0)
        failed(path);
#else

and newterm to pass that file descriptor to ncurses:

static void
open_screen(DITTO * target, char **source, int length, int which1)
{   
    if (which1 != 0) { 
        target->input =
            target->output = open_tty(source[which1]);
    } else {
        target->input = stdin;
        target->output = stdout;
    }

    target->which1 = which1;   
    target->titles = source;
    target->length = length;
    target->fifo.head = -1;
    target->screen = newterm((char *) 0,        /* assume $TERM is the same */
                             target->output,
                             target->input);

    if (target->screen == 0)
        failed("newterm");

    (void) USING_SCREEN(target->screen, init_screen, target);
}

If you read the first quoted section of ditto, you will notice that it uses an option of xterm which allows an application to pass a file descriptor to it:

   -Sccn   This option allows xterm to be used  as  an  input  and  output
           channel  for  an existing program and is sometimes used in spe-
           cialized applications.  The option value specifies the last few
           letters  of the name of a pseudo-terminal to use in slave mode,
           plus the number of  the  inherited  file  descriptor.   If  the
           option  contains  a "/" character, that delimits the characters
           used for the pseudo-terminal name  from  the  file  descriptor.
           Otherwise,  exactly two characters are used from the option for
           the pseudo-terminal name, the remainder is the file descriptor.
           Examples  (the  first  two  are equivalent since the descriptor
           follows the last "/"):

               -S/dev/pts/123/45
               -S123/45
               -Sab34

           Note that xterm does not close any file descriptor which it did
           not  open for its own use.  It is possible (though probably not
           portable) to have an application  which  passes  an  open  file
           descriptor  down  to  xterm  past  the initialization or the -S
           option to a process running in the xterm.

Here is a screenshot showing ditto:

enter image description here

查看更多
Root(大扎)
4楼-- · 2019-07-18 16:13

curses uses a terminal for its input and output, so if you want to intercept that and make it go somewhere other than a terminal, the easiest method (though non-trivial) is to use a psuedoterminal. You do that by calling posix_openpt which gives you a pseudoterminal master device. You then call grantpt, unlockpt, and ptsname to get the name of a terminal device that you can then fopen and pass to curses newterm to initialize the terminal.

Once that is done, everything that curses writes to the terminal will be readble from the master, and everything written to the master will be input to curses. It is like a fifo, just with all the extra terminal functionality curses expects.

查看更多
登录 后发表回答