
It's worth noting that `hGetNonBlocking` only works as described with GHC on *nix. Windows and non-GHC treat it as a blocking hGet. Also, unless I'm mistaken, my example leaks the Handle for the slave PTY (though it would be trivial to fix). --- Elliot Robinson Phone: (321) 252-9660 Site: www.argiopetech.com Email: elliot.robinson@argiopetech.com PGP Fingerprint: 0xD1E72E6A9D0610FFBBF838A6FFB5205A9FEDE59A On Sat, Aug 2, 2014 at 2:32 AM, Elliot Robinson < elliot.robinson@argiopetech.com> wrote:
I second pseudo-terminals. I find the System.Posix.Terminal to be very usable (though I've admittedly never tried System.Posix.Pty). I occasionally dabble with "bots" for terminal-based games (e.g., Nethack), and have used PTYs with some deal of success.
I've included some code from a Nethack bot attempt from several years ago. Hopefully you'll find it useful. As a quick usage/rationale overview, I feed an attoparsec parser by repeatedly calling receive (intermixed with some intelligent use of `transmit` to deal with messages and other incomplete screen updates) until I receive a valid map. `hGetNonBlocking` bypasses the standard buffering mechanisms and returns whatever is in the PTY buffer (which may be nothing) rather than waiting for the specified line/block/whatever buffer to fill. `stop` and `start` should be obvious in their purpose, if not their implementation.
data Local = Local { pty :: Handle }
class Connection a where transmit :: a -> ByteString -> IO () receive :: a -> IO ByteString stop :: a -> IO () start :: IO a
instance Connection Local where transmit l s = B.hPut (pty l) s receive l = B.hGetNonBlocking (pty l) 4096 stop l = hClose $ pty l start = do (fd1, fd2) <- openPseudoTerminal (hPty) <- fdToHandle fd1 slave <- fdToHandle fd2 _<- createProcess (proc "sh" ["-c", "/usr/games/bin/nethack"]){ std_in = (UseHandle slave), std_out = (UseHandle slave) } return $ Local hPty
Cheers, Elliot Robinson Phone: (321) 252-9660 Site: www.argiopetech.com Email: elliot.robinson@argiopetech.com
PGP Fingerprint: 0xD1E72E6A9D0610FFBBF838A6FFB5205A9FEDE59A
On Fri, Aug 1, 2014 at 11:20 PM, Brandon Allbery
wrote: On Fri, Aug 1, 2014 at 11:07 PM, Chris Myzie
wrote: As a workaround, I am able to trick the child haskell process into thinking it's running in an interactive terminal by wrapping it with /usr/bin/script:
We discussed this on IRC the other day. Haskell is doing the same thing that C/C++ stdio / iostreams, and most other buffering systems, do: line buffering on terminal-like devices, block buffering on files and pipes. This is generally expected behavior; although it can be confusing to new programmers, ultimately it is more efficient for most programs.
Interactive use like this, especially over pipes, is fairly unusual; normally you're just copying data around /en masse/, and block buffering is far more efficient. Note that line buffering is not and can not be implemented at the kernel level for ordinary files or pipes, so the kernel interface is actually character buffering which is extremely inefficient (at least one context switch per individual character).
You might want to search for something like "buffering line block pipes files" to see quite a lot of discussion about it, in pretty much every language you can think of.
By the way, more efficient than using script(1) is, as I told you in IRC, to use the mechanism it is using directly: pseudo-terminals (ptys). See http://hackage.haskell.org/package/unix-2.7.0.1/docs/System-Posix-Terminal.h... for the standard pty stuff or http://hackage.haskell.org/package/posix-pty-0.1.0/docs/System-Posix-Pty.htm... for what is claimed to be a simpler interface intended for what you are doing.
-- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe