vty-windows-0.2.0.3: src/Graphics/Vty/Platform/Windows/WindowsInterfaces.hs
{-# LANGUAGE ForeignFunctionInterface, CPP #-}
-- | This module provides wrappers around Win32 API calls. These functions provide initialization,
-- shutdown, and input event handling.
module Graphics.Vty.Platform.Windows.WindowsInterfaces
( readBuf,
configureInput,
configureOutput
) where
#include "windows_cconv.h"
import Graphics.Vty.Platform.Windows.WindowsConsoleInput
import Graphics.Vty.Input.Events (Event(EvResize), InternalEvent(InputEvent))
import Codec.Binary.UTF8.String (encodeChar)
import Control.Concurrent (yield)
import Control.Concurrent.STM (TChan, atomically, writeTChan)
import Control.Monad (foldM, when)
import Data.Bits ((.|.), (.&.), shiftL)
import Data.Word (Word8)
import Foreign.Storable (Storable(..))
import GHC.Ptr (Ptr)
import System.IO (Handle)
import System.Win32.Types (DWORD, HANDLE, withHandleToHANDLE, iNVALID_HANDLE_VALUE)
import System.Win32.Console
import System.Win32.File hiding (copyFile)
foreign import ccall "windows.h WaitForSingleObject" c_WaitForSingleObject :: HANDLE -> DWORD -> IO DWORD
-- | Read the contents of the Windows input buffer. The contents are parsed and either written to
-- the TChan queue for Window size events, or written to the Word8 buffer for keyboard events and
-- VT sequences. Returns the # of bytes written to the Word8 buffer.
readBuf :: TChan InternalEvent -> Ptr WinConsoleInputEvent -> Handle -> Ptr Word8 -> Int -> IO Int
readBuf eventChannel inputEventPtr handle bufferPtr maxInputRecords = do
ret <- withHandleToHANDLE handle (`c_WaitForSingleObject` 500)
yield -- otherwise, the above foreign call causes the loop to never
-- respond to the killThread
if ret /= 0
then readBuf eventChannel inputEventPtr handle bufferPtr maxInputRecords
else readBuf' eventChannel inputEventPtr handle bufferPtr maxInputRecords
readBuf' :: TChan InternalEvent -> Ptr WinConsoleInputEvent -> Handle -> Ptr Word8 -> Int -> IO Int
readBuf' eventChannel inputEventPtr handle bufferPtr maxInputRecords = do
inputEvents <- readConsoleInput inputEventPtr maxInputRecords handle
(offset, _) <- foldM handleInputEvent (0, Nothing) inputEvents
return offset
where
handleInputEvent :: (Int, Maybe Int) -> WinConsoleInputEvent -> IO (Int, Maybe Int)
handleInputEvent (offset, mSurrogateVal) inputEvent = do
case inputEvent of
KeyEventRecordU (KeyEventRecordC isKeyDown _ _ _ cwChar _) -> do
-- Process the character if this is a 'key down' event,
-- AND the char is not NULL
if isKeyDown && cwChar /= 0
then processCWChar (offset, mSurrogateVal) $ fromEnum cwChar
else return (offset, Nothing)
WindowBufferSizeRecordU (WindowBufferSizeRecordC (COORD x y)) -> do
let resize = EvResize (fromIntegral x) (fromIntegral y)
atomically $ writeTChan eventChannel (InputEvent resize)
return (offset, Nothing)
_ -> return (offset, Nothing)
processCWChar :: (Int, Maybe Int) -> Int -> IO (Int, Maybe Int)
processCWChar (offset, Nothing) charVal = do
if isSurrogate charVal
then return (offset, Just charVal)
else encodeAndWriteToBuf offset charVal
where
isSurrogate :: Int -> Bool
isSurrogate c = 0xD800 <= c && c < 0xDC00
processCWChar (offset, Just surogateVal) charVal = do
let charVal' = (((surogateVal .&. 0x3FF) `shiftL` 10) .|. (charVal .&. 0x3FF)) + 0x10000
encodeAndWriteToBuf offset charVal'
encodeAndWriteToBuf :: Int -> Int -> IO (Int, Maybe Int)
encodeAndWriteToBuf offset charVal = do
let utf8Char = encodeChar $ toEnum charVal
mapM_ (\(w, offset') -> pokeElemOff bufferPtr (offset + offset') w) $ zip utf8Char [0..]
return (offset + length utf8Char, Nothing)
-- | Configure Windows to correctly handle input for a Vty application
configureInput :: Handle -> IO (IO (), IO ())
configureInput _ = do
inHandle <- configureHandle "CONIN$"
original <- getConsoleMode inHandle
let setMode = setConsoleMode inHandle $ eNABLE_VIRTUAL_TERMINAL_INPUT .|. eNABLE_EXTENDED_FLAGS
pure (setMode, setConsoleMode inHandle original)
-- | Configure Windows to correctly handle output for a Vty application
configureOutput :: Handle -> IO (IO ())
configureOutput _ = do
outHandle <- configureHandle "CONOUT$"
original <- getConsoleMode outHandle
setConsoleOutputCP 65001
setConsoleMode outHandle $ eNABLE_VIRTUAL_TERMINAL_PROCESSING .|. eNABLE_PROCESSED_OUTPUT
pure (setConsoleMode outHandle original)
configureHandle :: String -> IO HANDLE
configureHandle handleName = do
outHandle <- createFile handleName (gENERIC_WRITE .|. gENERIC_READ)
fILE_SHARE_WRITE Nothing oPEN_EXISTING 0 Nothing
when (outHandle == iNVALID_HANDLE_VALUE) $ fail $ "Unable to configure terminal for input/output with handle: " ++ handleName
return outHandle