packages feed

vty-windows-0.2.0.2: src/Graphics/Vty/Platform/Windows/WindowsConsoleInput.hsc

{-# LANGUAGE ForeignFunctionInterface, CPP #-}

-- | This module provides a function to obtain input events from the Win32 API
module Graphics.Vty.Platform.Windows.WindowsConsoleInput
    (KeyEventRecord(..),
     MouseEventRecord(..),
     WindowBufferSizeRecord(..),
     MenuEventRecord(..),
     FocusEventRecord(..),
     WinConsoleInputEvent(..),
     readConsoleInput) where

import Control.Monad (foldM)
import Foreign.C.Types ( CWchar(..) )
import Foreign.Marshal.Alloc (alloca)
import Foreign.Storable (Storable(..))
import GHC.Ptr ( Ptr )
import System.Win32.Console
import System.Win32.Types
import System.IO (Handle)

#include <windows.h>

foreign import ccall unsafe "windows.h ReadConsoleInputW" cReadConsoleInput :: HANDLE -> Ptr WinConsoleInputEvent -> DWORD -> LPDWORD -> IO BOOL

-- | This type represents a keyboard input event. The structure is documented here:
-- https://learn.microsoft.com/en-us/windows/console/key-event-record-str
data KeyEventRecord = KeyEventRecordC
  { keyDown :: BOOL
  , repeatCount :: WORD
  , virtualKeyCode :: WORD
  , virtualScanCode :: WORD
  , uChar :: CWchar
  , controlKeyStateK :: DWORD
  } deriving (Eq, Show)

-- | This type represents a mouse event. The structure is documented here:
-- https://learn.microsoft.com/en-us/windows/console/mouse-event-record-str
data MouseEventRecord = MouseEventRecordC
  { mousePosition :: COORD
  , buttonState :: DWORD
  , controlKeyStateM :: DWORD
  , eventFlags :: DWORD
  } deriving (Eq, Show)

-- | This type represents a window size change event. The structure is documented here:
-- https://learn.microsoft.com/en-us/windows/console/window-buffer-size-record-str
newtype WindowBufferSizeRecord = WindowBufferSizeRecordC
  { windowSize :: COORD
  } deriving (Eq, Show)

-- | This type represents a window menu event. (Current ignored by VTY). The structure
-- is documented here: https://learn.microsoft.com/en-us/windows/console/menu-event-record-str
newtype MenuEventRecord = MenuEventRecordC
  { commandId :: UINT
  } deriving (Eq, Show)

-- | This type represents a window focus change event. The structure is documented here:
-- https://learn.microsoft.com/en-us/windows/console/focus-event-record-str
newtype FocusEventRecord = FocusEventRecordC
  { setFocus :: BOOL
  } deriving (Eq, Show)

-- | Description of a Windows console input event. Documented here:
-- https://learn.microsoft.com/en-us/windows/console/input-record-str
data WinConsoleInputEvent =
    KeyEventRecordU KeyEventRecord
  | MouseEventRecordU MouseEventRecord
  | WindowBufferSizeRecordU WindowBufferSizeRecord
  | MenuEventRecordU MenuEventRecord
  | FocusEventRecordU FocusEventRecord
  deriving (Eq, Show)

-- | A wrapper for the ReadConsoleInput Win32 API as documented here:
-- https://learn.microsoft.com/en-us/windows/console/readconsoleinput
readConsoleInput :: Ptr WinConsoleInputEvent -> Int -> Handle -> IO [WinConsoleInputEvent]
readConsoleInput inputRecordPtr maxEvents handle = withHandleToHANDLE handle (readConsoleInput' inputRecordPtr maxEvents)

readConsoleInput' :: Ptr WinConsoleInputEvent -> Int -> HANDLE -> IO [WinConsoleInputEvent]
readConsoleInput' inputRecordPtr maxEvents handle' =
    alloca $ \numEventsReadPtr -> do
        poke numEventsReadPtr 1
        _ <- cReadConsoleInput handle' inputRecordPtr (fromIntegral maxEvents) numEventsReadPtr
        numEvents <- peek numEventsReadPtr
        foldM addNextRecord [] [(fromIntegral numEvents - 1), (fromIntegral numEvents - 2)..0]
    where
        addNextRecord inputRecords idx = do
            inputRecord <- peekElemOff inputRecordPtr idx
            return (inputRecord:inputRecords)

instance Storable KeyEventRecord where
    sizeOf = const #{size KEY_EVENT_RECORD}
    alignment _ = #alignment KEY_EVENT_RECORD
    poke buf input = do
        (#poke KEY_EVENT_RECORD, bKeyDown)          buf (keyDown input)
        (#poke KEY_EVENT_RECORD, wRepeatCount)      buf (repeatCount input)
        (#poke KEY_EVENT_RECORD, wVirtualKeyCode)   buf (virtualKeyCode input)
        (#poke KEY_EVENT_RECORD, wVirtualScanCode)  buf (virtualScanCode input)
        (#poke KEY_EVENT_RECORD, uChar)             buf (uChar input)
        (#poke KEY_EVENT_RECORD, dwControlKeyState) buf (controlKeyStateK input)
    peek buf = do
        keyDown'          <- (#peek KEY_EVENT_RECORD, bKeyDown) buf
        repeatCount'      <- (#peek KEY_EVENT_RECORD, wRepeatCount) buf
        virtualKeyCode'   <- (#peek KEY_EVENT_RECORD, wVirtualKeyCode) buf
        virtualScanCode'  <- (#peek KEY_EVENT_RECORD, wVirtualScanCode) buf
        uChar'            <- (#peek KEY_EVENT_RECORD, uChar) buf
        controlKeyStateK' <- (#peek KEY_EVENT_RECORD, dwControlKeyState) buf
        return $ KeyEventRecordC keyDown' repeatCount' virtualKeyCode' virtualScanCode' uChar' controlKeyStateK'

instance Storable MouseEventRecord where
    sizeOf = const #{size MOUSE_EVENT_RECORD}
    alignment _ = #alignment MOUSE_EVENT_RECORD
    poke buf input = do
        (#poke MOUSE_EVENT_RECORD, dwMousePosition)   buf (mousePosition input)
        (#poke MOUSE_EVENT_RECORD, dwButtonState)     buf (buttonState input)
        (#poke MOUSE_EVENT_RECORD, dwControlKeyState) buf (controlKeyStateM input)
        (#poke MOUSE_EVENT_RECORD, dwEventFlags)      buf (eventFlags input)
    peek buf = do
        mousePosition'    <- (#peek MOUSE_EVENT_RECORD, dwMousePosition) buf
        buttonState'      <- (#peek MOUSE_EVENT_RECORD, dwButtonState) buf
        controlKeyStateM' <- (#peek MOUSE_EVENT_RECORD, dwControlKeyState) buf
        eventFlags'       <- (#peek MOUSE_EVENT_RECORD, dwEventFlags) buf
        return $ MouseEventRecordC mousePosition' buttonState' controlKeyStateM' eventFlags'

instance Storable WindowBufferSizeRecord where
    sizeOf = const #{size WINDOW_BUFFER_SIZE_RECORD}
    alignment _ = #alignment WINDOW_BUFFER_SIZE_RECORD
    poke buf input = do
        (#poke WINDOW_BUFFER_SIZE_RECORD, dwSize) buf (windowSize input)
    peek buf = do
        size' <- (#peek WINDOW_BUFFER_SIZE_RECORD, dwSize) buf
        return $ WindowBufferSizeRecordC size'

instance Storable MenuEventRecord where
    sizeOf = const #{size MENU_EVENT_RECORD}
    alignment _ = #alignment MENU_EVENT_RECORD
    poke buf input = do
        (#poke MENU_EVENT_RECORD, dwCommandId) buf (commandId input)
    peek buf = do
        commandId' <- (#peek MENU_EVENT_RECORD, dwCommandId) buf
        return $ MenuEventRecordC commandId'

instance Storable FocusEventRecord where
    sizeOf = const #{size FOCUS_EVENT_RECORD}
    alignment _ = #alignment FOCUS_EVENT_RECORD
    poke buf input = do
        (#poke FOCUS_EVENT_RECORD, bSetFocus) buf (setFocus input)
    peek buf = do
        setFocus' <- (#peek FOCUS_EVENT_RECORD, bSetFocus) buf
        return $ FocusEventRecordC setFocus'

instance Storable WinConsoleInputEvent where
    sizeOf = const #{size INPUT_RECORD}
    alignment _ = #alignment INPUT_RECORD

    poke buf (KeyEventRecordU key) = do
        (#poke INPUT_RECORD, EventType) buf (#{const KEY_EVENT} :: WORD)
        (#poke INPUT_RECORD, Event) buf key
    poke buf (MouseEventRecordU mouse) = do
        (#poke INPUT_RECORD, EventType) buf (#{const MOUSE_EVENT} :: WORD)
        (#poke INPUT_RECORD, Event) buf mouse
    poke buf (WindowBufferSizeRecordU window) = do
        (#poke INPUT_RECORD, EventType) buf (#{const WINDOW_BUFFER_SIZE_EVENT} :: WORD)
        (#poke INPUT_RECORD, Event) buf window
    poke buf (MenuEventRecordU menu) = do
        (#poke INPUT_RECORD, EventType) buf (#{const MENU_EVENT} :: WORD)
        (#poke INPUT_RECORD, Event) buf menu
    poke buf (FocusEventRecordU focus) = do
        (#poke INPUT_RECORD, EventType) buf (#{const FOCUS_EVENT} :: WORD)
        (#poke INPUT_RECORD, Event) buf focus

    peek buf = do
        event <- (#peek INPUT_RECORD, EventType) buf :: IO WORD
        case event of
          #{const KEY_EVENT} ->
              KeyEventRecordU `fmap` (#peek INPUT_RECORD, Event) buf
          #{const MOUSE_EVENT} ->
              MouseEventRecordU `fmap` (#peek INPUT_RECORD, Event) buf
          #{const WINDOW_BUFFER_SIZE_EVENT} ->
              WindowBufferSizeRecordU `fmap` (#peek INPUT_RECORD, Event) buf
          #{const MENU_EVENT} ->
              MenuEventRecordU `fmap` (#peek INPUT_RECORD, Event) buf
          #{const FOCUS_EVENT} ->
              FocusEventRecordU `fmap` (#peek INPUT_RECORD, Event) buf
          _ -> error $ "Unknown input event type " ++ show event