summaryrefslogtreecommitdiff
path: root/wayland/Wayland/Server/Object.hs
blob: b13e36f09d9ead1d348fce8c8a2af3a0aa4cec01 (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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ExistentialQuantification #-}

-- |
-- Module     : Wayland.Server.Object
-- Description: Low-level API for wrapping Wayland object/resource handles.
--
-- Objects are represented as wl_resources in libwayland. This module wraps a resource, providing an opaque handle to it
-- that is valid even after the underlying resource is destroyed. This is useful for storing the resource handle in a
-- map and associating other data with it, cleaning it up only after its destruction.

module Wayland.Server.Object
  ( -- * Handles
    Object(..)
  , AnyObject
  , NewObject
  , createNewObject
  , RequestClass
  , EventClass
  , ArgumentMarshaller(..)
  , ArgumentDemarshaller(..)
  , -- * State
    ObjectCache
  , ObjectCacheState
  , newObjectCache
  , destroyObjectCache
  , resolveTypeInterface
  , -- * Lifecycle
    unwrapResource
  , wrapResource
  , createResource
  , sendResourceEvent
  ) where

import System.Posix.Types (Fd(..))
import Data.Kind (Type)
import Data.Hashable (Hashable(..))
import Data.IORef (readIORef, newIORef, IORef, writeIORef)
import Data.Typeable (Proxy(..), TypeRep, typeRep, Typeable)
import Data.ByteString (ByteString, useAsCStringLen, packCStringLen)
import Foreign
import Foreign.C (withCString, peekCString, CInt)
import Lens.Micro.Mtl (use, (%=), zoom)
import Lens.Micro.TH (makeLenses)
import Control.Monad.State.Strict (StateT(..), MonadState(..))
import Control.Monad.Cont (ContT(..))
import Control.Monad.Trans (MonadIO(..), MonadTrans(..))
import Control.Monad.Reader (ReaderT (..), MonadReader(..))
import Wayland.Server.System
import Wayland.Server.Interface (MessageDesc(..), InterfaceDesc(..), InterfaceCache, newInterfaceCache, destroyInterfaceCache, resolveInterface)
import Wayland.Server.Client (Client, unwrapClient, wrapClient)
import Wayland.Server.Types (Handle, invalidateHandle, resolveHandle, createHandle, MessageClass, Message(..))
import qualified Data.Map as M

-- | The primary data type for typed object handles.
--
-- An instance of the Object class wraps an opaque object handle ('AnyObject') and additionally provides information
-- about the object's interface.
class (Typeable o, RequestClass (ObjectRequest o), EventClass (ObjectEvent o)) => Object o where
  -- | Associated type for requests sent to object instances.
  type ObjectRequest o :: Type
  -- | Associated type for events emitted by object instances.
  type ObjectEvent o :: Type

  -- | Unwrap this typed object into an opaque object handle.
  toAny :: o -> AnyObject
  -- | Create a typed object from an opaque object handle.
  fromAny :: AnyObject -> o

  -- | Provide an interface description for objects of this type.
  describeInterface :: Proxy o -> InterfaceDesc
  -- | Provide a message description for an event instance.
  eventSignature :: Proxy o -> ObjectEvent o -> MessageDesc
  -- | Provide a message description for a request instance.
  requestSignature :: Proxy o -> ObjectRequest o -> MessageDesc

  -- | Marshal the arguments provided in the event instance into an argument marshaller.
  --
  -- This should marshal exactly @length . msgArguments . eventSignature@ arguments for the passed event and match the
  -- types described in the signature. After that, the opcode for the event should be returned.
  marshalEvent
    :: ArgumentMarshaller m -- ^ Monad used to write marshalled arguments.
    => Proxy o              -- ^ Proxy for the object type this event is referring to.
    -> ObjectEvent o        -- ^ The event that should be marshalled.
    -> m Int                -- ^ Argument marshaller returning the event's opcode.

  -- | Demarshal the arguments provided into a request instance using an argument demarshaller.
  --
  -- This should demarshal exactly @length . msgArguments . requestSignature@ arguments using the demarshaller interface
  -- and match the types described in the signature.
  demarshalRequest
    :: ArgumentDemarshaller m -- ^ Monad used to extract marshalled arguments.
    => o                      -- ^ The instance this request is referring to.
    -> Int                    -- ^ The opcode of the request.
    -> m (ObjectRequest o)    -- ^ Argument demarshaller returning the demarshalled request.

-- | A request for an object sent by a client.
--
-- The request usually includes the object it is referring to, as well as arguments provided by the client. It is
-- processed as part of the server's message loop.
class MessageClass r => RequestClass r where

-- | An event that can be emitted on an object by the server.
--
-- The event stores the arguments passed to the client.
class MessageClass e => EventClass e where

-- | A monad providing an environment for marshalling a fixed number of arguments of various types.
--
-- For libwayland, the arguments are stored into wl_arguments. It is considered a programmer error to write more
-- arguments than expected by an event or different argument types than the ones specified.
class Monad m => ArgumentMarshaller m where
  marshalInt         :: Int                     -> m ()
  marshalUInt        :: Word                    -> m ()
  marshalFixed       :: Float                   -> m ()
  marshalString      :: String                  -> m ()
  marshalMaybeString :: Maybe String            -> m ()
  marshalObject      :: Object o => o           -> m ()
  marshalMaybeObject :: Object o => Maybe o     -> m ()
  marshalNewID       :: Object o => NewObject o -> m ()
  marshalArray       :: ByteString              -> m ()
  marshalFD          :: Fd                      -> m ()

-- | A monad providing an environment for reading a fixed number of arguments of various types.
--
-- For libwayland, the arguments are read from wl_arguments. It is considered a programmer error to read more arguments
-- than expected by a request or different argument types than the ones specified.
class Monad m => ArgumentDemarshaller m where
  demarshalInt         :: m Int
  demarshalUInt        :: m Word
  demarshalFixed       :: m Float
  demarshalString      :: m String
  demarshalMaybeString :: m (Maybe String)
  demarshalObject      :: Object o => m o
  demarshalMaybeObject :: Object o => m (Maybe o)
  demarshalNewID       :: Object o => m (NewObject o)
  demarshalArray       :: m ByteString
  demarshalFD          :: m Fd

-- | An opaque handle to a Wayland object.
--
-- The handle remains valid and can be used for equality checks even after the underlying libwayland resource was
-- destroyed. However, actions using the object (like sending events to it) will fail.
--
-- In addition, a custom type-erased cleanup action is stored which is invoked once the resource is destroyed.
data AnyObject
  = AnyObject { objHandle      :: Handle WLResource  -- ^ The underlying resource.
              , objDestroyHook :: AnyObject -> IO () -- ^ Function executed after the object was destroyed.
              }

-- | Handle to an object which has not yet been created but has an ID assigned by the client.
--
-- This handle can be used to create an object. It is a programmer error to use it more than once.
data NewObject o
  = NewObject { newClient  :: !Client            -- ^ The client this object is associated with.
              , newID      :: !Word32            -- ^ Object ID of the new object.
              , newObject  :: !(IORef (Maybe o)) -- ^ Reference to the object, assigned when it is created.
              }

-- | Data related to an object type.
data TypeInfo
  = TypeInfo { typeInterface    :: !(Ptr WLInterface)         -- ^ Reference to the libwayland interface for the type.
             , typeDispatchFunc :: !(FunPtr WLDispatcherFunc) -- ^ The dispatcher function handling requests for the type.
             }

-- | State used to cache object-related data.
data ObjectCache
  = ObjectCache { _cacheTypeInfo    :: !(M.Map TypeRep TypeInfo)       -- ^ Type-specific information.
                , _cacheDestroyFunc :: !(FunPtr WLResourceDestroyFunc) -- ^ Global destroy function for all objects.
                , _cacheInterfaces  :: !InterfaceCache                 -- ^ Cache for native interfaces.
                }

-- | A state monad providing an instance of 'ObjectCache'.
type ObjectCacheState = StateT ObjectCache IO

-- | State used when iterating over a buffer of wl_arguments.
newtype ArgumentIterator = ArgumentIterator (Ptr WLArgument)

-- | Monad used for writing arguments into a wl_argument buffer.
--
-- The monad wraps a ContT monad to allow using alloca when wrapping arguments.
newtype ArgumentWriter r a = ArgumentWriter { runArgumentWriter :: StateT ArgumentIterator (ContT r IO) a }
  deriving (Functor, Applicative, Monad, MonadState ArgumentIterator, MonadIO)

-- | Monad used for reading arguments from a wl_argument buffer.
newtype ArgumentReader a = ArgumentReader { runArgumentReader :: StateT ArgumentIterator (ReaderT Client IO) a }
  deriving (Functor, Applicative, Monad, MonadState ArgumentIterator, MonadIO, MonadReader Client)

makeLenses ''ObjectCache

instance Eq AnyObject where
  a == b = objHandle a == objHandle b

instance Ord AnyObject where
  a <= b = objHandle a <= objHandle b

instance Hashable AnyObject where
  hashWithSalt s o = hashWithSalt s (objHandle o)

instance Show AnyObject where
  show o = show (objHandle o)

instance Show (NewObject o) where
  show o = "NewObject " ++ show (newClient o) ++ " " ++ show (newID o)

instance Eq (NewObject o) where
  a == b = newObject a == newObject b

-- | Create a handle to an object that has yet to be created.
createNewObject :: Client -> Word32 -> IO (NewObject o)
createNewObject client objID = do
  obj <- newIORef Nothing
  pure $ NewObject { newClient  = client
                   , newID      = objID
                   , newObject  = obj
                   }

-- | Initialize an object cache.
newObjectCache :: IO ObjectCache
newObjectCache = do
  destroyFunc <- wrapWLResourceDestroyFunc destroyResource
  interfaces  <- newInterfaceCache
  pure $ ObjectCache { _cacheTypeInfo    = M.empty
                     , _cacheDestroyFunc = destroyFunc
                     , _cacheInterfaces  = interfaces
                     }

-- | Destroy an object cache, freeing all acquired resources.
--
-- This should only be done after no wl_resource instances are alive as it will free memory used by them.
destroyObjectCache :: ObjectCache -> IO ()
destroyObjectCache c = do
  freeHaskellFunPtr (_cacheDestroyFunc c)
  mapM_ (freeHaskellFunPtr . typeDispatchFunc) (_cacheTypeInfo c)
  destroyInterfaceCache (_cacheInterfaces c)

-- | Get the underlying Wayland resource wrapped by a typed object.
--
-- This function may raise an error if the underlying resource is no longer alive.
unwrapResource :: Object o => o -> IO WLResource
unwrapResource o = resolveHandle (objHandle (toAny o))

-- | Get the typed object wrapping a Wayland resource.
--
-- The object wrapper is stored as a StablePtr in the resource's userdata.
wrapResource :: Object o => WLResource -> IO o
wrapResource o = do
  objPtr <- castPtrToStablePtr <$> wlResourceGetUserData o
  deRefStablePtr objPtr

-- | Handle the destruction of a Wayland resource.
--
-- This will set the object handle's reference to a nullPtr and call the destroy hook.
destroyResource :: WLResource -> IO ()
destroyResource res = do
  objPtr <- castPtrToStablePtr <$> wlResourceGetUserData res
  obj <- deRefStablePtr objPtr
  invalidateHandle (objHandle obj)
  freeStablePtr objPtr
  objDestroyHook obj obj

-- | Create a new Wayland resource and associate it with a typed object wrapper.
--
-- This not only creates the object but also attaches a dispatch function to it and updates the destroy callback.
createResource
  :: forall o destroyMsg. Object o
  => MessageClass destroyMsg
  => (Message -> IO ()) -- ^ Message handler used for dispatching object requests and the destroy hook.
  -> NewObject o        -- ^ Handle describing the object to be created
  -> (o -> destroyMsg)  -- ^ Function creating a message dispatched after the object was destroyed.
  -> ObjectCacheState o -- ^ State monad returning the newly created object.
createResource handler newObj destroyHook = do
  existing <- liftIO $ readIORef (newObject newObj)
  case existing of
    Just _  -> error "The NewObject instance was already used to create an object."
    Nothing -> do typeInfo <- resolveTypeInfo (Proxy :: Proxy o) handler
                  destroyFunc <- use cacheDestroyFunc
                  liftIO $ do
                    let version = Wayland.Server.Interface.ifVersion (describeInterface (Proxy :: Proxy o))
                    client <- unwrapClient (newClient newObj)
                    res <- wlResourceCreate client (typeInterface typeInfo) (fromIntegral version) (newID newObj)
                    handle <- createHandle res
                    let obj = fromAny $ AnyObject { objHandle      = handle
                                                  , objDestroyHook = handler . Message . destroyHook . fromAny
                                                  }
                    objPtr <- newStablePtr obj
                    wlResourceSetDispatcher res (typeDispatchFunc typeInfo) nullPtr (castStablePtrToPtr objPtr) destroyFunc
                    writeIORef (newObject newObj) (Just obj)
                    pure obj

-- | Send an event for a typed object.
--
-- This function may raise an error if the underlying resource is no longer alive.
sendResourceEvent :: forall o. Object o => o -> ObjectEvent o -> IO ()
sendResourceEvent o e = do
  let paramCount = length $ msgArguments (eventSignature (Proxy :: Proxy o) e)
  res <- unwrapResource o
  allocaArray0 paramCount $ \buffer ->
    runContT (runStateT (runArgumentWriter (marshalEvent (Proxy :: Proxy o) e)) (ArgumentIterator buffer)) $ \(opcode, _) ->
      wlResourcePostEventArray res (fromIntegral opcode) buffer

-- | Request handler for a typed object.
--
-- This provides a WLDispatcherFunc for an object type.
dispatchResourceRequest :: forall o. Object o => Proxy o -> (Message -> IO ()) -> Ptr () -> WLResource -> Word32 -> Ptr WLMessage -> Ptr WLArgument -> IO CInt
dispatchResourceRequest _ handler _ res opcode _ args = do
  obj :: o <- wrapResource res
  client <- wlResourceGetClient res >>= wrapClient
  (req, _) <- runReaderT (runStateT (runArgumentReader (demarshalRequest obj (fromIntegral opcode))) (ArgumentIterator args)) client
  handler $ Message req
  pure 0

-- | Resolve a type information for an object type.
--
-- This will first attempt to use an existing instance from the 'ObjectCache'. If there is none, it will create a new
-- structure storing the wl_interface pointer and allocating a function pointer to the typed dispatcher function.
resolveTypeInfo
  :: Object o
  => Proxy o                   -- ^ Proxy for the object type to resolve.
  -> (Message -> IO ())        -- ^ Message handler used to dispatch requests in the type's dispatch func.
  -> ObjectCacheState TypeInfo -- ^ State monad wrapping the type information.
resolveTypeInfo proxy handler = do
  existing <- use cacheTypeInfo
  case M.lookup (typeRep proxy) existing of
    Just info -> pure info
    Nothing   -> do let desc = describeInterface proxy
                    interface    <- zoom cacheInterfaces $ resolveInterface desc
                    dispatchFunc <- liftIO $ wrapWLDispatcherFunc (dispatchResourceRequest proxy handler)
                    let info = TypeInfo { typeInterface    = interface
                                        , typeDispatchFunc = dispatchFunc
                                        }
                    cacheTypeInfo %= M.insert (typeRep proxy) info
                    pure info

-- | Resolve the native interface for an object type.
resolveTypeInterface
  :: Object o
  => Proxy o                            -- ^ Proxy for the object type to resolve.
  -> (Message -> IO ())                 -- ^ Message handler used to dispatch requests in the type's dispatch func.
  -> ObjectCacheState (Ptr WLInterface) -- ^ State monad wrapping the native interface.
resolveTypeInterface proxy handler = typeInterface <$> resolveTypeInfo proxy handler

-- | Get the next argument in an argument iterator.
--
-- This function may raise an error if there is no more argument remaining.
advanceIterator :: MonadState ArgumentIterator m => m (Ptr WLArgument)
advanceIterator = do
  ArgumentIterator arg <- get
  put $ ArgumentIterator $ plusPtr arg (sizeOf (undefined :: WLArgument))
  pure arg

-- | Create an 'ArgumentWriter' wrapping a continuation (like alloca).
liftCont :: ((a -> IO r) -> IO r) -> ArgumentWriter r a
liftCont = ArgumentWriter . lift . ContT

instance ArgumentMarshaller (ArgumentWriter r) where
  marshalInt v = do
    arg <- advanceIterator
    liftIO $ wlArgumentPokeInt arg (fromIntegral v)
  marshalUInt v = do
    arg <- advanceIterator
    liftIO $ wlArgumentPokeUInt arg (fromIntegral v)
  marshalFixed v = do
    arg <- advanceIterator
    liftIO $ wlArgumentPokeFixed arg (WLFixed (round (v * 256)))
  marshalFD (Fd v) = do
    arg <- advanceIterator
    liftIO $ wlArgumentPokeFD arg (fromIntegral v)
  marshalString v = do
    arg <- advanceIterator
    str <- liftCont $ withCString v
    liftIO $ wlArgumentPokeString arg str
  marshalMaybeString (Just v) = marshalString v
  marshalMaybeString Nothing = do
    arg <- advanceIterator
    liftIO $ wlArgumentPokeString arg nullPtr
  marshalObject v = do
    arg <- advanceIterator
    obj <- liftIO $ unwrapResource v
    liftIO $ wlArgumentPokeObject arg obj
  marshalMaybeObject (Just v) = marshalObject v
  marshalMaybeObject Nothing = do
    arg <- advanceIterator
    liftIO $ wlArgumentPokeObject arg (WLResource nullPtr)
  marshalNewID v = do
    arg <- advanceIterator
    liftIO $ wlArgumentPokeNewID arg (newID v)
  marshalArray v = do
    arg <- advanceIterator
    (buf, len) <- liftCont $ useAsCStringLen v
    arr <- liftCont $ with $ WLArray { arrSize  = fromIntegral len
                                     , arrAlloc = fromIntegral len
                                     , arrData  = castPtr buf
                                     }
    liftIO $ wlArgumentPokeArray arg arr

instance ArgumentDemarshaller ArgumentReader where
  demarshalInt    = fromIntegral   <$> (advanceIterator >>= liftIO . wlArgumentPeekInt)
  demarshalUInt   = fromIntegral   <$> (advanceIterator >>= liftIO . wlArgumentPeekUInt)
  demarshalFixed  = wlFixedToFloat <$> (advanceIterator >>= liftIO . wlArgumentPeekFixed)
  demarshalString = do
    ptr <- advanceIterator >>= liftIO . wlArgumentPeekString
    liftIO $ peekCString ptr
  demarshalMaybeString = do
    ptr <- advanceIterator >>= liftIO . wlArgumentPeekString
    if ptr == nullPtr then pure Nothing else Just <$> liftIO (peekCString ptr)
  demarshalObject = do
    ptr <- advanceIterator >>= liftIO . wlArgumentPeekObject
    liftIO $ wrapResource ptr
  demarshalMaybeObject = do
    ptr <- advanceIterator >>= liftIO . wlArgumentPeekObject
    if ptr == WLResource nullPtr then pure Nothing else Just <$> liftIO (wrapResource ptr)
  demarshalNewID = do
    objID <- advanceIterator >>= liftIO . wlArgumentPeekNewID
    client <- ask
    liftIO $ createNewObject client objID
  demarshalArray = do
    ptr <- advanceIterator >>= liftIO . wlArgumentPeekArray
    arr <- liftIO $ peek ptr
    liftIO $ packCStringLen (castPtr (arrData arr), fromIntegral (arrSize arr))
  demarshalFD = Fd . fromIntegral <$> (advanceIterator >>= liftIO . wlArgumentPeekFD)