pythonlinuxbashcursesoutput-redirect

Show curses GUI for a script, even with redirected output


I want to write something like a terminal version of dmenu, which can be used to search for a file, and then pass the file's location to another program in the pipeline, like:

my_script | xargs vim # search for a file and open in vim

I tried to do it in python with output redirection, but it doesn't seem to work with curses.

import sys
import curses

prev_out = sys.stdout
print("1:", sys.stdout)
sys.stdout = open("/dev/tty", "w")
print("2:", sys.stdout)

window = curses.initscr()
window.addstr(1, 1, "testing")
window.getch()
curses.endwin()

sys.stdout = prev_out
print("3:", sys.stdout)

When I call it like this:

myscript > /dev/pts/1 # redirect output to another tty

print's behave how I'd expect them to (2 in original tty, 1 and 3 in the other one), but the curses UI is displayed in the /dev/pts/1.
So my question is, is there a way to redirect curses output back to /dev/tty, or is there a different way to display a text based GUI, which can be redirected by altering sys.stdout?


Solution

  • I achieved this behaviour by writing a short bash wrapper for my python script.

    #!/bin/bash
    # bash wrapper, that handles forking output between
    # GUI and the output meant to go further down the pipe
    
    # a pipe is created to catch the the wanted output
    # date and username added in case of two scripts running at
    # approximately the same time
    fifo=/tmp/search_gui_pipe-$(whoami)-$(date +%H-%M-%S-%N)
    [ ! -p "$fifo" ] && mkfifo $fifo
    
    # python script is called in background, with stdin/out
    # redirected to current tty, and location of the pipe file
    # passed as an argument (also pass all args for parsing)
    ./search_gui.py "$fifo" $@ >/dev/tty </dev/tty &
    
    # write the program output to stdout and remove the pipe file
    cat "$fifo" && rm "$fifo"