{-# LANGUAGE FlexibleContexts, Rank2Types, KindSignatures, NoMonomorphismRestriction, ExistentialQuantification, NamedFieldPuns, RecordWildCards #-} {-# OPTIONS -Wall #-} -- | For a higher-level API for textual OSDs using /Pango/, use "Graphics.Aosd.Pango". module Graphics.Aosd( -- * Renderers AosdRenderer(..), GeneralRenderer(..), -- -- ** Simple combinators -- HCatRenderer(..), VCatRenderer(..), -- * Options AosdOptions(..), Transparency(..), Position(..), XClassHint(..), defaultOpts, -- * Construction/destruction AosdPtr,aosdNew,aosdDestroy,withAosd, -- * Displaying aosdFlash, FlashDurations(..), symDurations, -- ** Low-level operations reconfigure, aosdRender, aosdShow, aosdHide, aosdLoopOnce, aosdLoopFor, -- * Diagnostics debugRenderer, -- * Reexports module Graphics.Rendering.Cairo, Rectangle(..), CInt, CUInt ) where import Control.Concurrent.MVar import Control.Monad.Trans.Reader import Foreign.C import Foreign.Ptr import Foreign.StablePtr import Foreign.Storable import Graphics.Aosd.AOSD_H import Graphics.Aosd.Options import Graphics.Aosd.Renderer import Graphics.Aosd.Util import Graphics.Aosd.CallbackUtil import Graphics.Aosd.XUtil import Graphics.Rendering.Cairo import Graphics.Rendering.Cairo.Internal(runRender,Cairo) import Graphics.Rendering.Pango.Enums import System.IO.Unsafe import Graphics.X11.Xlib(Display,openDisplay,closeDisplay) import Control.Exception import Data.Maybe -- toAosdCoordinate :: Position -> C'AosdCoordinate -- toAosdCoordinate Min = c'COORDINATE_MINIMUM -- toAosdCoordinate Center = c'COORDINATE_CENTER -- toAosdCoordinate Max = c'COORDINATE_MAXIMUM c'aosd_new_debug :: String -> IO (Ptr C'Aosd) c'aosd_new_debug cxt = do p <- c'aosd_new putDebugMemory cxt ("c'aosd_new ==> "++show p) return p c'aosd_destroy_debug :: String -> Ptr C'Aosd -> IO () c'aosd_destroy_debug cxt p = do putDebugMemory cxt ("c'aosd_destroy "++show p) c'aosd_destroy p type MouseEventHandler = AosdPtr -> C'AosdMouseEvent -> IO () {- Position calculation comments: (These comments only look at the x dimension; the other is analogous) Consider the mapping "screenx" from grRender x coordinates to screen x coordinates Since we translate grRender by -(li,ti), we have: screenx x = windowLeft + x - li If xPos is Min, we want: screenx lp = 0 <=> windowLeft + lp - li = 0 <=> windowLeft = li - lp If xPos is Center, we want: screenx (lp + wp/2) = screenWidth/2 <=> windowLeft + (lp + wp/2) - li = screenWidth/2 <=> windowLeft = li - lp + (screenWidth - wp)/2 If xPos is Max, we want: screenx (lp+wp) = screenWidth <=> windowLeft + (lp+wp) - li = screenWidth <=> windowLeft = li - lp + screenWidth - wp -} -- | Must *NOT* access the aosdStructOwnedDataVar of the AosdPtr argument (-> deadlock). reconfigure0 :: (AosdRenderer renderer) => AosdOptions -> renderer -> AosdPtr -> IO AosdStructOwnedData reconfigure0 AosdOptions{..} renderer aosd@AosdPtr {unAosdPtr=ptr, display} = do GeneralRenderer{..} <- toGeneralRenderer renderer ScreenSize{..} <- getScreenSize display let -- l=Left, t=Top, w=Width, h=Height Rectangle li ti wi hi = grInkExtent Rectangle lp tp wp hp = grPositioningExtent calculateOffsetAdjustment pos min_ink min_positioning size_positioning size_screen = fromIntegral $ case pos of Min -> min_ink - min_positioning Center -> min_ink - min_positioning + div (size_screen - size_positioning) 2 Max -> min_ink - min_positioning + size_screen - size_positioning windowLeft = calculateOffsetAdjustment xPos li lp wp screenWidth + fst offset windowTop = calculateOffsetAdjustment yPos ti tp hp screenHeight + snd offset windowWidth = fromIntegral wi windowHeight = fromIntegral hi finalRenderer = do translate (fi . negate $ li) (fi . negate $ ti) grRender maybeDo (setClassHint ptr) classHint maybeDo (setHideUponMouseEvent ptr) hideUponMouseEvent rendererPtr <- setRenderer ptr finalRenderer handlerPtr <- traverseMaybe (setMouseEventCB aosd) mouseEventCB maybeDo (c'aosd_set_transparency ptr . toAosdTransparency) transparency c'aosd_set_geometry ptr windowLeft windowTop windowWidth windowHeight return (AosdStructOwnedData rendererPtr handlerPtr) -- | Does *NOT* free the old handler setRenderer :: Ptr C'Aosd -> Render () -> IO (StablePtr (Cairo -> IO ())) setRenderer ptr renderer = tunnelCallback theC'AosdRenderer (c'aosd_set_renderer ptr) f where f = runReaderT . runRender $ renderer {-# NOINLINE theC'AosdRenderer #-} theC'AosdRenderer :: UniversalCallback Cairo theC'AosdRenderer = unsafePerformIO $ mkUniversalCallback mk'AosdRenderer {-# NOINLINE theC'AosdMouseEventCb #-} theC'AosdMouseEventCb :: UniversalCallback (Ptr C'AosdMouseEvent) theC'AosdMouseEventCb = unsafePerformIO $ mkUniversalCallback mk'AosdMouseEventCb setClassHint :: Ptr C'Aosd -> XClassHint -> IO () setClassHint a XClassHint{ resName, resClass } = withCString resName (\resName' -> withCString resClass (\resClass' -> c'aosd_set_names a resName' resClass')) setHideUponMouseEvent :: Ptr C'Aosd -> Bool -> IO () setHideUponMouseEvent a b = c'aosd_set_hide_upon_mouse_event a (if b then 1 else 0) -- | Does *NOT* free the old handler. -- Must *NOT* access the aosdStructOwnedDataVar of the AosdPtr argument (-> deadlock). setMouseEventCB :: AosdPtr -> MouseEventHandler -> IO (StablePtr (Ptr C'AosdMouseEvent -> IO ())) setMouseEventCB aosd@AosdPtr {unAosdPtr=ptr} handler = tunnelCallback theC'AosdMouseEventCb (c'aosd_set_mouse_event_cb ptr) f where f eventp = do event <- peek eventp handler aosd event -- | Main high-level displayer. Blocks. aosdFlash :: AosdPtr -> FlashDurations -> IO () aosdFlash a FlashDurations{..} = wrapAosd (\p -> c'aosd_flash p inMillis fullMillis outMillis) a data AosdPtr = AosdPtr { unAosdPtr :: !(Ptr C'Aosd) -- We only keep this around for deallocating , aosdStructOwnedDataVar :: !(MVar (Maybe AosdStructOwnedData)) , display :: Display } aosdNew0 :: IO AosdPtr aosdNew0 = do display <- openDisplay "" unAosdPtr <- c'aosd_new_debug "aosdNew" aosdStructOwnedDataVar <- newMVar Nothing return AosdPtr {unAosdPtr,aosdStructOwnedDataVar,display} aosdNew :: (AosdRenderer renderer) => AosdOptions -> renderer -> IO AosdPtr aosdNew opts r = do aosd <- aosdNew0 z <- reconfigure0 opts r aosd modifyMVar_ (aosdStructOwnedDataVar aosd) (\x -> assert (isNothing x) $ return (Just z)) return aosd reconfigure :: (AosdRenderer renderer) => AosdOptions -> renderer -> AosdPtr -> IO () reconfigure opts r aosd@AosdPtr {aosdStructOwnedDataVar} = modifyMVar_ aosdStructOwnedDataVar (\zOld -> do zNew <- reconfigure0 opts r aosd maybeDo (freeAosdStructOwnedData "reconfigure") zOld return (Just zNew)) wrapAosd :: (Ptr C'Aosd -> c) -> AosdPtr -> c wrapAosd f = f . unAosdPtr aosdRender :: AosdPtr -> IO () aosdRender = wrapAosd c'aosd_render aosdShow :: AosdPtr -> IO () aosdShow = wrapAosd c'aosd_show aosdHide :: AosdPtr -> IO () aosdHide = wrapAosd c'aosd_hide aosdLoopOnce :: AosdPtr -> IO () aosdLoopOnce = wrapAosd c'aosd_loop_once aosdLoopFor :: AosdPtr -> CUInt -- ^ Time in milliseconds. -> IO () aosdLoopFor a millis = wrapAosd (flip c'aosd_loop_for millis) a data AosdStructOwnedData = AosdStructOwnedData !(StablePtr (Cairo -> IO ())) !(Maybe (StablePtr (Ptr C'AosdMouseEvent -> IO()))) freeAosdStructOwnedData :: String -> AosdStructOwnedData -> IO () freeAosdStructOwnedData cxt (AosdStructOwnedData sp_r sp_h) = do freeStablePtrDebug cxt "renderer" sp_r maybeDo (freeStablePtrDebug cxt "mouse event handler") sp_h aosdDestroy :: AosdPtr -> IO () aosdDestroy AosdPtr {unAosdPtr, aosdStructOwnedDataVar, display} = modifyMVar_ aosdStructOwnedDataVar $ \z -> do c'aosd_destroy_debug "aosdDestroy" unAosdPtr maybeDo (freeAosdStructOwnedData "aosdDestroy") z closeDisplay display return Nothing -- | 'aosdNew'/'aosdDestroy' bracket. Leaking the 'AosdPtr' out of the third argument leads to undefined behaviour. withAosd :: AosdRenderer renderer => AosdOptions -> renderer -> (AosdPtr -> IO c) -> IO c withAosd opts ren = bracket (aosdNew opts ren) aosdDestroy