{"id":19234,"url":"https://patchwork.libcamera.org/api/1.1/patches/19234/?format=json","web_url":"https://patchwork.libcamera.org/patch/19234/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20231124123713.22519-2-naush@raspberrypi.com>","date":"2023-11-24T12:37:07","name":"[libcamera-devel,v3,1/7] controls: Add vendor control/property support to generation scripts","commit_ref":null,"pull_url":null,"state":"superseded","archived":false,"hash":"6e173736fbd21d0d54db593155dde2daabdfa1c9","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/1.1/people/34/?format=json","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/19234/mbox/","series":[{"id":4084,"url":"https://patchwork.libcamera.org/api/1.1/series/4084/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=4084","date":"2023-11-24T12:37:06","name":"Vendor controls and properties","version":3,"mbox":"https://patchwork.libcamera.org/series/4084/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/19234/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/19234/checks/","tags":{},"headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id C74E5C3220\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 24 Nov 2023 12:37:20 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7D7F9629BF;\n\tFri, 24 Nov 2023 13:37:20 +0100 (CET)","from mail-wm1-x32c.google.com (mail-wm1-x32c.google.com\n\t[IPv6:2a00:1450:4864:20::32c])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 0A2CB629AF\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Nov 2023 13:37:17 +0100 (CET)","by mail-wm1-x32c.google.com with SMTP id\n\t5b1f17b1804b1-40b2afd049aso12855635e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 24 Nov 2023 04:37:17 -0800 (PST)","from localhost.localdomain ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\tv11-20020a05600c444b00b0040b3867a297sm3303690wmn.36.2023.11.24.04.37.14\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 24 Nov 2023 04:37:15 -0800 (PST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1700829440;\n\tbh=WzHbA91RccDLAaEK8gMjXDiGvvpl869JLB7tz96MCdw=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=3UWHinZgsPuEJOC652TE6Fb1k+VB/hGhOONrBB6MYeT9W/lxZhq8bGx3LHTmRCjIC\n\twxpQKBsAGy71SFsyVMbcGFRgEuG96Ag+GKyQqiOLCZr2nA8XihKT8TUZxThkTft1Pt\n\tZNljwPsGy6aDI6Md3CWjkNFRWO6J10lMvVi+hrZwLnuKeE4TmPmEZmTXs/wbwxXeaG\n\tA83nggjOVYAODhbAcoZsKcK3sLQbDYpMQa95OUeSkmBxV05veSMGDhBL//o5U/rRyg\n\tUeB6jmC4hucjlxIa6vuIiLuMnPa0DeUgku1n+x1z4PYBsjlJnXGBA+7C4SVtmiiFG4\n\tI+wQ0axGdJlVQ==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1700829436; x=1701434236;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=nohTSKl3D4c/l1i+Uga5J7Ea2iqx/KDHfAMYupJkF0g=;\n\tb=dMV/5VYCx2rJRr/ovLGPvAJ61FtXzhmzaVtC2Df6eahCx8qlz6KG/gDRLbDhfLWscM\n\tJC5VdL239puHv15xyFfx7aVC2mLlvGgza1TUGdqUTDK/OuWealJoNXE47fIpIVv+iSXo\n\t/pYEcx71iR2GgQb0PddogbqW0UWea00aqF/A7x4/c8leVzup3Jul4/EimQxWlPcNyFYV\n\tJvYJdbVvA+9kdbeiMMiAqI7MpSAmXTzgALJngtAACAb7qLCAzSuzba0EPmu0MuOodYAc\n\tmds9HCI4LT4fs002J4x6q8zayGMnxWWANJ16+FZIHiXO2EDQ+AAV9WNzh/wHBYgBqOld\n\t890A=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"dMV/5VYC\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1700829436; x=1701434236;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=nohTSKl3D4c/l1i+Uga5J7Ea2iqx/KDHfAMYupJkF0g=;\n\tb=K4sihWTtoX6amVNtEmlspeZe47HNufCJQdt0yTMPTonKeuZlLiZygS/Y8OUfvcGieC\n\tXO/KR3J80axTRWaeMqZotlDnFcr16hg3BP698Fh9B5rbN3WDCDvEgxsxmvB4HtwDiRFc\n\tdB72rvT62lO/IvE+sUxRgd/PJlbMV0kuKlvwaH4A5odgbFwuBHQw5whAWDufkJdnO35C\n\tlwqp8P7WIFnC+hWLcRISUl/1kW0UNRy1Rb3ZgBS225atJ5NgWk6eVmIyNA8sq8wu5uGh\n\t/bnoot/X03Yr6BTqNV5TcjqNhGdz5VAyhpGbgNr4Ncs34eWDDmT8mWGBlKaQOSfqrD3d\n\t7Hng==","X-Gm-Message-State":"AOJu0YwjgNTTM49kemj+TLmBpIwCy15fWk2GOcgeIp2ORBIWaDUghsq8\n\tpjyb/kFoRYwTnUFxMzJPKaVbqvWhNPhrrwGG+jStwA==","X-Google-Smtp-Source":"AGHT+IHdFRd+LZ7/+h9i67jD/xVPpA6bHz+FHRCm7t8CZlBwNctFsXpxSa9qgT9i81sk3MSz8EbtXw==","X-Received":"by 2002:a05:600c:4f4b:b0:40b:36e9:bf4b with SMTP id\n\tm11-20020a05600c4f4b00b0040b36e9bf4bmr2613184wmq.41.1700829436151; \n\tFri, 24 Nov 2023 04:37:16 -0800 (PST)","To":"libcamera-devel@lists.libcamera.org","Date":"Fri, 24 Nov 2023 12:37:07 +0000","Message-Id":"<20231124123713.22519-2-naush@raspberrypi.com>","X-Mailer":"git-send-email 2.34.1","In-Reply-To":"<20231124123713.22519-1-naush@raspberrypi.com>","References":"<20231124123713.22519-1-naush@raspberrypi.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","Subject":"[libcamera-devel] [PATCH v3 1/7] controls: Add vendor\n\tcontrol/property support to generation scripts","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Naushir Patuck via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Naushir Patuck <naush@raspberrypi.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"},"content":"Add support for vendor-specific controls and properties to libcamera.\nThe controls/properties are defined by a \"vendor\" tag in the YAML\ncontrol description file, for example:\n\nvendor: rpi\ncontrols:\n  - MyExampleControl:\n      type: string\n      description: |\n        Test for libcamera vendor-specific controls.\n\nThis will now generate a control id in the libcamera::controls::rpi\nnamespace, ensuring no id conflict between different vendors, core or\ndraft libcamera controls. Similarly, a ControlIdMap control is generated\nin the libcamera::controls::rpi namespace.\n\nA #define LIBCAMERA_HAS_RPI_VENDOR_CONTROLS is also generated to allow\napplications to conditionally compile code if the specific vendor\ncontrols are present. For the python bindings, the control is available\nwith libcamera.controls.rpi.MyExampleControl. The above controls\nexample applies similarly to properties.\n\nExisting libcamera controls defined in control_ids.yaml are given the\n\"libcamera\" vendor tag.\n\nA new --mode flag is added to gen-controls.py to specify the mode of\noperation, either 'controls' or 'properties' to allow the code generator\nto correctly set the #define string.\n\nAs a drive-by, sort and redefine the output command line argument in\ngen-controls.py and gen-py-controls.py to ('--output', '-o') for\nconsistency.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\n---\n include/libcamera/control_ids.h.in            |   2 +\n include/libcamera/meson.build                 |  15 +--\n include/libcamera/property_ids.h.in           |   2 +\n src/libcamera/control_ids.cpp.in              |   4 +\n src/libcamera/control_ids.yaml                |   1 +\n src/libcamera/meson.build                     |   5 +-\n src/libcamera/property_ids.cpp.in             |   4 +\n src/libcamera/property_ids.yaml               |   1 +\n src/py/libcamera/gen-py-controls.py           |  81 +++++++-----\n src/py/libcamera/py_controls_generated.cpp.in |   3 +\n .../libcamera/py_properties_generated.cpp.in  |   3 +\n utils/gen-controls.py                         | 117 +++++++++++++-----\n 12 files changed, 168 insertions(+), 70 deletions(-)","diff":"diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in\nindex 0718a8886f6c..c97b09a82450 100644\n--- a/include/libcamera/control_ids.h.in\n+++ b/include/libcamera/control_ids.h.in\n@@ -32,6 +32,8 @@ ${draft_controls}\n \n } /* namespace draft */\n \n+${vendor_controls}\n+\n } /* namespace controls */\n \n } /* namespace libcamera */\ndiff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\nindex a24c50d66a82..2c8c0258c95e 100644\n--- a/include/libcamera/meson.build\n+++ b/include/libcamera/meson.build\n@@ -32,20 +32,21 @@ install_headers(libcamera_public_headers,\n \n libcamera_headers_install_dir = get_option('includedir') / libcamera_include_dir\n \n-# control_ids.h and property_ids.h\n-control_source_files = [\n-    'control_ids',\n-    'property_ids',\n-]\n+# control_ids.h and property_ids.h and associated modes\n+control_source_files = {\n+    'control_ids': 'controls',\n+    'property_ids': 'properties',\n+}\n \n control_headers = []\n \n-foreach header : control_source_files\n+foreach header, mode : control_source_files\n     input_files = files('../../src/libcamera/' + header +'.yaml', header + '.h.in')\n     control_headers += custom_target(header + '_h',\n                                      input : input_files,\n                                      output : header + '.h',\n-                                     command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'],\n+                                     command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@',\n+                                                '--mode', mode],\n                                      install : true,\n                                      install_dir : libcamera_headers_install_dir)\n endforeach\ndiff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in\nindex ff0194083af0..47c5d6bf2e28 100644\n--- a/include/libcamera/property_ids.h.in\n+++ b/include/libcamera/property_ids.h.in\n@@ -31,6 +31,8 @@ ${draft_controls}\n \n extern const ControlIdMap properties;\n \n+${vendor_controls}\n+\n } /* namespace properties */\n \n } /* namespace libcamera */\ndiff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in\nindex 5fb1c2c30558..be86548cf73f 100644\n--- a/src/libcamera/control_ids.cpp.in\n+++ b/src/libcamera/control_ids.cpp.in\n@@ -33,6 +33,8 @@ ${draft_controls_doc}\n \n } /* namespace draft */\n \n+${vendor_controls_doc}\n+\n #ifndef __DOXYGEN__\n /*\n  * Keep the controls definitions hidden from doxygen as it incorrectly parses\n@@ -45,6 +47,8 @@ namespace draft {\n ${draft_controls_def}\n \n } /* namespace draft */\n+\n+${vendor_controls_def}\n #endif\n \n /**\ndiff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\nindex 5827d7ecef49..ff74ce1deedb 100644\n--- a/src/libcamera/control_ids.yaml\n+++ b/src/libcamera/control_ids.yaml\n@@ -6,6 +6,7 @@\n ---\n # Unless otherwise stated, all controls are bi-directional, i.e. they can be\n # set through Request::controls() and returned out through Request::metadata().\n+vendor: libcamera\n controls:\n   - AeEnable:\n       type: bool\ndiff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\nindex d0e26f6b4141..e49bf850b355 100644\n--- a/src/libcamera/meson.build\n+++ b/src/libcamera/meson.build\n@@ -127,12 +127,13 @@ endif\n \n control_sources = []\n \n-foreach source : control_source_files\n+foreach source, mode : control_source_files\n     input_files = files(source +'.yaml', source + '.cpp.in')\n     control_sources += custom_target(source + '_cpp',\n                                      input : input_files,\n                                      output : source + '.cpp',\n-                                     command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@'])\n+                                     command : [gen_controls, '-o', '@OUTPUT@', '@INPUT@',\n+                                                '--mode', mode])\n endforeach\n \n libcamera_sources += control_sources\ndiff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in\nindex f917e3349766..0771ac5c091f 100644\n--- a/src/libcamera/property_ids.cpp.in\n+++ b/src/libcamera/property_ids.cpp.in\n@@ -32,6 +32,8 @@ ${draft_controls_doc}\n \n } /* namespace draft */\n \n+${vendor_controls_doc}\n+\n #ifndef __DOXYGEN__\n /*\n  * Keep the properties definitions hidden from doxygen as it incorrectly parses\n@@ -44,6 +46,8 @@ namespace draft {\n ${draft_controls_def}\n \n } /* namespace draft */\n+\n+${vendor_controls_def}\n #endif\n \n /**\ndiff --git a/src/libcamera/property_ids.yaml b/src/libcamera/property_ids.yaml\nindex f35563842a5a..45f3609b4236 100644\n--- a/src/libcamera/property_ids.yaml\n+++ b/src/libcamera/property_ids.yaml\n@@ -4,6 +4,7 @@\n #\n %YAML 1.1\n ---\n+vendor: libcamera\n controls:\n   - Location:\n       type: int32_t\ndiff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py\nindex 9948c41e42b1..dfd7c179a883 100755\n--- a/src/py/libcamera/gen-py-controls.py\n+++ b/src/py/libcamera/gen-py-controls.py\n@@ -24,45 +24,59 @@ def find_common_prefix(strings):\n def generate_py(controls, mode):\n     out = ''\n \n-    for ctrl in controls:\n-        name, ctrl = ctrl.popitem()\n-\n-        if ctrl.get('draft'):\n-            ns = 'libcamera::{}::draft::'.format(mode)\n-            container = 'draft'\n-        else:\n-            ns = 'libcamera::{}::'.format(mode)\n-            container = 'controls'\n+    vendors_class_def = []\n+    vendor_defs = []\n+    vendors = []\n+    for vendor, ctrl_list in controls.items():\n+        for ctrls in ctrl_list:\n+            name, ctrl = ctrls.popitem()\n+\n+            if vendor not in vendors and vendor != 'libcamera':\n+                vendors_class_def.append('class Py{}Controls\\n{{\\n}};\\n'.format(vendor))\n+                vendor_defs.append('\\tauto {} = py::class_<Py{}Controls>(controls, \\\"{}\\\");'.format(vendor, vendor, vendor))\n+                vendors.append(vendor)\n+\n+            if ctrl.get('draft'):\n+                ns = 'libcamera::{}::draft::'.format(mode)\n+                container = 'draft'\n+            elif vendor != 'libcamera':\n+                ns = 'libcamera::{}::{}::'.format(mode, vendor)\n+                container = vendor\n+            else:\n+                ns = 'libcamera::{}::'.format(mode)\n+                container = 'controls'\n \n-        out += f'\\t{container}.def_readonly_static(\"{name}\", static_cast<const libcamera::ControlId *>(&{ns}{name}));\\n\\n'\n+            out += f'\\t{container}.def_readonly_static(\"{name}\", static_cast<const libcamera::ControlId *>(&{ns}{name}));\\n\\n'\n \n-        enum = ctrl.get('enum')\n-        if not enum:\n-            continue\n+            enum = ctrl.get('enum')\n+            if not enum:\n+                continue\n \n-        cpp_enum = name + 'Enum'\n+            cpp_enum = name + 'Enum'\n \n-        out += '\\tpy::enum_<{}{}>({}, \\\"{}\\\")\\n'.format(ns, cpp_enum, container, cpp_enum)\n+            out += '\\tpy::enum_<{}{}>({}, \\\"{}\\\")\\n'.format(ns, cpp_enum, container, cpp_enum)\n \n-        if mode == 'controls':\n-            # Adjustments for controls\n-            if name == 'LensShadingMapMode':\n-                prefix = 'LensShadingMapMode'\n+            if mode == 'controls':\n+                # Adjustments for controls\n+                if name == 'LensShadingMapMode':\n+                    prefix = 'LensShadingMapMode'\n+                else:\n+                    prefix = find_common_prefix([e['name'] for e in enum])\n             else:\n+                # Adjustments for properties\n                 prefix = find_common_prefix([e['name'] for e in enum])\n-        else:\n-            # Adjustments for properties\n-            prefix = find_common_prefix([e['name'] for e in enum])\n \n-        for entry in enum:\n-            cpp_enum = entry['name']\n-            py_enum = entry['name'][len(prefix):]\n+            for entry in enum:\n+                cpp_enum = entry['name']\n+                py_enum = entry['name'][len(prefix):]\n \n-            out += '\\t\\t.value(\\\"{}\\\", {}{})\\n'.format(py_enum, ns, cpp_enum)\n+                out += '\\t\\t.value(\\\"{}\\\", {}{})\\n'.format(py_enum, ns, cpp_enum)\n \n-        out += '\\t;\\n\\n'\n+            out += '\\t;\\n\\n'\n \n-    return {'controls': out}\n+    return {'controls': out,\n+            'vendors_class_def': '\\n'.join(vendors_class_def),\n+            'vendors_defs': '\\n'.join(vendor_defs)}\n \n \n def fill_template(template, data):\n@@ -75,14 +89,14 @@ def fill_template(template, data):\n def main(argv):\n     # Parse command line arguments\n     parser = argparse.ArgumentParser()\n-    parser.add_argument('-o', dest='output', metavar='file', type=str,\n+    parser.add_argument('--mode', '-m', type=str, required=True,\n+                        help='Mode is either \"controls\" or \"properties\"')\n+    parser.add_argument('--output', '-o', metavar='file', type=str,\n                         help='Output file name. Defaults to standard output if not specified.')\n     parser.add_argument('input', type=str,\n                         help='Input file name.')\n     parser.add_argument('template', type=str,\n                         help='Template file name.')\n-    parser.add_argument('--mode', type=str, required=True,\n-                        help='Mode is either \"controls\" or \"properties\"')\n     args = parser.parse_args(argv[1:])\n \n     if args.mode not in ['controls', 'properties']:\n@@ -90,7 +104,10 @@ def main(argv):\n         return -1\n \n     data = open(args.input, 'rb').read()\n-    controls = yaml.safe_load(data)['controls']\n+\n+    controls = {}\n+    vendor = yaml.safe_load(data)['vendor']\n+    controls[vendor] = yaml.safe_load(data)['controls']\n \n     data = generate_py(controls, args.mode)\n \ndiff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in\nindex 18fa57d948ea..ec4b55ef2011 100644\n--- a/src/py/libcamera/py_controls_generated.cpp.in\n+++ b/src/py/libcamera/py_controls_generated.cpp.in\n@@ -21,10 +21,13 @@ class PyDraftControls\n {\n };\n \n+${vendors_class_def}\n+\n void init_py_controls_generated(py::module& m)\n {\n \tauto controls = py::class_<PyControls>(m, \"controls\");\n \tauto draft = py::class_<PyDraftControls>(controls, \"draft\");\n+${vendors_defs}\n \n ${controls}\n }\ndiff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in\nindex e49b6e91bb83..f7b5ec8c635d 100644\n--- a/src/py/libcamera/py_properties_generated.cpp.in\n+++ b/src/py/libcamera/py_properties_generated.cpp.in\n@@ -21,10 +21,13 @@ class PyDraftProperties\n {\n };\n \n+${vendors_class_def}\n+\n void init_py_properties_generated(py::module& m)\n {\n \tauto controls = py::class_<PyProperties>(m, \"properties\");\n \tauto draft = py::class_<PyDraftProperties>(controls, \"draft\");\n+${vendors_defs}\n \n ${controls}\n }\ndiff --git a/utils/gen-controls.py b/utils/gen-controls.py\nindex 1075ae302ce1..4c8d41eb9997 100755\n--- a/utils/gen-controls.py\n+++ b/utils/gen-controls.py\n@@ -35,11 +35,12 @@ class ControlEnum(object):\n \n \n class Control(object):\n-    def __init__(self, name, data):\n+    def __init__(self, name, data, vendor):\n         self.__name = name\n         self.__data = data\n         self.__enum_values = None\n         self.__size = None\n+        self.__vendor = vendor\n \n         enum_values = data.get('enum')\n         if enum_values is not None:\n@@ -89,6 +90,11 @@ class Control(object):\n         \"\"\"Is the control a draft control\"\"\"\n         return self.__data.get('draft') is not None\n \n+    @property\n+    def vendor(self):\n+        \"\"\"The vendor string, or None\"\"\"\n+        return self.__vendor\n+\n     @property\n     def name(self):\n         \"\"\"The control name (CamelCase)\"\"\"\n@@ -145,15 +151,18 @@ ${description}\n     enum_values_start = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values = {''')\n     enum_values_values = string.Template('''\\tstatic_cast<int32_t>(${name}),''')\n \n-    ctrls_doc = []\n-    ctrls_def = []\n-    draft_ctrls_doc = []\n-    draft_ctrls_def = []\n+    ctrls_doc = {}\n+    ctrls_def = {}\n     ctrls_map = []\n \n     for ctrl in controls:\n         id_name = snake_case(ctrl.name).upper()\n \n+        vendor = 'draft' if ctrl.is_draft else ctrl.vendor\n+        if vendor not in ctrls_doc:\n+            ctrls_doc[vendor] = []\n+            ctrls_def[vendor] = []\n+\n         info = {\n             'name': ctrl.name,\n             'type': ctrl.type,\n@@ -161,11 +170,8 @@ ${description}\n             'id_name': id_name,\n         }\n \n-        target_doc = ctrls_doc\n-        target_def = ctrls_def\n-        if ctrl.is_draft:\n-            target_doc = draft_ctrls_doc\n-            target_def = draft_ctrls_def\n+        target_doc = ctrls_doc[vendor]\n+        target_def = ctrls_def[vendor]\n \n         if ctrl.is_enum:\n             enum_doc = []\n@@ -203,39 +209,65 @@ ${description}\n \n         ctrls_map.append('\\t{ ' + id_name + ', &' + ctrl.q_name + ' },')\n \n+    vendor_ctrl_doc_sub = []\n+    vendor_ctrl_template = string.Template('''\n+namespace ${vendor} {\n+\n+${vendor_controls_str}\n+\n+} /* namespace ${vendor} */''')\n+\n+    for vendor in [v for v in ctrls_doc.keys() if v not in ['libcamera', 'draft']]:\n+        vendor_ctrl_doc_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\\n\\n'.join(ctrls_doc[vendor])}))\n+\n+    vendor_ctrl_def_sub = []\n+    for vendor in [v for v in ctrls_def.keys() if v not in ['libcamera', 'draft']]:\n+        vendor_ctrl_def_sub.append(vendor_ctrl_template.substitute({'vendor': vendor, 'vendor_controls_str': '\\n'.join(ctrls_def[vendor])}))\n+\n     return {\n-        'controls_doc': '\\n\\n'.join(ctrls_doc),\n-        'controls_def': '\\n'.join(ctrls_def),\n-        'draft_controls_doc': '\\n\\n'.join(draft_ctrls_doc),\n-        'draft_controls_def': '\\n\\n'.join(draft_ctrls_def),\n+        'controls_doc': '\\n\\n'.join(ctrls_doc['libcamera']),\n+        'controls_def': '\\n'.join(ctrls_def['libcamera']),\n+        'draft_controls_doc': '\\n\\n'.join(ctrls_doc['draft']),\n+        'draft_controls_def': '\\n\\n'.join(ctrls_def['draft']),\n         'controls_map': '\\n'.join(ctrls_map),\n+        'vendor_controls_doc': '\\n'.join(vendor_ctrl_doc_sub),\n+        'vendor_controls_def': '\\n'.join(vendor_ctrl_def_sub),\n     }\n \n \n-def generate_h(controls):\n+def generate_h(controls, mode):\n     enum_template_start = string.Template('''enum ${name}Enum {''')\n     enum_value_template = string.Template('''\\t${name} = ${value},''')\n     enum_values_template = string.Template('''extern const std::array<const ControlValue, ${size}> ${name}Values;''')\n     template = string.Template('''extern const Control<${type}> ${name};''')\n \n-    ctrls = []\n-    draft_ctrls = []\n-    ids = []\n-    id_value = 1\n+    ctrls = {}\n+    ids = {}\n+    id_value = {}\n \n     for ctrl in controls:\n         id_name = snake_case(ctrl.name).upper()\n \n-        ids.append('\\t' + id_name + ' = ' + str(id_value) + ',')\n+        vendor = 'draft' if ctrl.is_draft else ctrl.vendor\n+        if vendor not in ctrls:\n+            ids[vendor] = []\n+            id_value[vendor] = 1\n+            ctrls[vendor] = []\n+\n+        # Core and draft controls use the same ID value\n+        target_ids = ids['libcamera'] if vendor in ['libcamera', 'draft'] else ids[vendor]\n+        target_ids.append('\\t' + id_name + ' = ' + str(id_value[vendor]) + ',')\n \n         info = {\n             'name': ctrl.name,\n             'type': ctrl.type,\n         }\n \n-        target_ctrls = ctrls\n+        target_ctrls = ctrls['libcamera']\n         if ctrl.is_draft:\n-            target_ctrls = draft_ctrls\n+            target_ctrls = ctrls['draft']\n+        elif vendor != 'libcamera':\n+            target_ctrls = ctrls[vendor]\n \n         if ctrl.is_enum:\n             target_ctrls.append(enum_template_start.substitute(info))\n@@ -257,12 +289,35 @@ def generate_h(controls):\n             target_ctrls.append(enum_values_template.substitute(values_info))\n \n         target_ctrls.append(template.substitute(info))\n-        id_value += 1\n+        id_value[vendor] += 1\n+\n+    vendor_template = string.Template('''\n+namespace ${vendor} {\n+\n+#define LIBCAMERA_HAS_${vendor_def}_VENDOR_${mode}\n+\n+enum {\n+${vendor_enums}\n+};\n+\n+${vendor_controls}\n+\n+} /* namespace ${vendor} */\n+''')\n+\n+    vendor_sub = []\n+    for vendor in [v for v in ctrls.keys() if v not in ['libcamera', 'draft']]:\n+        vendor_sub.append(vendor_template.substitute({'mode': mode.upper(),\n+                                                      'vendor': vendor,\n+                                                      'vendor_def': vendor.upper(),\n+                                                      'vendor_enums': '\\n'.join(ids[vendor]),\n+                                                      'vendor_controls': '\\n'.join(ctrls[vendor])}))\n \n     return {\n-        'ids': '\\n'.join(ids),\n-        'controls': '\\n'.join(ctrls),\n-        'draft_controls': '\\n'.join(draft_ctrls)\n+        'ids': '\\n'.join(ids['libcamera']),\n+        'controls': '\\n'.join(ctrls['libcamera']),\n+        'draft_controls': '\\n'.join(ctrls['draft']),\n+        'vendor_controls': '\\n'.join(vendor_sub)\n     }\n \n \n@@ -278,22 +333,26 @@ def main(argv):\n \n     # Parse command line arguments\n     parser = argparse.ArgumentParser()\n-    parser.add_argument('-o', dest='output', metavar='file', type=str,\n+    parser.add_argument('--mode', '-m', type=str, required=True, choices=['controls', 'properties'],\n+                        help='Mode of operation')\n+    parser.add_argument('--output', '-o', metavar='file', type=str,\n                         help='Output file name. Defaults to standard output if not specified.')\n     parser.add_argument('input', type=str,\n                         help='Input file name.')\n     parser.add_argument('template', type=str,\n                         help='Template file name.')\n+\n     args = parser.parse_args(argv[1:])\n \n     data = open(args.input, 'rb').read()\n+    vendor = yaml.safe_load(data)['vendor']\n     controls = yaml.safe_load(data)['controls']\n-    controls = [Control(*ctrl.popitem()) for ctrl in controls]\n+    controls = [Control(*ctrl.popitem(), vendor) for ctrl in controls]\n \n     if args.template.endswith('.cpp.in'):\n         data = generate_cpp(controls)\n     elif args.template.endswith('.h.in'):\n-        data = generate_h(controls)\n+        data = generate_h(controls, args.mode)\n     else:\n         raise RuntimeError('Unknown template type')\n \n","prefixes":["libcamera-devel","v3","1/7"]}