summaryrefslogtreecommitdiff
path: root/wayland/Wayland/Server/Client.hs
blob: e7325c9560549be62bbd4717a02d95815363b380 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- |
-- Module     : Wayland.Server.Client
-- Description: Low-level API for wrapping Wayland client handles.

module Wayland.Server.Client
  ( -- * Handles
    Client
  , ClientMessage(..)
  , createClient
  , unwrapClient
  , wrapClient
  , -- * Data
    ClientData
  , ClientDataReader
  , newClientData
  , destroyClientData
  ) where

import Data.Hashable (Hashable)
import Control.Monad.Trans (MonadIO(..))
import Control.Monad.Reader (ReaderT, asks)
import Foreign (newStablePtr, castStablePtrToPtr, FunPtr, freeHaskellFunPtr, Ptr, castPtrToStablePtr, deRefStablePtr, freeStablePtr)
import Wayland.Server.Types (Handle, createHandle, Message(..), invalidateHandle, MessageClass, resolveHandle)
import Wayland.Server.System

-- | A handle to a Wayland client.
--
-- The handle remains valid and can be used for equality checks even after the underlying libwayland client was
-- destroyed. However, actions accessing the client will fail.
newtype Client = Client (Handle WLClient)
  deriving (Show, Eq, Ord, Hashable)

-- | Messages informing the server that a client has been created/connected to the server or destroyed.
data ClientMessage
  = ClientCreated   Client
  | ClientDestroyed Client
  deriving (Show, Eq)

-- | Client-related data.
newtype ClientData = ClientData { dataDestroyFunc :: FunPtr WLUserDataDestroyFunc }

-- | A reader monad providing an instance of 'ClientData'.
type ClientDataReader = ReaderT ClientData IO

instance MessageClass ClientMessage

-- | Initialize client-related data.
newClientData
  :: (Message -> IO ()) -- ^ Message handler used to dispatch messages when clients were destroyed.
  -> IO ClientData      -- ^ Action creating the data.
newClientData handler = do
  destroyFunc <- wrapWLUserDataDestroyFunc (destroyClient handler)
  pure $ ClientData { dataDestroyFunc = destroyFunc }

-- | Destroy client-related data, freeing all acquired resources.
--
-- This should only be done after no wl_client instances are alive as it will free memory used by them.
destroyClientData :: ClientData -> IO ()
destroyClientData c = do
  freeHaskellFunPtr (dataDestroyFunc c)

-- | Create a wrapper for a native wl_client instance.
--
-- This will associate user data with the client and register a destroy function which dispatches a message.
createClient :: WLClient -> ClientDataReader Client
createClient c = do
  destroyFunc <- asks dataDestroyFunc
  liftIO $ do
    handle <- createHandle c
    let client = Client handle
    clientPtr <- newStablePtr client
    wlClientSetUserData c (castStablePtrToPtr clientPtr) destroyFunc
    pure client

-- | Callback used when a client was destroyed, disassociating any data and dispatching a message to the message loop.
destroyClient :: (Message -> IO ()) -> Ptr () -> IO ()
destroyClient handler c = do
  let clientPtr = castPtrToStablePtr c
  client@(Client handle) <- deRefStablePtr clientPtr
  invalidateHandle handle
  freeStablePtr clientPtr
  handler $ Message $ ClientDestroyed client

-- | Get the underlying Wayland client wrapped by a 'Client'.
--
-- This function may raise an error if the underlying client is no longer alive.
unwrapClient :: Client -> IO WLClient
unwrapClient (Client handle) = resolveHandle handle

-- | Get the existing wrapper for a Wayland client.
--
-- The client wrapper is stored as a StablePtr in the client's userdata.
wrapClient :: WLClient -> IO Client
wrapClient c = do
  clientPtr <- castPtrToStablePtr <$> wlClientGetUserData c
  deRefStablePtr clientPtr