packages feed

barbly-0.1.0.0: src/AppKit.hs

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ExtendedDefaultRules #-}
module AppKit where

import Control.Exception (finally, handle, SomeException(..))
import Control.Monad.Cont
import Data.ByteString (useAsCString)
import Data.Text (Text)
import qualified Data.Text.Encoding as Text
import Foreign hiding (newForeignPtr)
import Foreign.C

newtype NSStatusItem = NSStatusItem (Ptr ())
newtype NSMenu       = NSMenu (Ptr ())
newtype NSMenuItem   = NSMenuItem (Ptr ())

foreign import ccall "addMenuItem" addMenuItem :: NSMenu -> NSMenuItem -> IO ()
foreign import ccall "assignAction" assignAction' :: NSMenuItem -> FunPtr (IO ()) -> IO ()
foreign import ccall "assignSubMenu" assignSubMenu :: NSMenuItem -> NSMenu -> IO ()
foreign import ccall "initApp" initApp :: IO ()
foreign import ccall "newMenu" newMenu' :: CString -> IO NSMenu
foreign import ccall "newMenuItem" newMenuItem' :: CString -> IO NSMenuItem
foreign import ccall "newSeparator" newSeparator' :: IO NSMenuItem
foreign import ccall "newStatusItem" newStatusItem' :: IO NSStatusItem
foreign import ccall "release" release :: Ptr () -> IO ()
foreign import ccall "runApp" runApp' :: FunPtr (IO ()) -> IO ()
foreign import ccall "sendEvent" sendEvent :: IO ()
foreign import ccall "setStatusItemMenu" setStatusItemMenu :: NSStatusItem  -> NSMenu -> IO ()
foreign import ccall "setTitle" setTitle' :: NSStatusItem -> CString -> IO ()
foreign import ccall "wrapper" wrap :: IO () -> IO (FunPtr (IO ()))

foreign export ccall freeHaskellFunPtr :: FunPtr  (IO ()) -> IO ()

runApp :: IO () -> IO ()
runApp p = do
    p' <- wrap p
    runApp' p'

assignAction :: NSMenuItem -> IO () -> IO ()
assignAction mi act = do
    -- We simply handle any exceptions by printing them. No need
    -- to take down the whole process.
    ioact <- wrap (handle (\s@SomeException{} -> print s) act)
    liftIO $ assignAction' mi ioact

newStatusItem :: ContT r IO NSStatusItem
newStatusItem = ContT $ \go -> do
    si@(NSStatusItem p) <- newStatusItem'
    go si `finally` release p

newMenuItem :: Text -> ContT r IO NSMenuItem
newMenuItem s = ContT $ \go -> do
    useAsCString (Text.encodeUtf8 s) $ \cs -> do
        mi@(NSMenuItem p) <- newMenuItem' cs
        go mi `finally` release p

newMenu :: Text -> ContT r IO NSMenu
newMenu s = ContT $ \go -> do
    m@(NSMenu p) <- useAsCString (Text.encodeUtf8 s) newMenu'
    go m `finally` release p

setTitle :: NSStatusItem -> Text -> IO ()
setTitle si s = useAsCString (Text.encodeUtf8 s) $ setTitle' si

newSeparator :: ContT r IO NSMenuItem
newSeparator = ContT $ \go -> do
    mi <- newSeparator'
    go mi
    -- Does not need to be released, it's a constant.