Pages

Sunday, March 25, 2018

How to write interactive pager

When I wrote pspg, I had to solve few issues. The main problem was switch stdin stream from input data to keyboard to ensure user input. The pspg is pager. Pager input is stdin usually. But input for ncurses event loop is stdin too. There are few solution, but, unfortunately, some solution in some environments doesn't work.

I have a function when data is read from input:
void
readfile(FILE *fp, ...)
{
    if (fp == NULL)
       /* use stdin */
       fp = stdin;

    while (read = getline(&line, &len, fp))
    {
    }
}

/* when I process a option -f I opening the file */
fp = fopen(optarg, "r");
if (fp == NULL)
{
    fprintf(stderr, "cannot to open file: %s", optarg);
    exit(1);
}

/* now I can read data from input */
readfile(fp, ...);

/* when file was explicitly opened, close it */
if (fp != NULL)
   fclose(fp);

And the real important part started:
if (!isatty(fileno(stdin)))
{
    /* 
     * We should to try reopen some terminal device as stdin.
     * Start with /dev/tty. If it is not possible, try to use
     * device attached to stdout
     */
    if (freopen("/dev/tty", "r", stdin) != NULL)
        noatty = false;
    else if (freopen(ttyname(fileno(stdout)), "r", stdin) != NULL)
        noatty = false;
    else
    {
        /*
         * freopen fails. We can try to use device attached to
         * stderr. Check if stderr is joined with terminal and
         * and close stdin against some artefacts.
         */
        if (!isatty(fileno(stderr)))
        {
            fprintf(stderr, "no terminal device..");
            exit(1);
        }
        noatty = true;
        close(stdin);
    }
}
else
{
    /* all is done, stdin is joined to terminal */
    noatty = false;
}   

if (!noatty)
    /* usual ncurses start */
    initscr();
else
    /* use stderr as input stream - fallback solution used by less pager */
    newterm(termname(), stdout, stderr);
That is all. It is not too complicate code, but tuning this code needed few months to work inside wide set of environments: ssh, screen, ...

Note: how to detect if terminal uses UTF8 encoding? This question is much simpler:
/* init all locale variables */
setlocale(LC_ALL, ""); 
/* check LC_CTYPE */
force8bit = strcmp(nl_langinfo(CODESET), "UTF-8") != 0;

No comments:

Post a Comment