Search code examples
pythoncursesttyinittab

Sending curses application's output to tty1


Goal

I'd like to make my curses Python application display its output on a Linux machine's first physical console (TTY1) by adding it to /etc/inittab, reloading init with telinit q and so on.

I'd like to avoid a hacky way of using IO redirection when starting it from /etc/inittab with:

1:2345:respawn:/path/to/app.py > /dev/tty1 < /dev/tty1

What I'm after is doing it natively from within my app, similar to the way getty does it, i.e. you use a command line argument to tell it on which TTY to listen to:

S0:2345:respawn:/sbin/getty -L ttyS1 115200 vt100

Example code

For simplicity, let's say I've written this very complex app that when invoked, prints some content using ncurses routines.

import curses

class CursesApp(object):

  def __init__(self, stdscr):
    self.stdscr = stdscr
    # Code producing some output, accepting user input, etc.
    # ...    

curses.wrapper(CursesApp)

The code I already have does everything I need, except that it only shows its output on the terminal it's run from. When invoked from inittab without the hacky redirection I mentioned above, it works but there's no output on TTY1.

I know that init doesn't redirect input and output by itself, so that's expected.

How would I need to modify my existing code to send its output to the requested TTY instead of STDOUT?

PS. I'm not asking how to add support for command line arguments, I already have this but removed it from the code sample for brevity.


Solution

  • This is rather simple. Just open the terminal device once for input and once for output; then duplicate the input descriptor to the active process' file descriptor 0, and output descriptor over file descriptors 1 and 2. Then close the other handles to the TTY:

    import os
    import sys
    
    with open('/dev/tty6', 'rb') as inf, open('/dev/tty6', 'wb') as outf:
        os.dup2(inf.fileno(), 0)
        os.dup2(outf.fileno(), 1)
        os.dup2(outf.fileno(), 2)
    

    I tested this with the cmd module running on TTY6:

    import cmd
    cmd.Cmd().cmdloop()
    

    Works perfectly. With curses it is apparent from their looks that something is missing: TERM environment variable:

    os.environ['TERM'] = 'linux'
    

    Execute all these statements before even importing curses and it should work.