2

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 Answers3

2

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.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • You're answer seems to be the closest for my question but I don't understand the last part: once I've opened the pseudoterminal with fopen should I pass it as the input and pass my fifo as the output? The fifo is used to communicate with the external xterm. – Nicolas Scotto Di Perto Apr 27 '16 at 22:14
  • Also if I call newterm I get a `SCREEN *`, in know how to write on `WINDOW *` (with `waddch` for example) but how to can I write on a `SCREEN *`? – Nicolas Scotto Di Perto Apr 28 '16 at 08:34
  • You use `set_term` to set the current screen you're working with, and then the global `WINDOW *stdscr` – Chris Dodd Apr 28 '16 at 14:38
  • The program runs asynchronously and some threads may print some text, won't it alter the output of other threads? – Nicolas Scotto Di Perto Apr 28 '16 at 14:58
1

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)

Stian Skjelstad
  • 2,277
  • 1
  • 9
  • 19
  • Yes but I wonder how to catch curses datas output, I don't know how to do it. – Nicolas Scotto Di Perto Apr 27 '16 at 18:55
  • You are not supposed to, since curses expect to have direct write access to the tty. You can probably cheat and replace the filedescriptor at location 1 after curses has been initiallized with something else, like a pipe – Stian Skjelstad Apr 27 '16 at 19:04
  • You can use `newterm` instead of `initscr` to initialize curses with some terminal other than stdin/stdout. You can even call it multiple times to manage multiple terminals. – Chris Dodd Apr 27 '16 at 19:09
1

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

Community
  • 1
  • 1
Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • This is pretty neat, but I can only use opencurses I don't know ncurses, does that example really use it? – Nicolas Scotto Di Perto Apr 27 '16 at 21:17
  • The example builds/works with different curses implementations. There are ifdef's to use ncurses extensions, but the screenshot did not use those. (what is "opencurses"?). – Thomas Dickey Apr 27 '16 at 21:18
  • It believe it's the name of the UNIX curses library, `curses.h`. – Nicolas Scotto Di Perto Apr 27 '16 at 21:22
  • ncurses is largely compatible with "X/Open Curses" (as I said, the examples should *work*). – Thomas Dickey Apr 27 '16 at 21:25
  • untar the files, run the configure script and make. If you have problems, there's a bug-reporting mailing list - this website isn't *that*. – Thomas Dickey Apr 27 '16 at 21:28
  • All right I think I understand the example a bit more, it needs the name of the pseudoterminal from the command line arguments and then the output is redirected to them. If I am right it's a little bit different since in my case I don't wan't to open an existing pseudoterminal but output to a fifo that xterm read from because I don't want to ask the user for pseudoterminal name. – Nicolas Scotto Di Perto Apr 27 '16 at 21:53
  • I was having trouble getting input with my second window newterm; this way with openpty() and -S(ccn) was the solution. Thanks! – CourageousPotato May 26 '19 at 02:02