From a9be7bf5cc1c934c5de63830498e76a8aabe4ace Mon Sep 17 00:00:00 2001 From: Jens Nolte <git@queezle.net> Date: Mon, 6 Sep 2021 04:01:05 +0200 Subject: [PATCH] Parse received messages --- quasar-wayland.cabal | 6 +- src/Quasar/Wayland/Client.hs | 24 +++-- src/Quasar/Wayland/Protocol.hs | 159 +++++++++++++++++++++++++++++++-- src/Quasar/Wayland/TH.hs | 88 +++++++++++++----- 4 files changed, 239 insertions(+), 38 deletions(-) diff --git a/quasar-wayland.cabal b/quasar-wayland.cabal index 63d666c..ded8592 100644 --- a/quasar-wayland.cabal +++ b/quasar-wayland.cabal @@ -88,16 +88,16 @@ library Quasar.Wayland.TH build-depends: base >=4.7 && <5, - --binary, + binary, bytestring, exceptions, filepath, - --mtl, + mtl, network, quasar, template-haskell, --unix, - --unordered-containers, + unordered-containers, stm, xml, -- required for record-dot-preprocessor diff --git a/src/Quasar/Wayland/Client.hs b/src/Quasar/Wayland/Client.hs index 0a3d3c3..ddb9841 100644 --- a/src/Quasar/Wayland/Client.hs +++ b/src/Quasar/Wayland/Client.hs @@ -8,6 +8,7 @@ import Control.Monad.Catch import Network.Socket (Socket) import Network.Socket qualified as Socket import Network.Socket.ByteString qualified as Socket +import Network.Socket.ByteString.Lazy qualified as SocketL import Quasar import Quasar.Prelude import Quasar.Wayland.Protocol @@ -17,7 +18,7 @@ import Text.Read (readEither) data WaylandClient = WaylandClient { - protocolStateVar :: TVar ProtocolState, + protocolStateVar :: TVar ClientProtocolState, socket :: Socket, resourceManager :: ResourceManager } @@ -30,7 +31,7 @@ instance IsDisposable WaylandClient where newWaylandClient :: MonadResourceManager m => Socket -> m WaylandClient newWaylandClient socket = do - protocolStateVar <- liftIO $ newTVarIO initialProtocolState + protocolStateVar <- liftIO $ newTVarIO initialClientProtocolState resourceManager <- newResourceManager onResourceManager resourceManager do @@ -43,20 +44,31 @@ newWaylandClient socket = do registerDisposeAction $ closeWaylandClient client runUnlimitedAsync do - async $ liftIO $ waylandClientSendThread client - async $ liftIO $ waylandClientReceiveThread client + async $ liftIO $ waylandClientSendThread client `catchAll` \ex -> traceIO (displayException ex) >> void (dispose resourceManager) + async $ liftIO $ waylandClientReceiveThread client `catchAll` \ex -> traceIO (displayException ex) >> void (dispose resourceManager) pure client waylandClientSendThread :: WaylandClient -> IO () waylandClientSendThread client = forever do - undefined + bytes <- atomically do + outbox <- stateTVar client.protocolStateVar takeOutbox + case outbox of + Just bytes -> pure bytes + Nothing -> retry + + traceIO $ "Sending data" + SocketL.sendAll client.socket bytes + waylandClientReceiveThread :: WaylandClient -> IO () waylandClientReceiveThread client = forever do bytes <- Socket.recv client.socket 4096 traceIO $ "Received data" - atomically $ modifyTVar client.protocolStateVar $ feedInput bytes + events <- atomically $ stateTVar client.protocolStateVar $ feedInput bytes + + traceIO $ "Received " <> show (length events) <> " events" + mapM_ (traceIO . show) events state <- atomically $ readTVar client.protocolStateVar traceIO $ show state.bytesReceived diff --git a/src/Quasar/Wayland/Protocol.hs b/src/Quasar/Wayland/Protocol.hs index 24dea57..aeb1398 100644 --- a/src/Quasar/Wayland/Protocol.hs +++ b/src/Quasar/Wayland/Protocol.hs @@ -1,29 +1,172 @@ module Quasar.Wayland.Protocol ( ProtocolState, + ClientProtocolState, + initialClientProtocolState, + --ServerProtocolState, + --initialServerProtocolState, + Request, + Event, initialProtocolState, feedInput, + takeOutbox, ) where +import Control.Monad.State (State) +import Control.Monad.State qualified as State +import Data.Binary.Get +import Data.Binary.Put +import Data.Bits ((.&.), (.|.), shiftL, shiftR) import Data.ByteString (ByteString) import Data.ByteString qualified as BS +import Data.ByteString.Lazy qualified as BSL +import Data.HashMap.Strict (HashMap) +import Data.HashMap.Strict qualified as HM import Quasar.Prelude import Quasar.Wayland.TH $(generateWaylandProcol "protocols/wayland.xml") -data ProtocolState = ProtocolState { +type ObjectId = Word32 +type ObjectType = String +type Opcode = Word16 + +data Object = Object { + objectId :: ObjectId, + objectType :: ObjectType +} + + +data Argument + = IntArgument Int32 + | UIntArgument Word32 + -- TODO + | FixedArgument Void + | StringArgument String + | ObjectArgument ObjectId + | NewIdArgument ObjectId + | FdArgument () + +argumentSize :: Argument -> Word16 +argumentSize (IntArgument _) = 4 +argumentSize (UIntArgument _) = 4 +argumentSize (ObjectArgument _) = 4 +argumentSize (NewIdArgument _) = 4 +argumentSize _ = undefined + +putArgument :: Argument -> Put +putArgument (IntArgument x) = putInt32host x +putArgument (UIntArgument x) = putWord32host x +putArgument (ObjectArgument x) = putWord32host x +putArgument (NewIdArgument x) = putWord32host x +putArgument _ = undefined + + +type ClientProtocolState = ProtocolState Request Event +type ServerProtocolState = ProtocolState Event Request + +data ProtocolState up down = ProtocolState { bytesReceived :: Word64, - bytesSent :: Word64 + bytesSent :: Word64, + parser :: Decoder down, + inboxDecoder :: Decoder down, + outbox :: Maybe Put, + objects :: HashMap ObjectId Object } -initialProtocolState :: ProtocolState -initialProtocolState = ProtocolState { +data Request = Request ObjectId Opcode BSL.ByteString + deriving stock Show +data Event = Event ObjectId Opcode (Either BSL.ByteString (Word32, BSL.ByteString, Word32)) + deriving stock Show + +initialClientProtocolState :: ClientProtocolState +initialClientProtocolState = initialProtocolState decodeEvent + +initialProtocolState :: Get down -> ProtocolState up down +initialProtocolState downGet = sendInitialMessage ProtocolState { bytesReceived = 0, - bytesSent = 0 + bytesSent = 0, + parser = runGetIncremental downGet, + inboxDecoder = runGetIncremental downGet, + outbox = Nothing, + objects = HM.singleton 1 (Object 1 "wl_display") } -feedInput :: ByteString -> ProtocolState -> (ProtocolState) -feedInput bytes oldState = oldState { - bytesReceived = oldState.bytesReceived + fromIntegral (BS.length bytes) +sendInitialMessage :: ProtocolState up down -> ProtocolState up down +sendInitialMessage = sendMessage 1 1 [NewIdArgument 2] + +feedInput :: forall up down. ByteString -> ProtocolState up down -> ([down], ProtocolState up down) +feedInput bytes = State.runState do + State.modify (receive bytes) + go + where + go :: State (ProtocolState up down) [down] + go = State.state takeDownMsg >>= \case + Nothing -> pure [] + Just msg -> (msg :) <$> go + + +receive :: forall up down. ByteString -> ProtocolState up down -> ProtocolState up down +receive bytes state = state { + bytesReceived = state.bytesReceived + fromIntegral (BS.length bytes), + inboxDecoder = pushChunk state.inboxDecoder bytes } + +takeDownMsg :: forall up down. ProtocolState up down -> (Maybe down, ProtocolState up down) +takeDownMsg state = (result, state{inboxDecoder = newDecoder}) + where + result :: Maybe down + newDecoder :: Decoder down + (result, newDecoder) = checkDecoder state.inboxDecoder + checkDecoder :: Decoder down -> (Maybe down, Decoder down) + checkDecoder (Fail _ _ _) = undefined + checkDecoder x@(Partial _) = (Nothing, x) + checkDecoder (Done leftovers _ result) = (Just result, pushChunk state.parser leftovers) + + +decodeEvent :: Get Event +decodeEvent = do + objectId <- getWord32host + sizeAndOpcode <- getWord32host + let + size = fromIntegral (sizeAndOpcode `shiftR` 16) - 8 + opcode = fromIntegral (sizeAndOpcode .&. 0xFFFF) + body <- if (objectId == 2 && opcode == 0) + then Right <$> parseGlobal + else Left <$> getLazyByteString size <* skipPadding + pure $ Event objectId opcode body + where + parseGlobal :: Get (Word32, BSL.ByteString, Word32) + parseGlobal = (,,) <$> getWord32host <*> getWaylandString <*> getWord32host + getWaylandString :: Get BSL.ByteString + getWaylandString = do + size <- getWord32host + Just (string, 0) <- BSL.unsnoc <$> getLazyByteString (fromIntegral size) + skipPadding + pure string + +skipPadding :: Get () +skipPadding = do + bytes <- bytesRead + skip $ fromIntegral ((4 - (bytes `mod` 4)) `mod` 4) + + +sendMessage :: ObjectId -> Opcode -> [Argument] -> ProtocolState up down -> ProtocolState up down +sendMessage objectId opcode args = sendRaw do + putWord32host objectId + putWord32host $ (fromIntegral msgSize `shiftL` 16) .|. fromIntegral opcode + mapM_ putArgument args + -- TODO padding + where + msgSize :: Word16 + msgSize = if msgSizeInteger <= fromIntegral (maxBound :: Word16) then fromIntegral msgSizeInteger else undefined + msgSizeInteger :: Integer + msgSizeInteger = foldr ((+) . (fromIntegral . argumentSize)) 8 args :: Integer + +sendRaw :: Put -> ProtocolState up down -> ProtocolState up down +sendRaw put oldState = oldState { + outbox = Just (maybe put (<> put) oldState.outbox) +} + +takeOutbox :: ProtocolState up down -> (Maybe BSL.ByteString, ProtocolState up down) +takeOutbox state = (runPut <$> state.outbox, state{outbox = Nothing}) diff --git a/src/Quasar/Wayland/TH.hs b/src/Quasar/Wayland/TH.hs index 9d8943b..c76b8d6 100644 --- a/src/Quasar/Wayland/TH.hs +++ b/src/Quasar/Wayland/TH.hs @@ -11,35 +11,81 @@ import Language.Haskell.TH.Lib import Language.Haskell.TH.Syntax (addDependentFile) + generateWaylandProcol :: FilePath -> Q [Dec] generateWaylandProcol protocolFile = do addDependentFile protocolFile xml <- liftIO (BS.readFile protocolFile) - protocol <- loadProtocol xml + protocol <- parseProtocol xml - traceIO $ show $ (.name) <$> (interfaces protocol) + traceIO $ show $ interfaces protocol pure [] +type Opcode = Word16 + data Protocol = Protocol {interfaces :: [Interface]} - deriving (Show) -data Interface = Interface { name :: String } - deriving (Show) - -loadProtocol :: MonadFail m => BS.ByteString -> m Protocol -loadProtocol xml = do - (Just protocolEl) <- pure $ parseXMLDoc xml - interfaces <- mapM loadInterface $ findChildren (blank_name { qName = "interface" }) protocolEl - pure $ Protocol interfaces - -loadInterface :: MonadFail m => Element -> m Interface -loadInterface interfaceEl = do - name <- interfaceName - pure $ Interface name - where - interfaceName :: MonadFail m => m String - interfaceName = do - (Just name) <- pure $ findAttr (blank_name { qName = "name" }) interfaceEl - pure name + deriving stock (Show) + +data Interface = Interface { + name :: String, + requests :: [Request], + events :: [Event] +} + deriving stock (Show) + +data Request = Request { + name :: String, + opcode :: Opcode +} + deriving stock (Show) + +data Event = Event { + name :: String, + opcode :: Opcode +} + deriving stock (Show) + +parseProtocol :: MonadFail m => BS.ByteString -> m Protocol +parseProtocol xml = do + (Just element) <- pure $ parseXMLDoc xml + interfaces <- mapM parseInterface $ findChildren (blank_name { qName = "interface" }) element + pure Protocol { + interfaces + } + +parseInterface :: MonadFail m => Element -> m Interface +parseInterface element = do + name <- getAttr "name" element + requests <- mapM parseRequest $ zip [0..] $ findChildren (qname "request") element + events <- mapM parseEvent $ zip [0..] $ findChildren (qname "events") element + pure Interface { + name, + requests, + events + } + +parseRequest :: MonadFail m => (Opcode, Element) -> m Request +parseRequest (opcode, element) = do + name <- getAttr "name" element + pure Request { + name, + opcode + } + +parseEvent :: MonadFail m => (Opcode, Element) -> m Event +parseEvent (opcode, element) = do + name <- getAttr "name" element + pure Event { + name, + opcode + } + +qname :: String -> QName +qname name = blank_name { qName = name } +getAttr :: MonadFail m => String -> Element -> m String +getAttr name element = do + (Just value) <- pure $ findAttr (qname name) element + pure value -- GitLab