[{"id":28176,"web_url":"https://patchwork.libcamera.org/comment/28176/","msgid":"<CAEmqJPqzZ-JEwQiOB_m8sXyDbQee6XWBG+RbxtTMoBeER0eMVg@mail.gmail.com>","date":"2023-11-27T12:07:41","subject":"Re: [libcamera-devel] [PATCH v3 1/7] controls: Add vendor\n\tcontrol/property support to generation scripts","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"Hi all,\n\nOn Fri, 24 Nov 2023 at 12:37, Naushir Patuck <naush@raspberrypi.com> wrote:\n>\n> Add support for vendor-specific controls and properties to libcamera.\n> The controls/properties are defined by a \"vendor\" tag in the YAML\n> control description file, for example:\n>\n> vendor: rpi\n> controls:\n>   - MyExampleControl:\n>       type: string\n>       description: |\n>         Test for libcamera vendor-specific controls.\n>\n> This will now generate a control id in the libcamera::controls::rpi\n> namespace, ensuring no id conflict between different vendors, core or\n> draft libcamera controls. Similarly, a ControlIdMap control is generated\n> in the libcamera::controls::rpi namespace.\n>\n> A #define LIBCAMERA_HAS_RPI_VENDOR_CONTROLS is also generated to allow\n> applications to conditionally compile code if the specific vendor\n> controls are present. For the python bindings, the control is available\n> with libcamera.controls.rpi.MyExampleControl. The above controls\n> example applies similarly to properties.\n>\n> Existing libcamera controls defined in control_ids.yaml are given the\n> \"libcamera\" vendor tag.\n>\n> A new --mode flag is added to gen-controls.py to specify the mode of\n> operation, either 'controls' or 'properties' to allow the code generator\n> to correctly set the #define string.\n>\n> As a drive-by, sort and redefine the output command line argument in\n> gen-controls.py and gen-py-controls.py to ('--output', '-o') for\n> consistency.\n>\n> Signed-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(-)\n>\n> diff --git a/include/libcamera/control_ids.h.in b/include/libcamera/control_ids.h.in\n> index 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 */\n> diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\n> index 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\n> diff --git a/include/libcamera/property_ids.h.in b/include/libcamera/property_ids.h.in\n> index 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 */\n> diff --git a/src/libcamera/control_ids.cpp.in b/src/libcamera/control_ids.cpp.in\n> index 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>  /**\n> diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\n> index 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\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index 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\n> diff --git a/src/libcamera/property_ids.cpp.in b/src/libcamera/property_ids.cpp.in\n> index 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>  /**\n> diff --git a/src/libcamera/property_ids.yaml b/src/libcamera/property_ids.yaml\n> index 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\n> diff --git a/src/py/libcamera/gen-py-controls.py b/src/py/libcamera/gen-py-controls.py\n> index 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>\n> diff --git a/src/py/libcamera/py_controls_generated.cpp.in b/src/py/libcamera/py_controls_generated.cpp.in\n> index 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>         auto controls = py::class_<PyControls>(m, \"controls\");\n>         auto draft = py::class_<PyDraftControls>(controls, \"draft\");\n> +${vendors_defs}\n>\n>  ${controls}\n>  }\n> diff --git a/src/py/libcamera/py_properties_generated.cpp.in b/src/py/libcamera/py_properties_generated.cpp.in\n> index 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>         auto controls = py::class_<PyProperties>(m, \"properties\");\n>         auto draft = py::class_<PyDraftProperties>(controls, \"draft\");\n> +${vendors_defs}\n>\n>  ${controls}\n>  }\n> diff --git a/utils/gen-controls.py b/utils/gen-controls.py\n> index 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\nKieran noticed that this was missing a Doxygen tag above the\nnamespace.  I'll fix this and send an update in-reply-to this patch.\n\nRegards,\nNaush\n\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> --\n> 2.34.1\n>","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 8ABEEC31E9\n\tfor <parsemail@patchwork.libcamera.org>;\n\tMon, 27 Nov 2023 12:08:23 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 95D08629BC;\n\tMon, 27 Nov 2023 13:08:22 +0100 (CET)","from mail-yw1-x1131.google.com (mail-yw1-x1131.google.com\n\t[IPv6:2607:f8b0:4864:20::1131])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 904E2629B6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 27 Nov 2023 13:08:20 +0100 (CET)","by mail-yw1-x1131.google.com with SMTP id\n\t00721157ae682-5cfc3a48ab2so10561867b3.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tMon, 27 Nov 2023 04:08:20 -0800 (PST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1701086902;\n\tbh=IExmynGNPtogtaxuhC68psDiL3PNnnZUYloBwa1utMg=;\n\th=References:In-Reply-To:Date:To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=ZJtOcLQaTvEU0IdkqNjQl0xDHckA0MBGQpknawUNBQWVCdLcd51oMd9CtrZp4EWfF\n\t0sx8eBOJsdVJS9ChNnxeInaGg3mrsgTS/jvRh0N+UynuFktU8FR6/KJVx22t1VKaCV\n\tKRt+cJ8Ecf0aRZJwmYmZY+0FL5DRHvDCSJqa7R28Xvq1mb8QaHBs5OleQXQxuMfRLn\n\tYkMDFz9wlDbrrEnaPZ4C8kAZouFeHUXIx4eG77i8vdhhw+Yi6EydH95eyvqAwxXuhH\n\tNLNLdXVzxqSFuu7sNrlbDgvezpg8Cqqpx3GGM+/vgiDz/Yh5bROVlxgfn7lEC97No4\n\tmw3/AwzDbbDxw==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1701086899; x=1701691699;\n\tdarn=lists.libcamera.org; \n\th=to:subject:message-id:date:from:in-reply-to:references:mime-version\n\t:from:to:cc:subject:date:message-id:reply-to;\n\tbh=Om+Ya+EbUTx7EwIuuHiWUufj+juL80wKsLOkcn1KOOc=;\n\tb=faiMbVRM+sfehW6y6sHBiJLhfnTmdto6TVOKi3vpYPq3IM0w3wvhpnzv9Xv70YF5Kj\n\tPT4nDjqYXb+KL7rKtc4binHYb9HMDMWFHClMYRQnfJApQY37IQaxKr3AeUU6xmxXbVu7\n\tpePhR5yuvCR1AWcFEHhXRyiZAxcmVHgo39ZRr3Kx/33AKUYWg3PEZML5FE/Mu8qcddHW\n\t3UR3i+4o1TRIazxPck7qy2Bnobofc7UDZAiruCp/bt2Z/GPjCSlnmrAm8Kvt4/FWNUa2\n\tzOi36c9XQMerjPWWrWT3tZFo+VwpilKSKfFsfyR1Dp9qGrLV1vxokIdHdIGEhxbupEl5\n\taamw=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"faiMbVRM\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1701086899; x=1701691699;\n\th=to:subject:message-id:date:from:in-reply-to:references:mime-version\n\t:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;\n\tbh=Om+Ya+EbUTx7EwIuuHiWUufj+juL80wKsLOkcn1KOOc=;\n\tb=miIalX7/p0cgcwL2KFWyNuzhYJIYA3vWKJWgMfqQj8W0XpB7PxDhy5iT/JzZXMBBLu\n\tU8fcgHOOqvaDzghqKznP/w2QIDyGtXyQCDgmkZ+FIQZg6CKLSHTS1SKSt/xBesivLyeV\n\tYZcdh7ZtZ57r+wurhGHfkdNtazvafg12JkZeGNX+dwlRUicUOSpKsVIQw+mKSAn7WlBg\n\tul+KHqGcs9RhHyt/ENufQXLsxofnK4nOjijBP0VJYtpTQSkU7lsmduVcY/NTJJwDYzyK\n\tXwL24gI7Cj3WUvpdEbjaniAHC+/TwkiGtSNmXSM/ESDWCSu//ZJeGxMmgCjM1kTAWqTI\n\tBf/g==","X-Gm-Message-State":"AOJu0Yz/53KUHsjSF5Uc2EPJuxjqVU8A99kz/abKt/L/21C7shxkrCIW\n\t3txGHatLrgsbv8U8AvwuXLtldLUi+mNoER6d29zM9ekR1/pnMyqylIs=","X-Google-Smtp-Source":"AGHT+IF2G+0xB/OWto5MXBzBQl8pH/xHl3Jn7TZN1Yd97WuqEpucE8uUSQ2oXpfUjFySdenHJxegYLl/L/+O/5EtjqA=","X-Received":"by 2002:a0d:fc05:0:b0:5cb:332e:ab68 with SMTP id\n\tm5-20020a0dfc05000000b005cb332eab68mr11235331ywf.5.1701086897336;\n\tMon, 27 Nov 2023 04:08:17 -0800 (PST)","MIME-Version":"1.0","References":"<20231124123713.22519-1-naush@raspberrypi.com>\n\t<20231124123713.22519-2-naush@raspberrypi.com>","In-Reply-To":"<20231124123713.22519-2-naush@raspberrypi.com>","Date":"Mon, 27 Nov 2023 12:07:41 +0000","Message-ID":"<CAEmqJPqzZ-JEwQiOB_m8sXyDbQee6XWBG+RbxtTMoBeER0eMVg@mail.gmail.com>","To":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [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>"}}]