packages feed

ansi-terminal-0.8.0.3: src/System/Console/ANSI/Windows/Detect.hs

{-# OPTIONS_HADDOCK hide #-}

module System.Console.ANSI.Windows.Detect
  (
    ANSISupport (..)
  , ConsoleDefaultState (..)
  , aNSISupport
  ) where

import Control.Exception (SomeException(..), throwIO, try)
import Data.Bits ((.&.), (.|.))
import System.IO (stdout)
import System.IO.Unsafe (unsafePerformIO)

import System.Console.ANSI.Windows.Foreign (ConsoleException(..),
  CONSOLE_SCREEN_BUFFER_INFO (..), DWORD, HANDLE, WORD,
  bACKGROUND_INTENSE_WHITE, eNABLE_VIRTUAL_TERMINAL_PROCESSING,
  fOREGROUND_INTENSE_WHITE, getConsoleMode, getConsoleScreenBufferInfo,
  iNVALID_HANDLE_VALUE, nullHANDLE, setConsoleMode, withHandleToHANDLE)

-- | The default state of the console.
data ConsoleDefaultState = ConsoleDefaultState
  { defaultForegroundAttributes :: WORD -- ^ Foreground attributes
  , defaultBackgroundAttributes :: WORD -- ^ Background attributes
  } deriving (Eq, Show)

-- | How the console is assumed to support ANSI control codes.
data ANSISupport
  = Native                       -- ^ Assume ANSI-enabled
  | Emulated 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 requires emulation, that will not change. If the console
-- requires emulation, the state of the console is considered to be its default
-- state.
{-# NOINLINE aNSISupport #-}
aNSISupport :: ANSISupport
aNSISupport = unsafePerformIO $ withHandleToHANDLE stdout aNSISupport'

-- | This function first checks if the Windows handle is valid and throws an
-- exception if it is not. It then tries to get a ConHost console mode for
-- that handle. If it can not, it assumes that the handle is ANSI-enabled. If
-- virtual termimal (VT) processing is already enabled, the handle does not
-- require emulation. Otherwise, it trys to enable processing. If it can, the
-- handle is ANSI-enabled. If it can not, emulation will be attempted and the
-- state of the console is considered to be its default state.
aNSISupport' :: HANDLE -> IO ANSISupport
aNSISupport' h =
  if h == iNVALID_HANDLE_VALUE || h == nullHANDLE
    then throwIO $ ConsoleException 6  -- Invalid handle or no handle
    else do
      tryMode <- try (getConsoleMode h) :: IO (Either SomeException DWORD)
      case tryMode of
        Left _     -> return Native  -- No ConHost mode
        Right mode -> if mode .&. eNABLE_VIRTUAL_TERMINAL_PROCESSING /= 0
          then return Native  -- VT processing already enabled
          else do
            let mode' = mode .|. eNABLE_VIRTUAL_TERMINAL_PROCESSING
            trySetMode <- try (setConsoleMode h mode') :: IO (Either SomeException ())
            case trySetMode of
              Left _   -> emulated       -- Can't enable VT processing
              Right () -> return Native  -- VT processing enabled
 where
  emulated = do
    info <- getConsoleScreenBufferInfo h
    let attributes = csbi_attributes info
        fgAttributes = attributes .&. fOREGROUND_INTENSE_WHITE
        bgAttributes = attributes .&. bACKGROUND_INTENSE_WHITE
        consoleDefaultState = ConsoleDefaultState
          { defaultForegroundAttributes = fgAttributes
          , defaultBackgroundAttributes = bgAttributes }
    return $ Emulated consoleDefaultState