ymonad-0.1.0.0: src/YMonad/Protocol/Wayland/Wire.hs
{-# LANGUAGE OverloadedStrings #-}
module YMonad.Protocol.Wayland.Wire (
encodeWireMessage,
encodeWireBatch,
decodeRawFrames,
decodeMessageArgs,
) where
import Data.Binary.Get
import Data.Binary.Put
import Data.Bits ((.&.))
import Data.ByteString qualified as BS
import YMonad.Protocol.Wayland.Types
encodeWireMessage :: WireMessage -> BS.ByteString
encodeWireMessage msg = toStrict $ runPut $ do
let payload = toStrict (runPut (mapM_ putArgValue (wireArgs msg)))
size = fromIntegral (8 + BS.length payload) :: Word16
putWord32le (wireObjectId msg)
putWord16le (wireOpcode msg)
putWord16le size
putByteString payload
encodeWireBatch :: [WireMessage] -> BS.ByteString
encodeWireBatch = BS.concat . map encodeWireMessage
decodeRawFrames :: BS.ByteString -> Either String ([RawFrame], BS.ByteString)
decodeRawFrames input = go input id
where
go bytes acc
| BS.length bytes < 8 = Right (acc [], bytes)
| otherwise =
let header = runGet getHeader (fromStrict (BS.take 8 bytes))
payloadLen = fromIntegral (frameSize header) - 8
in if frameSize header < 8
then Left "Wayland frame shorter than header"
else
if BS.length bytes < fromIntegral (frameSize header)
then Right (acc [], bytes)
else
let payload = BS.take payloadLen (BS.drop 8 bytes)
rest = BS.drop (fromIntegral (frameSize header)) bytes
frame = RawFrame (frameObjectId header) (frameOpcode header) payload
in go rest (acc . (frame :))
getHeader = do
objectId <- getWord32le
opcode <- getWord16le
Header objectId opcode <$> getWord16le
decodeMessageArgs :: MessageSpec -> BS.ByteString -> Either String [ArgValue]
decodeMessageArgs spec payload =
case runGetOrFail (mapM getArgValue (messageSpecArgs spec)) (fromStrict payload) of
Left (_, _, err) -> Left err
Right (_, _, args) -> Right args
putArgValue :: ArgValue -> Put
putArgValue arg = case arg of
ArgIntValue n -> putInt32le n
ArgUintValue n -> putWord32le n
ArgObjectValue Nothing -> putWord32le 0
ArgObjectValue (Just n) -> putWord32le n
ArgNewIdValue n -> putWord32le n
ArgStringValue Nothing -> putWord32le 0
ArgStringValue (Just txt) -> do
let bytes = encodeUtf8 txt
total = BS.length bytes + 1
putWord32le (fromIntegral total)
putByteString bytes
putWord8 0
putPadding total
getArgValue :: ArgSpec -> Get ArgValue
getArgValue spec = case argSpecType spec of
ArgInt -> ArgIntValue <$> getInt32le
ArgUint -> ArgUintValue <$> getWord32le
ArgObject _ -> do
oid <- getWord32le
pure $ ArgObjectValue (if oid == 0 then Nothing else Just oid)
ArgNewId _ -> ArgNewIdValue <$> getWord32le
ArgString -> do
len <- getWord32le
if len == 0
then pure (ArgStringValue Nothing)
else do
let total = fromIntegral len
contentLen = max 0 (total - 1)
bytes <- getByteString contentLen
_nul <- getWord8
skipPadding total
case decodeUtf8' bytes of
Left err -> fail (show err)
Right txt -> pure (ArgStringValue (Just txt))
putPadding :: Int -> Put
putPadding payloadLen = replicateM_ (padLength payloadLen) (putWord8 0)
skipPadding :: Int -> Get ()
skipPadding payloadLen = do
_ <- getByteString (padLength payloadLen)
pass
padLength :: Int -> Int
padLength n = (4 - (n .&. 3)) .&. 3
data Header = Header !Word32 !Word16 !Word16
frameObjectId :: Header -> Word32
frameObjectId (Header objectId _ _) = objectId
frameOpcode :: Header -> Word16
frameOpcode (Header _ opcode _) = opcode
frameSize :: Header -> Word16
frameSize (Header _ _ size) = size