flaw-window/Flaw/Window/Win32.hs
{-|
Module: Flaw.Window.Win32
Description: Win32 window framework.
License: MIT
-}
{-# LANGUAGE GADTs, PatternSynonyms, TypeFamilies #-}
{-# OPTIONS_GHC -fno-warn-missing-pattern-synonym-signatures #-}
module Flaw.Window.Win32
( Win32WindowSystem()
, Win32Window(..)
, runWin32WindowSystem
, createWin32Window
, createLayeredWin32Window
, updateLayeredWin32Window
, invokeWin32WindowSystem
, invokeWin32WindowSystem_
, addWin32WindowCallback
, chanWin32WindowMessages
, getWin32WindowClientSize_unsafe
) where
import Control.Concurrent
import Control.Concurrent.STM
import Control.Exception
import Control.Monad
import Control.Monad.Fix
import Data.IORef
import Data.Maybe
import qualified Data.Text as T
import qualified Data.Text.Foreign as T
import Foreign.C.String
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Marshal.Array
import Foreign.Ptr
import Foreign.Storable
import Flaw.Exception
import Flaw.Window
import Flaw.FFI.Win32
newtype Win32WindowSystem = Win32WindowSystem
{ wsHandle :: Ptr () -- ^ Opaque handle for C side.
}
data Win32Window = Win32Window
{ wWindowSystem :: !Win32WindowSystem
, wHandle :: {-# UNPACK #-} !HWND
, wCallback :: !(FunPtr WindowCallback)
, wUserCallbacksRef :: {-# UNPACK #-} !(IORef [WindowCallback])
, wMessagesChan :: {-# UNPACK #-} !(TChan (Word, WPARAM, LPARAM))
, wEventsChan :: {-# UNPACK #-} !(TChan WindowEvent)
}
instance Window Win32Window where
setWindowTitle Win32Window { wWindowSystem = ws, wHandle = hwnd } title = invokeWin32WindowSystem_ ws $
withCWString (T.unpack title) $ \titleCString ->
c_setWin32WindowTitle hwnd titleCString
getWindowClientSize window@Win32Window
{ wWindowSystem = ws
} = invokeWin32WindowSystem ws $ getWin32WindowClientSize_unsafe window
chanWindowEvents Win32Window
{ wEventsChan = eventsChan
} = dupTChan eventsChan
getWindowClipboardText Win32Window
{ wWindowSystem = ws
, wHandle = hwnd
} = invokeWin32WindowSystem ws $
alloca $ \memPtr -> alloca $ \lenPtr -> do
strPtr <- c_getClipboardTextBegin hwnd memPtr lenPtr
if strPtr /= nullPtr then do
len <- peek lenPtr
text <- T.fromPtr strPtr $ fromIntegral len
c_getClipboardTextEnd =<< peek memPtr
return text
else return T.empty
setWindowClipboardText Win32Window
{ wWindowSystem = ws
, wHandle = hwnd
} text = invokeWin32WindowSystem ws $ do
let len = T.lengthWord16 text
allocaArray (len + 1) $ \ptr -> do
T.unsafeCopyToPtr text ptr
pokeElemOff ptr len 0
c_setClipboardText hwnd ptr
setWindowMouseCursor Win32Window
{ wWindowSystem = ws
, wHandle = hwnd
} mouseCursor = invokeWin32WindowSystem_ ws $ c_setMouseCursor hwnd $ fromIntegral $ fromEnum mouseCursor
setWindowMouseLock Win32Window
{ wWindowSystem = ws
, wHandle = hwnd
} mouseLock = invokeWin32WindowSystem_ ws $ c_setMouseLock hwnd (if mouseLock then 1 else 0)
-- | Run Win32 window system.
runWin32WindowSystem :: MVar (Win32WindowSystem, IO ()) -> IO ()
runWin32WindowSystem resultVar = do
-- initialize window system, get handle
h <- c_initWin32WindowSystem
-- create shutdown var
shutdownVar <- newEmptyMVar
-- return result in var
let
ws = Win32WindowSystem
{ wsHandle = h
}
shutdown = do
-- send a message to stop window loop
invokeWin32WindowSystem_ ws c_stopWin32WindowSystem
-- wait for actual completion
readMVar shutdownVar
putMVar resultVar (ws, shutdown)
-- run window system
c_runWin32WindowSystem h
-- free resources
c_shutdownWin32WindowSystem h
-- signal end
putMVar shutdownVar ()
createWin32Window :: Win32WindowSystem -> T.Text -> Maybe (Int, Int) -> Maybe (Int, Int) -> IO (Win32Window, IO ())
createWin32Window ws title maybePosition maybeSize = internalCreateWin32Window ws title maybePosition maybeSize False
createLayeredWin32Window :: Win32WindowSystem -> T.Text -> Maybe (Int, Int) -> Maybe (Int, Int) -> IO (Win32Window, IO ())
createLayeredWin32Window ws title maybePosition maybeSize = internalCreateWin32Window ws title maybePosition maybeSize True
internalCreateWin32Window :: Win32WindowSystem -> T.Text -> Maybe (Int, Int) -> Maybe (Int, Int) -> Bool -> IO (Win32Window, IO ())
internalCreateWin32Window ws title maybePosition maybeSize layered = do
w <- invokeWin32WindowSystem ws $ mfix $ \w -> do
-- create callback
userCallbacksRef <- newIORef []
messagesChan <- newBroadcastTChanIO
eventsChan <- newBroadcastTChanIO
callback <- wrapWindowCallback $ \msg wParam lParam -> do
-- run sync callbacks
userCallbacks <- readIORef userCallbacksRef
forM_ userCallbacks $ \callback -> callback msg wParam lParam
-- dispatch raw message
atomically $ writeTChan messagesChan (msg, wParam, lParam)
-- dispatch event
let
maybeEvent = case msg of
0x0002 {- WM_DESTROY -} -> Just DestroyWindowEvent
0x0005 {- WM_SIZE -} -> Just $ ResizeWindowEvent (fromIntegral $ loWord lParam) (fromIntegral $ hiWord lParam)
0x0010 {- WM_CLOSE -} -> Just CloseWindowEvent
_ -> Nothing
case maybeEvent of
Just event -> atomically $ writeTChan eventsChan event
Nothing -> return ()
-- process some other messages
case msg of
0x0002 -> -- WM_DESTROY
-- free callback
freeHaskellFunPtr $ wCallback w
_ -> return ()
let
(left, top) = fromMaybe (CW_USEDEFAULT, CW_USEDEFAULT) maybePosition
(width, height) = fromMaybe (CW_USEDEFAULT, CW_USEDEFAULT) maybeSize
-- create window
hwnd <- withCWString (T.unpack title) $ \titleCString ->
c_createWin32Window (wsHandle ws) titleCString left top width height callback (if layered then 1 else 0)
when (hwnd == nullPtr) $ throwIO $ DescribeFirstException "cannot create Win32Window"
return Win32Window
{ wWindowSystem = ws
, wHandle = hwnd
, wCallback = callback
, wUserCallbacksRef = userCallbacksRef
, wMessagesChan = messagesChan
, wEventsChan = eventsChan
}
return (w, invokeWin32WindowSystem_ ws $ c_destroyWin32Window $ wHandle w)
updateLayeredWin32Window :: Win32Window -> IO ()
updateLayeredWin32Window w = invokeWin32WindowSystem_ (wWindowSystem w) $ c_updateLayeredWin32Window $ wHandle w
invokeWithMaybeResultVar :: Maybe (MVar (Either SomeException a)) -> Win32WindowSystem -> IO a -> IO ()
invokeWithMaybeResultVar maybeResultVar ws io = do
-- create callback
invokeCallback <- mfix $ \invokeCallback -> wrapInvokeCallback $ do
-- run IO with exception propagation
result <- try io
case maybeResultVar of
Just resultVar -> putMVar resultVar result
Nothing -> return ()
-- free fun ptr
freeHaskellFunPtr invokeCallback
-- do invoke
c_invokeWin32WindowSystem (wsHandle ws) invokeCallback
-- | Invoke function in window system thread.
-- Do not wait for the result.
invokeWin32WindowSystem_ :: Win32WindowSystem -> IO () -> IO ()
invokeWin32WindowSystem_ = invokeWithMaybeResultVar Nothing
-- | Invoke function in window system thread.
-- Wait for the result.
invokeWin32WindowSystem :: Win32WindowSystem -> IO a -> IO a
invokeWin32WindowSystem ws io = do
resultVar <- newEmptyMVar
invokeWithMaybeResultVar (Just resultVar) ws io
result <- takeMVar resultVar
case result of
Left e -> throwIO e
Right r -> return r
addWin32WindowCallback :: Win32Window -> WindowCallback -> IO ()
addWin32WindowCallback Win32Window
{ wWindowSystem = ws
, wUserCallbacksRef = userCallbacksRef
} callback = invokeWin32WindowSystem ws $ do
callbacks <- readIORef userCallbacksRef
writeIORef userCallbacksRef $ callback : callbacks
chanWin32WindowMessages :: Win32Window -> STM (TChan (Word, WPARAM, LPARAM))
chanWin32WindowMessages Win32Window
{ wMessagesChan = messagesChan
} = dupTChan messagesChan
-- | Get window size, no syncronization.
getWin32WindowClientSize_unsafe :: Win32Window -> IO (Int, Int)
getWin32WindowClientSize_unsafe Win32Window
{ wHandle = hwnd
} = alloca $ \widthPtr -> alloca $ \heightPtr -> do
c_getWin32WindowClientSize hwnd widthPtr heightPtr
width <- peek widthPtr
height <- peek heightPtr
return (fromIntegral width, fromIntegral height)
-- foreign imports
foreign import ccall safe "initWin32WindowSystem" c_initWin32WindowSystem :: IO (Ptr ())
foreign import ccall safe "runWin32WindowSystem" c_runWin32WindowSystem :: Ptr () -> IO ()
foreign import ccall safe "shutdownWin32WindowSystem" c_shutdownWin32WindowSystem :: Ptr () -> IO ()
foreign import ccall safe "stopWin32WindowSystem" c_stopWin32WindowSystem :: IO ()
foreign import ccall safe "invokeWin32WindowSystem" c_invokeWin32WindowSystem :: Ptr () -> FunPtr InvokeCallback -> IO ()
foreign import ccall safe "createWin32Window" c_createWin32Window
:: Ptr () -- window system handle
-> LPWSTR -- title
-> Int -> Int -- x y
-> Int -> Int -- width height
-> FunPtr WindowCallback -- callback
-> Int -- layered
-> IO HWND -- HWND
foreign import ccall unsafe "setWin32WindowTitle" c_setWin32WindowTitle :: HWND -> LPWSTR -> IO ()
foreign import ccall unsafe "getWin32WindowClientSize" c_getWin32WindowClientSize :: HWND -> Ptr CInt -> Ptr CInt -> IO ()
foreign import ccall safe "destroyWin32Window" c_destroyWin32Window :: HWND -> IO ()
foreign import ccall unsafe "getClipboardTextBegin" c_getClipboardTextBegin :: HWND -> Ptr HANDLE -> Ptr CInt -> IO (Ptr WCHAR)
foreign import ccall unsafe "getClipboardTextEnd" c_getClipboardTextEnd :: HANDLE -> IO ()
foreign import ccall unsafe "setClipboardText" c_setClipboardText :: HWND -> Ptr WCHAR -> IO ()
foreign import ccall unsafe "setWin32WindowMouseCursor" c_setMouseCursor :: HWND -> CInt -> IO ()
foreign import ccall unsafe "setWin32WindowMouseLock" c_setMouseLock :: HWND -> CInt -> IO ()
foreign import ccall safe "updateLayeredWin32Window" c_updateLayeredWin32Window :: HWND -> IO ()
foreign import ccall safe "getLayeredWin32WindowBitmapData" c_getLayeredWin32WindowBitmapData :: HWND -> Ptr (Ptr CUChar) -> Ptr CInt -> Ptr CInt -> Ptr CInt -> IO ()
-- wrappers
type InvokeCallback = IO ()
foreign import ccall "wrapper" wrapInvokeCallback :: InvokeCallback -> IO (FunPtr InvokeCallback)
type WindowCallback = Word -> WPARAM -> LPARAM -> IO ()
foreign import ccall "wrapper" wrapWindowCallback :: WindowCallback -> IO (FunPtr WindowCallback)
-- constants
pattern CW_USEDEFAULT = 0x80000000