mort
mort

Reputation: 13598

Haskell code to interact with xterm-subprocess

I want to implement a simple editor with Haskell.

My basic idea was to open an xterm instance and then send it the content the editor should display (text + e.g. coloring, cursor position, etc). The content could then simply be rewritten on every key stroke.

I managed to open xterm in a subprocess and let it display the contents of a file (see code below); however, writing to its stdin does not seem to work (I don't get any error, but the text doesn't show up in the xterm window either). Then, I tried to run simple shell commands like ls or cat - with those, the interaction via the streams does work.

Question: How can my Haskell process interact with the created xterm instance?

import System.IO
import System.Process             

main = do
    (Just hin, Just hout, Just herr, jHandle) <-
        createProcess (proc "xterm" ["-e", "tail", "-f", "foo.txt"])
           { cwd = Just "."
           , std_in = CreatePipe 
           , std_out = CreatePipe
           , std_err = CreatePipe
           }
    hPutStrLn hin "This should be printed to xterm"
    waitForProcess jHandle

Upvotes: 2

Views: 289

Answers (1)

danidiaz
danidiaz

Reputation: 27766

Unlike with cat and grep, reading and writing from an xterm is not done from the usual standard streams. You need to open a pseudo-terminal (using functions from System.Posix.Terminal), hook the slave part to the xterm by means of the -S argument, and then read/write from the master part.

About the -S argument, the xterm manpage says:

This option allows xterm to be used as an input and output channel for an existing program and is sometimes used in specialized applications. The option value specifies the last few letters of the name of a pseudo-terminal to use in slave mode, plus the number of the inherited file descriptor. If the option contains a “/” character, that delimits the characters used for the pseudo-terminal name from the file descriptor.

More information here and here.

Some example code:

import Control.Monad
import Control.Applicative
import System.IO
import System.FilePath
import System.Posix.Terminal
import System.Posix.Types
import System.Posix.IO
import System.Process

main :: IO ()
main = do
    (master@(Fd mfd),slave@(Fd sfd)) <- openPseudoTerminal
    -- deactivating echo seems to be neccessary
    slaveattr <- flip withoutMode EnableEcho <$> getTerminalAttributes slave
    setTerminalAttributes slave slaveattr Immediately
    sname <- getSlaveTerminalName master
    let sbasename = takeFileName sname
        sargs = "-S" ++ sbasename ++ "/" ++ show sfd
    (Just _, Just _, Just _, _) <- createProcess $
                (proc "xterm" [sargs]  ) { cwd = Just "."
                                         , std_in = CreatePipe
                                         , std_out = CreatePipe
                                         , std_err = CreatePipe
                                         }
    h <- fdToHandle master
    hSetBuffering h NoBuffering
    -- read and print the initial line sent by xterm
    hGetLine h >>= putStrLn
    hPutStrLn h "this should appear in the xterm"
    -- this should appear both in console and xterm
    forever $ hGetChar h >>= \c -> (putChar c >> hPutChar h c)

Upvotes: 2

Related Questions