ansi-terminal-0.8.0.2: src/System/Console/ANSI/Windows/Detect.hs
{-# OPTIONS_HADDOCK hide #-}
module System.Console.ANSI.Windows.Detect
(
ANSIEnabledStatus (..)
, ConsoleDefaultState (..)
, isANSIEnabled
) where
import Control.Exception (SomeException(..), throwIO, try)
import Data.Bits ((.&.), (.|.))
-- 'lookupEnv' is not available until base-4.6.0.0 (GHC 7.6.1)
import System.Environment.Compat (lookupEnv)
import System.IO.Unsafe (unsafePerformIO)
import System.Console.ANSI.Windows.Foreign (bACKGROUND_INTENSE_WHITE,
ConsoleException(..), CONSOLE_SCREEN_BUFFER_INFO (..), DWORD,
eNABLE_VIRTUAL_TERMINAL_PROCESSING, fOREGROUND_INTENSE_WHITE,
getConsoleMode, getConsoleScreenBufferInfo, getStdHandle, HANDLE,
iNVALID_HANDLE_VALUE, nullHANDLE, setConsoleMode, sTD_OUTPUT_HANDLE, WORD)
-- | The default state of the console
data ConsoleDefaultState = ConsoleDefaultState
{ defaultForegroundAttributes :: WORD -- ^ Foreground attributes
, defaultBackgroundAttributes :: WORD -- ^ Background attributes
} deriving (Eq, Show)
-- | The status of the console as regards it being ANSI-enabled or not
-- ANSI-enabled.
data ANSIEnabledStatus
= ANSIEnabled -- ^ ANSI-enabled
| NotANSIEnabled ConsoleDefaultState -- ^ Not ANSI-enabled (including the
-- state of the console when that status
-- was determined)
deriving (Eq, Show)
-- | This function assumes that once it is first established whether or not the
-- Windows console is ANSI-enabled, that will not change. If the console is not
-- ANSI-enabled, the state of the console is considered to be its default state.
{-# NOINLINE isANSIEnabled #-}
isANSIEnabled :: ANSIEnabledStatus
isANSIEnabled = unsafePerformIO $ do
e <- safeIsANSIEnabled
if e
then return ANSIEnabled
else do
hOut <- getValidStdHandle sTD_OUTPUT_HANDLE
info <- getConsoleScreenBufferInfo hOut
let attributes = csbi_attributes info
fgAttributes = attributes .&. fOREGROUND_INTENSE_WHITE
bgAttributes = attributes .&. bACKGROUND_INTENSE_WHITE
consoleDefaultState = ConsoleDefaultState
{ defaultForegroundAttributes = fgAttributes
, defaultBackgroundAttributes = bgAttributes }
return $ NotANSIEnabled consoleDefaultState
-- This function takes the following approach. If the environment variable
-- APPVEYOR exists and is set to 'True', it assumes the code is running in the
-- Appveyor build environment and that the build console is ANSI-enabled.
-- Otherwise, if the environment variable TERM exists and is not set to 'dumb'
-- or 'msys' (see below), it assumes the console is ANSI-enabled. Otherwise, it
-- tries to get a valid standard output handle. If that does not fail, it
-- tried to get a ConHost console mode. If that fails, it assumes that the
-- handle is ANSI-enabled. Otherwise, it tries to enable virtual terminal
-- processing. If that fails, it assumes the console is not ANSI-enabled.
--
-- In Git Shell, if Command Prompt or PowerShell are used, the environment
-- variable TERM is set to 'msys'. If 'Git Bash' (mintty) is used, TERM is set
-- to 'xterm' (by default).
safeIsANSIEnabled :: IO Bool
safeIsANSIEnabled = do
appveyor <- lookupEnv "APPVEYOR"
case appveyor of
Just "True" -> return True
_ -> do
term <- lookupEnv "TERM"
case term of
Just "dumb" -> return False
Just "msys" -> doesEnableANSIOutSucceed
Just _ -> return True
Nothing -> doesEnableANSIOutSucceed
-- This function returns whether or not an attempt to enable virtual terminal
-- processing succeeded, or was deemed to succeed, in the IO monad.
doesEnableANSIOutSucceed :: IO Bool
doesEnableANSIOutSucceed = do
result <- try enableANSIOut :: IO (Either SomeException ())
case result of
Left _ -> return False
Right () -> return True
-- This function tries to get a valid handle on the standard output and throws
-- an exception if it cannot. It then tries to get a ConHost console mode for
-- that handle. If it can not, it assumes that the standard output handle is
-- ANSI-enabled. If it can, it tries to enable virtual terminal processing and
-- throws an exception if it can not.
enableANSIOut :: IO ()
enableANSIOut = do
hOut <- getValidStdHandle sTD_OUTPUT_HANDLE
result <- conHostConsoleMode hOut
case result of
Nothing -> return () -- assume hOut is ANSI-enabled
Just mOut -> do
let mOut' = mOut .|. eNABLE_VIRTUAL_TERMINAL_PROCESSING
setConsoleMode hOut mOut'
-- This function tries to get a valid standard handle and throws an exception if
-- it cannot.
getValidStdHandle :: DWORD -> IO HANDLE
getValidStdHandle nStdHandle = do
h <- getStdHandle nStdHandle
if h == iNVALID_HANDLE_VALUE || h == nullHANDLE
then throwIO $ ConsoleException 6 -- Invalid handle or no handle
else return h
-- The function tries to get a ConHost console mode from a handle that is
-- assumed to be a valid standard handle (see getValidStdHandle)
conHostConsoleMode :: HANDLE -> IO (Maybe DWORD)
conHostConsoleMode h = do
result <- try (getConsoleMode h) :: IO (Either SomeException DWORD)
case result of
Left _ -> return Nothing
Right mode -> return (Just mode)