packages feed

hermit-0.6.0.0: src/HERMIT/Win32/IO.hsc

-- | Adapted from circular-ruin's StackOverflow answer at <http://stackoverflow.com/a/10779150>
{-# LANGUAGE ForeignFunctionInterface, CPP, NoImplicitPrelude #-}
module HERMIT.Win32.IO (
    HERMIT.Win32.IO.putChar
  , HERMIT.Win32.IO.putStr
  , HERMIT.Win32.IO.putStrLn
  , HERMIT.Win32.IO.print
  , ePutChar
  , ePutStr
  , ePutStrLn
  , ePrint
  , HERMIT.Win32.IO.hPutChar
  , HERMIT.Win32.IO.hPutStr
  , HERMIT.Win32.IO.hPutStrLn
  , HERMIT.Win32.IO.hPrint
  , trace
  , traceIO
  ) where

#include <windows.h>

import Control.Concurrent.MVar
import Control.Exception (bracket)

import Data.Char (ord)
import Data.Typeable

#if MIN_VERSION_base(4,7,0)
import Foreign
#else
import Foreign hiding (unsafePerformIO)
#endif
import Foreign.C.Types

import GHC.IO.FD (FD(..)) -- A wrapper around an Int32
import GHC.IO.Handle.Types (Handle(..), Handle__(..))

import Prelude hiding (putStr, putStrLn) --(IO, Read, Show, String)

import qualified System.IO (hPutChar, hPutStr, hPutStrLn)
import System.IO hiding (putStr, putStrLn, hPutChar, hPutStr, hPutStrLn)
import System.IO.Unsafe (unsafePerformIO)
import System.Win32.Types (BOOL, HANDLE, DWORD, LPDWORD, LPWSTR, LPVOID)

-- | <http://msdn.microsoft.com/en-us/library/ms683231(VS.85).aspx>
--   HANDLE WINAPI GetStdHandle(DWORD nStdHandle);
--   returns INVALID_HANDLE_VALUE, NULL, or a valid handle
foreign import ccall unsafe "GetStdHandle"
    win32GetStdHandle :: DWORD -> IO HANDLE

sTD_OUTPUT_HANDLE, sTD_ERROR_HANDLE :: DWORD
sTD_OUTPUT_HANDLE = (#const STD_OUTPUT_HANDLE)  -- all DWORD arithmetic is performed modulo 2^n
sTD_ERROR_HANDLE  = (#const STD_ERROR_HANDLE)

-- | <http://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx>
--   DWORD WINAPI GetFileType(HANDLE hFile);
foreign import ccall unsafe "GetFileType"
    win32GetFileType :: HANDLE -> IO DWORD

fILE_TYPE_CHAR, fILE_TYPE_REMOTE :: DWORD
fILE_TYPE_CHAR   = (#const FILE_TYPE_CHAR)
fILE_TYPE_REMOTE = (#const FILE_TYPE_REMOTE)

-- | <http://msdn.microsoft.com/en-us/library/ms683167(VS.85).aspx>
--   BOOL WINAPI GetConsoleMode(HANDLE hConsole, LPDWORD lpMode);
foreign import ccall unsafe "GetConsoleMode"
    win32GetConsoleMode :: HANDLE -> LPDWORD -> IO BOOL

iNVALID_HANDLE_VALUE :: HANDLE
iNVALID_HANDLE_VALUE = intPtrToPtr $ -1

isAConsole :: HANDLE -> IO Bool
isAConsole handle
  = if (handle == iNVALID_HANDLE_VALUE) then return False
      else do ft <- win32GetFileType handle
              if ((ft .&. complement fILE_TYPE_REMOTE) /= fILE_TYPE_CHAR) then return False
                else do ptr <- malloc
                        cm  <- win32GetConsoleMode handle ptr
                        free ptr
                        return cm

-- | BOOL WINAPI WriteConsoleW(HANDLE hOutput, LPWSTR lpBuffer, DWORD nChars,
--                             LPDWORD lpCharsWritten, LPVOID lpReserved); -}
foreign import ccall unsafe "WriteConsoleW" win32WriteConsoleW
  :: HANDLE -> LPWSTR -> DWORD -> LPDWORD -> LPVOID -> IO BOOL

data ConsoleInfo = ConsoleInfo Int (Ptr CWchar) (Ptr DWORD) HANDLE

writeConsole :: ConsoleInfo -> String -> IO ()
writeConsole (ConsoleInfo bufsize buf written handle) string
  = let fillbuf :: Int -> String -> IO ()
        fillbuf i [] = emptybuf buf i []
        fillbuf i remain@(first:rest)
          | i + 1 < bufsize && ordf <= 0xffff = do pokeElemOff buf i asWord
                                                   fillbuf (i+1) rest
          | i + 1 < bufsize && ordf >  0xffff = do pokeElemOff buf i word1
                                                   pokeElemOff buf (i+1) word2
                                                   fillbuf (i+2) rest
          | otherwise                         = emptybuf buf i remain
          where ordf   = ord first
                asWord = fromInteger (toInteger ordf) :: CWchar
                sub    = ordf - 0x10000
                word1' = ((shiftR sub 10) .&. 0x3ff) + 0xD800
                word2' = (sub .&. 0x3FF)             + 0xDC00
                word1  = fromInteger . toInteger $ word1'
                word2  = fromInteger . toInteger $ word2'

        emptybuf :: (Ptr CWchar) -> Int -> String -> IO ()
        emptybuf _ 0 []     = return ()
        emptybuf _ 0 remain = fillbuf 0 remain
        emptybuf ptr nLeft remain
          = do let nLeft'    = fromInteger . toInteger $ nLeft
               ret          <- win32WriteConsoleW handle ptr nLeft' written nullPtr
               nWritten     <- peek written
               let nWritten' = fromInteger . toInteger $ nWritten
               if ret && (nWritten > 0)
                  then emptybuf (ptr `plusPtr` (nWritten' * szWChar)) (nLeft - nWritten') remain
                  else fail "WriteConsoleW failed.\n"

    in  fillbuf 0 string

szWChar :: Int
szWChar = sizeOf (0 :: CWchar)

makeConsoleInfo :: DWORD -> Handle -> IO (Either ConsoleInfo Handle)
makeConsoleInfo nStdHandle fallback = do
    handle <- win32GetStdHandle nStdHandle
    handleToConsoleInfo handle fallback

handleToConsoleInfo :: HANDLE -> Handle -> IO (Either ConsoleInfo Handle)
handleToConsoleInfo handle fallback = do
    isConsole <- isAConsole handle
    let bufsize = 10000
    if not isConsole then return $ Right fallback
        else do buf <- mallocBytes (szWChar * bufsize)
                written <- malloc
                return . Left $ ConsoleInfo bufsize buf written handle

{-# NOINLINE stdoutConsoleInfo #-}
stdoutConsoleInfo :: Either ConsoleInfo Handle
stdoutConsoleInfo = unsafePerformIO $ makeConsoleInfo sTD_OUTPUT_HANDLE stdout

{-# NOINLINE stderrConsoleInfo #-}
stderrConsoleInfo :: Either ConsoleInfo Handle
stderrConsoleInfo = unsafePerformIO $ makeConsoleInfo sTD_ERROR_HANDLE stderr

conPutChar :: ConsoleInfo -> Char -> IO ()
conPutChar ci = writeConsole ci . replicate 1

conPutStr :: ConsoleInfo -> String -> IO ()
conPutStr = writeConsole

conPutStrLn :: ConsoleInfo -> String -> IO ()
conPutStrLn ci = writeConsole ci . (++ "\n")

putChar :: Char -> IO ()
putChar = (either conPutChar System.IO.hPutChar) stdoutConsoleInfo

putStr :: String -> IO ()
putStr = (either conPutStr System.IO.hPutStr) stdoutConsoleInfo

putStrLn :: String -> IO ()
putStrLn = (either conPutStrLn System.IO.hPutStrLn) stdoutConsoleInfo

print :: Show a => a -> IO ()
print = putStrLn . show

ePutChar :: Char -> IO ()
ePutChar = (either conPutChar System.IO.hPutChar) stderrConsoleInfo

ePutStr :: String -> IO ()
ePutStr = (either conPutStr System.IO.hPutStr) stderrConsoleInfo

ePutStrLn :: String -> IO ()
ePutStrLn = (either conPutStrLn System.IO.hPutStrLn) stderrConsoleInfo

ePrint :: Show a => a -> IO ()
ePrint = ePutStrLn . show

hPutChar :: Handle -> Char -> IO ()
hPutChar haskHandle c = withHandleToHANDLE haskHandle $ \ winHandle -> do
    eitherHandle <- handleToConsoleInfo winHandle haskHandle
    either conPutChar System.IO.hPutChar eitherHandle c

hPutStr :: Handle -> String -> IO ()
hPutStr haskHandle str = withHandleToHANDLE haskHandle $ \ winHandle -> do
    eitherHandle <- handleToConsoleInfo winHandle haskHandle
    either conPutStr System.IO.hPutStr eitherHandle str

hPutStrLn :: Handle -> String -> IO ()
hPutStrLn haskHandle str = withHandleToHANDLE haskHandle $ \ winHandle -> do
    eitherHandle <- handleToConsoleInfo winHandle haskHandle
    either conPutStrLn System.IO.hPutStrLn eitherHandle str

hPrint :: Show a => Handle -> a -> IO ()
hPrint = (. show) . hPutStrLn

trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
    traceIO string
    return expr

traceIO :: String -> IO ()
traceIO = ePutStrLn

-------------------------------------------------------------------------------
-- All code that follows is taken from ansi-terminal by batterseapower, which
-- is licensed under BSD3

-- This essential function comes from the C runtime system. It is certainly provided by msvcrt, and also seems to be provided by the mingw C library - hurrah!
foreign import ccall unsafe "_get_osfhandle"
    cget_osfhandle :: CInt -> IO HANDLE

-- | This bit is all highly dubious. The problem is that we want to output ANSI to arbitrary Handles rather than forcing
-- people to use stdout. However, the Windows ANSI emulator needs a Windows HANDLE to work it's magic, so we need to be able
-- to extract one of those from the Haskell Handle.
--
-- This code accomplishes this, albeit at the cost of only being compatible with GHC.
withHandleToHANDLE :: Handle -> (HANDLE -> IO a) -> IO a
withHandleToHANDLE haskell_handle action =
    -- Create a stable pointer to the Handle. This prevents the garbage collector
    -- getting to it while we are doing horrible manipulations with it, and hence
    -- stops it being finalized (and closed).
    withStablePtr haskell_handle $ const $ do
        -- Grab the write handle variable from the Handle
        let write_handle_mvar = case haskell_handle of
                FileHandle _ handle_mvar -> handle_mvar
                DuplexHandle _ _ handle_mvar -> handle_mvar -- This is "write" MVar, we could also take the "read" one

        Just fd <- fmap (\(Handle__ { haDevice = dev }) -> fmap fdFD (cast dev)) $ readMVar write_handle_mvar

        -- Finally, turn that (C-land) FD into a HANDLE using msvcrt
        windows_handle <- cget_osfhandle fd

        -- Do what the user originally wanted
        action windows_handle

withStablePtr :: a -> (StablePtr a -> IO b) -> IO b
withStablePtr value = bracket (newStablePtr value) freeStablePtr