diff --git a/src/Quasar/Wayland/Protocol/Core.hs b/src/Quasar/Wayland/Protocol/Core.hs
index 2b5a0dff9adef68098416ec1f92bd37103f9079e..0222b96199359824e562e5ffad5423600afd808d 100644
--- a/src/Quasar/Wayland/Protocol/Core.hs
+++ b/src/Quasar/Wayland/Protocol/Core.hs
@@ -213,7 +213,6 @@ invalidOpcode object opcode =
   fail $ "Invalid opcode " <> show opcode <> " on " <> objectInterfaceName object <> "@" <> show (objectId object)
 
 
-
 -- TODO remove
 data DynamicArgument
   = DynamicIntArgument Int32
diff --git a/src/Quasar/Wayland/Protocol/TH.hs b/src/Quasar/Wayland/Protocol/TH.hs
index 6066fe2e9b41b833ff48e5e0ef6a9561fc4a07c6..a313fe0df7cbb6cc67ae4e7a4ebdbcbbc1425c3f 100644
--- a/src/Quasar/Wayland/Protocol/TH.hs
+++ b/src/Quasar/Wayland/Protocol/TH.hs
@@ -31,7 +31,28 @@ newtype EventSpec = EventSpec MessageSpec
 
 data MessageSpec = MessageSpec {
   name :: String,
-  opcode :: Opcode
+  opcode :: Opcode,
+  arguments :: [ArgumentSpec]
+}
+  deriving stock Show
+
+
+data ArgumentType
+  = IntArgument
+  | UIntArgument
+  | FixedArgument
+  | StringArgument
+  | ArrayArgument
+  | ObjectArgument String
+  | UnknownObjectArgument
+  | NewIdArgument String
+  | UnknownNewIdArgument
+  | FdArgument
+  deriving stock Show
+
+data ArgumentSpec = ArgumentSpec {
+  name :: String,
+  argType :: ArgumentType
 }
   deriving stock Show
 
@@ -155,20 +176,47 @@ parseInterface element = do
   }
 
 parseRequest :: MonadFail m => (Opcode, Element) -> m RequestSpec
-parseRequest (opcode, element) = do
+parseRequest x = RequestSpec <$> parseMessage x
+
+parseEvent :: MonadFail m => (Opcode, Element) -> m EventSpec
+parseEvent x = EventSpec <$> parseMessage x
+
+parseMessage :: MonadFail m => (Opcode, Element) -> m MessageSpec
+parseMessage (opcode, element) = do
   name <- getAttr "name" element
-  pure $ RequestSpec MessageSpec {
+  arguments <- mapM parseArgument $ findChildren (qname "arg") element
+  pure MessageSpec  {
     name,
-    opcode
+    opcode,
+    arguments
   }
 
-parseEvent :: MonadFail m => (Opcode, Element) -> m EventSpec
-parseEvent (opcode, element) = do
+
+parseArgument :: forall m. MonadFail m => Element -> m ArgumentSpec
+parseArgument element = do
   name <- getAttr "name" element
-  pure $ EventSpec MessageSpec  {
+  argTypeStr <- getAttr "type" element
+  interface <- peekAttr "interface" element
+  argType <- parseArgumentType argTypeStr interface
+  pure ArgumentSpec {
     name,
-    opcode
+    argType
   }
+  where
+    parseArgumentType :: String -> Maybe String -> m ArgumentType
+    parseArgumentType "int" Nothing = pure IntArgument
+    parseArgumentType "uint" Nothing = pure UIntArgument
+    parseArgumentType "fixed" Nothing = pure FixedArgument
+    parseArgumentType "string" Nothing = pure StringArgument
+    parseArgumentType "array" Nothing = pure ArrayArgument
+    parseArgumentType "object" (Just interface) = pure (ObjectArgument interface)
+    parseArgumentType "object" Nothing = pure UnknownObjectArgument
+    parseArgumentType "new_id" (Just interface) = pure (NewIdArgument interface)
+    parseArgumentType "new_id" Nothing = pure UnknownNewIdArgument
+    parseArgumentType "fd" Nothing = pure FdArgument
+    parseArgumentType x Nothing = fail $ "Unknown argument type \"" <> x <> "\" encountered"
+    parseArgumentType x _ = fail $ "Argument type \"" <> x <> "\" should not have \"interface\" attribute"
+
 
 qname :: String -> QName
 qname name = blank_name { qName = name }
@@ -177,3 +225,6 @@ getAttr :: MonadFail m => String -> Element -> m String
 getAttr name element = do
   (Just value) <- pure $ findAttr (qname name) element
   pure value
+
+peekAttr :: Applicative m => String -> Element -> m (Maybe String)
+peekAttr name element = pure $ findAttr (qname name) element