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
|
{-# 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 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 ()
marshalObject :: Object o => o -> m ()
marshalNewID :: Object o => NewObject o -> m ()
marshalArray :: Storable a => [a] -> 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
demarshalObject :: Object o => m o
demarshalNewID :: Object o => m (NewObject o)
demarshalArray :: Storable a => m [a]
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)
-- | 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
marshalObject v = do
arg <- advanceIterator
obj <- liftIO $ unwrapResource v
liftIO $ wlArgumentPokeObject arg obj
marshalNewID v = do
arg <- advanceIterator
liftIO $ wlArgumentPokeNewID arg (newID v)
marshalArray v = do
arg <- advanceIterator
let len = fromIntegral $ length v * sizeOf (head v)
buf <- liftCont $ withArray v
arr <- liftCont $ with $ WLArray { arrSize = len
, arrAlloc = 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
demarshalObject = do
ptr <- advanceIterator >>= liftIO . wlArgumentPeekObject
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 $ peekArray (fromIntegral (arrSize arr)) (castPtr (arrData arr))
demarshalFD = Fd . fromIntegral <$> (advanceIterator >>= liftIO . wlArgumentPeekFD)
|