I am trying to write a single-process, multi-threaded terminal application (not a multi-process, individually single-threaded terminal application as seems to be far more common). I want it to be single-process so that the different terminal sessions can interact more richly and so that shared libraries are only opened once.
The basic architecture at the moment is that a terminal creates a pseudoterminal in a "master process", with the slave end handled in a main process. (By "master process", I mean this in the sense that it is the master end of the psuedoterminal, and there is one master process per terminal, but it doesn't run any of the logic of the terminal application - this is the (one and only) multithreaded long-lived "main" process).
When a new session "connects", the "master process" sets up the master PTY, and then sends the slave PTY name over a socket to the main process. The master process is then just simply relaying I/O from STDIN/OUT to the PTY master file descriptor. The main process receives the slave name and then opens it, which allows I/O to pass through. If the master process gets a SIGWINCH, it sends the new terminal size over the socket (it can't forward the SIGWINCH using ioctl
, since the main process wouldn't know which psuedoterminal it's for).
Basic input and output actually seem to work fine, e.g. if the corresponding slave thread on the main process prints something to the slave fd, it prints out on the actual terminal. However, if I try to use something more complicated like ncurses
, funny things start to happen. I think this is because ncurses
is setup to use standard input/output by default, and this needs to be overridden, since STDIN/STDOUT of the main process has nothing to do with the user's actual terminal (either nothing at all, if the process is run as a daemon, or just a log console).
I have been trying to use the setupterm
function as discussed here: https://web.cs.wpi.edu/~claypool/courses/525-S01/projects/proj3/ncurses-intro.html
This routine is called to initialize a terminal's description, without setting up the curses screen structures or changing the tty-driver mode bits. term is the character string representing the name of the terminal being used. filenum is the UNIX file descriptor of the terminal to be used for output.
However, when I actually use this in my program, it doesn't seem to have any effect. As soon as I try to set up a menu, the program behaves as if it should be interacting with its STDIN/STDOUT, as opposed to the PTY slave file descriptor, which is the only fd it should be using for I/O. Really, that's the only way that multiple PTYs could be handled in a multi-threaded program anyways.
setupterm("vt100", fd, &errret);
initscr();
start_color();
/* etc... */
errret
is 1 per the man page, so setupterm
isn't encountering any errors.
Here, fd
refers to the file descriptor corresponding to the slave end of the PTY.
Despite the description, all my debug logs show that ncurses
is trying to use STDIN/STDOUT of the main process, rather than the PTY fd that I told it to use. I haven't found any other ncurses functions to set the fd
used. How can I get ncurses
to communicate with my pseudoterminal instead of STDIN/STDOUT?
CodePudding user response:
It's not so much that ncurses uses stdin
and stdout
by default. Rather, the call initscr
initializes the input and output devices to stdin
and stdout
, respectively.
If you want to use different devices, initialise with newterm
instead of initscr
. The prototype is:
SCREEN *newterm(char *type, FILE *outfd, FILE *infd);
type
is the name of the terminal type; if you pass NULL
, newstr
will try to use the current value of the environment variable TERM
(which is what initscr
uses). Since it expects FILE*
rather than unix file descriptors, you'll need to use fdopen
to construct stdio FILE objects.
(See man initscr
for more details.)