4

I am writing a python application that is intended to be used interactively inside unix pipelines. The application should fire up a curses based terminal UI, and based on user interaction, write to the standard output only right before exiting.

Typical usage would be that of a typical pipeline:

foo_command | my_application | sink_app

The problem I am having is that python curses library sends all sorts of things to stdout while the app is running. Furthermore, sink_app starts executing while my_application is running.

  • How to I prevent curses from polluting stdout?
  • How do I buffer the output and control when I want to flush it?
  • Is it possible to control when sink_app starts executing and when it stops accepting input?

From what I gather, I need to save a reference to the stdout file descriptor so I can later write to it. And pass another fd (which one?) to ncurses. Supposedly through newterm(), but this isn't available on python curses binding.

  • An answer on https://stackoverflow.com/questions/48697482/python-using-ncurses-when-underlying-library-logs-to-stdout shows how to call newterm from Python with ctypes. – Dan D. Dec 10 '18 at 02:41

2 Answers2

3

You could do this (setup a curses application in a pipeline) by using the newterm function to directly open the terminal for managing the screen, while reserving stdout for the pipeline. The dialog program does this.

But the Python curses interface does not have newterm (it only has initscr, which uses stdout for the screen...), and while there are probably workarounds (in Python, juggling the I/O streams), it hasn't been addressed in any of the answers on this forum.

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
  • 1
    Hey Thomas, thank you for the answer. I think we're on the right track. I believe such juggling you mention is implemented in [percol](https://github.com/mooz/percol). The relvant code is [here](https://github.com/mooz/percol/blob/d0bc902555fff5abef85012af3cbc323b915843b/percol/tty.py). The standard descriptors are taken from the *os* module and then there is some juggling with os.dup() and os.dup2(). But I don't quite grasp the whole thing. Could you be so kind as to update your answer with a small example for stdout only? – user3160153 Dec 10 '18 at 16:58
1

As suggested, juggling input streams. This is not well tested, but appears to be on the right track...

import os

# back up the piped stdin
mystdin_fd = os.dup(0)

# open the terminal for reading keyboard events
termin = open("/dev/tty")

# replace stdin with the terminal, so curses initscr() and getch() work
os.dup2(termin.fileno(), 0)

# initialise curses - https://docs.python.org/3/howto/curses.html
...

# read from the piped stdin
mystdin = open(mystdin_fd, 'r')
for line in mystdin:
    ...
jozxyqk
  • 16,424
  • 12
  • 91
  • 180