Show a patch.

GET /api/patches/26035/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 26035,
    "url": "https://patchwork.libcamera.org/api/patches/26035/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/26035/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20260129082814.1777779-8-paul.elder@ideasonboard.com>",
    "date": "2026-01-29T08:28:13",
    "name": "[v6,7/8] layer: Add layer to inject AeEnable control",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "b46d90b1cf90cbcf6cee8e574c00bc32641f686d",
    "submitter": {
        "id": 17,
        "url": "https://patchwork.libcamera.org/api/people/17/?format=api",
        "name": "Paul Elder",
        "email": "paul.elder@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/26035/mbox/",
    "series": [
        {
            "id": 5753,
            "url": "https://patchwork.libcamera.org/api/series/5753/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5753",
            "date": "2026-01-29T08:28:06",
            "name": "Add Layers support",
            "version": 6,
            "mbox": "https://patchwork.libcamera.org/series/5753/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/26035/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/26035/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 08506C3226\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 29 Jan 2026 08:28:42 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 91DB261FDE;\n\tThu, 29 Jan 2026 09:28:41 +0100 (CET)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5DB7761FC6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 29 Jan 2026 09:28:37 +0100 (CET)",
            "from neptunite.flets-east.jp (unknown\n\t[IPv6:2404:7a81:160:2100:2eea:f891:1bd7:2691])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id DC973281A;\n\tThu, 29 Jan 2026 09:27:58 +0100 (CET)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"cJlMnBq0\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1769675279;\n\tbh=NHq9VgUc/aNX72WH4DVe26BA4/HGxms7PD1QKg6z9A0=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=cJlMnBq0MczpsHLQTjLWnvoeEqD2EhB3xgF0QInOBycJK5gdXPSTm05qjn41yDOTC\n\trhdUXhfH5UDFh8XAjV2WfS2rxK/U2ZFwrzibTm7ObakH7bSvIUds3isFekibXfmbf6\n\tWlwMcHCPgtxugLa9toAsTNgELiNo40CcYE5B/MG8=",
        "From": "Paul Elder <paul.elder@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Paul Elder <paul.elder@ideasonboard.com>",
        "Subject": "[PATCH v6 7/8] layer: Add layer to inject AeEnable control",
        "Date": "Thu, 29 Jan 2026 17:28:13 +0900",
        "Message-ID": "<20260129082814.1777779-8-paul.elder@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.47.2",
        "In-Reply-To": "<20260129082814.1777779-1-paul.elder@ideasonboard.com>",
        "References": "<20260129082814.1777779-1-paul.elder@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "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>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Add a layer to implement the AeEnable control, so that we can remove it\nfrom all IPAs and the Camera class, so that it is transparent to all\nparties and it is available for applications to use.\n\nSigned-off-by: Paul Elder <paul.elder@ideasonboard.com>\n\n---\nFor layers like this that we forsee to be \"always built-in\" we might\nwant a way to build it into libcamera instead of into separate shared\nobject files. In any case, this also serves as a demo of how I envision\na layer implementation to work.\n\nChanges in v6:\n- as metadata can no longer be set directly in the Request, instead\n  return it in requestCompleted\n\nNo change in v5\n\nNo change in v4\n\nNo change in v3\n\nChanges in v2:\n- add init() and terminate()\n- use closures\n- remove layer namespacing\n- add gnu_symbol_visibility\n---\n src/layer/inject_controls/inject_controls.cpp | 177 ++++++++++++++++++\n src/layer/inject_controls/inject_controls.h   |  24 +++\n src/layer/inject_controls/meson.build         |  16 ++\n src/layer/meson.build                         |   2 +\n 4 files changed, 219 insertions(+)\n create mode 100644 src/layer/inject_controls/inject_controls.cpp\n create mode 100644 src/layer/inject_controls/inject_controls.h\n create mode 100644 src/layer/inject_controls/meson.build",
    "diff": "diff --git a/src/layer/inject_controls/inject_controls.cpp b/src/layer/inject_controls/inject_controls.cpp\nnew file mode 100644\nindex 000000000000..741180f35c60\n--- /dev/null\n+++ b/src/layer/inject_controls/inject_controls.cpp\n@@ -0,0 +1,177 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas On Board Oy\n+ *\n+ * Layer implementation for injecting controls\n+ */\n+\n+#include \"inject_controls.h\"\n+\n+#include <algorithm>\n+#include <set>\n+\n+#include <libcamera/control_ids.h>\n+#include <libcamera/controls.h>\n+#include <libcamera/layer.h>\n+#include <libcamera/request.h>\n+\n+void *init([[maybe_unused]] const std::string &id)\n+{\n+\tInjectControls *closure = new InjectControls;\n+\t*closure = {};\n+\treturn static_cast<void *>(closure);\n+}\n+\n+void terminate(void *closure)\n+{\n+\tInjectControls *obj = static_cast<InjectControls *>(closure);\n+\tdelete obj;\n+}\n+\n+libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls)\n+{\n+\tInjectControls *obj = static_cast<InjectControls *>(closure);\n+\n+\tauto it = ctrls.find(&libcamera::controls::ExposureTimeMode);\n+\tif (it != ctrls.end()) {\n+\t\tfor (auto entry : it->second.values()) {\n+\t\t\tif (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeAuto))\n+\t\t\t\tobj->aeAvailable = true;\n+\t\t\tif (entry == libcamera::ControlValue(libcamera::controls::ExposureTimeModeManual))\n+\t\t\t\tobj->meAvailable = true;\n+\t\t}\n+\t}\n+\n+\tit = ctrls.find(&libcamera::controls::AnalogueGainMode);\n+\tif (it != ctrls.end()) {\n+\t\tfor (auto entry : it->second.values()) {\n+\t\t\tif (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeAuto))\n+\t\t\t\tobj->agAvailable = true;\n+\t\t\tif (entry == libcamera::ControlValue(libcamera::controls::AnalogueGainModeManual))\n+\t\t\t\tobj->mgAvailable = true;\n+\t\t}\n+\t}\n+\n+\tstd::set<bool> values;\n+\tif (obj->aeAvailable || obj->agAvailable)\n+\t\tvalues.insert(true);\n+\tif (obj->meAvailable || obj->mgAvailable)\n+\t\tvalues.insert(false);\n+\n+\tif (values.empty())\n+\t\treturn {};\n+\n+\tif (values.size() == 1) {\n+\t\tbool value = *values.begin();\n+\t\treturn { { &libcamera::controls::AeEnable,\n+\t\t\t   libcamera::ControlInfo(value, value, value) } };\n+\t}\n+\n+\treturn { { &libcamera::controls::AeEnable, libcamera::ControlInfo(false, true, true) } };\n+}\n+\n+void queueRequest(void *closure, libcamera::Request *request)\n+{\n+\tInjectControls *obj = static_cast<InjectControls *>(closure);\n+\n+\tlibcamera::ControlList &ctrls = request->controls();\n+\tauto aeEnable = ctrls.get<bool>(libcamera::controls::AeEnable);\n+\tif (!aeEnable)\n+\t\treturn;\n+\n+\tif (*aeEnable) {\n+\t\tif (obj->aeAvailable) {\n+\t\t\tctrls.set(libcamera::controls::ExposureTimeMode,\n+\t\t\t\t  libcamera::controls::ExposureTimeModeAuto);\n+\t\t}\n+\n+\t\tif (obj->agAvailable) {\n+\t\t\tctrls.set(libcamera::controls::AnalogueGainMode,\n+\t\t\t\t  libcamera::controls::AnalogueGainModeAuto);\n+\t\t}\n+\t} else {\n+\t\tif (obj->meAvailable) {\n+\t\t\tctrls.set(libcamera::controls::ExposureTimeMode,\n+\t\t\t\t  libcamera::controls::ExposureTimeModeManual);\n+\t\t}\n+\n+\t\tif (obj->mgAvailable) {\n+\t\t\tctrls.set(libcamera::controls::AnalogueGainMode,\n+\t\t\t\t  libcamera::controls::AnalogueGainModeManual);\n+\t\t}\n+\t}\n+}\n+\n+libcamera::ControlList requestCompleted([[maybe_unused]] void *closure,\n+\t\t\t\t\tlibcamera::Request *request)\n+{\n+\tconst libcamera::ControlList &metadata = request->metadata();\n+\tlibcamera::ControlList ret(libcamera::controls::controls);\n+\n+\tauto eMode = metadata.get<int>(libcamera::controls::ExposureTimeMode);\n+\tauto aMode = metadata.get<int>(libcamera::controls::AnalogueGainMode);\n+\n+\tif (!eMode && !aMode)\n+\t\treturn ret;\n+\n+\tbool ae = eMode && eMode == libcamera::controls::ExposureTimeModeAuto;\n+\tbool me = eMode && eMode == libcamera::controls::ExposureTimeModeManual;\n+\tbool ag = aMode && aMode == libcamera::controls::AnalogueGainModeAuto;\n+\tbool mg = aMode && aMode == libcamera::controls::AnalogueGainModeManual;\n+\n+\t/* Exposure time not reported at all; use gain only */\n+\tif (!ae && !me) {\n+\t\tret.set(libcamera::controls::AeEnable, ag);\n+\t\treturn ret;\n+\t}\n+\n+\t/* Analogue gain not reported at all; use exposure time only */\n+\tif (!ag && !mg) {\n+\t\tret.set(libcamera::controls::AeEnable, ae);\n+\t\treturn ret;\n+\t}\n+\n+\t/*\n+\t * Gain mode and exposure mode are not equal; therefore at least one is\n+\t * manual, so set AeEnable to false\n+\t */\n+\tif (ag != ae) {\n+\t\tret.set(libcamera::controls::AeEnable, false);\n+\t\treturn ret;\n+\t}\n+\n+\t/* ag and ae are equal, so just choose one */\n+\tret.set(libcamera::controls::AeEnable, ag);\n+\treturn ret;\n+}\n+\n+namespace libcamera {\n+\n+extern \"C\" {\n+\n+[[gnu::visibility(\"default\")]]\n+struct LayerInfo layerInfo{\n+\t.name = \"inject_controls\",\n+\t.layerAPIVersion = 1,\n+};\n+\n+[[gnu::visibility(\"default\")]]\n+struct LayerInterface layerInterface{\n+\t.init = init,\n+\t.terminate = terminate,\n+\t.bufferCompleted = nullptr,\n+\t.requestCompleted = requestCompleted,\n+\t.disconnected = nullptr,\n+\t.acquire = nullptr,\n+\t.release = nullptr,\n+\t.controls = updateControls,\n+\t.properties = nullptr,\n+\t.configure = nullptr,\n+\t.createRequest = nullptr,\n+\t.queueRequest = queueRequest,\n+\t.start = nullptr,\n+\t.stop = nullptr,\n+};\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/layer/inject_controls/inject_controls.h b/src/layer/inject_controls/inject_controls.h\nnew file mode 100644\nindex 000000000000..042872d27630\n--- /dev/null\n+++ b/src/layer/inject_controls/inject_controls.h\n@@ -0,0 +1,24 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2025, Ideas On Board Oy\n+ *\n+ * Layer implementation for injecting controls\n+ */\n+\n+#pragma once\n+\n+#include <libcamera/controls.h>\n+#include <libcamera/request.h>\n+\n+struct InjectControls {\n+\tbool aeAvailable;\n+\tbool meAvailable;\n+\tbool agAvailable;\n+\tbool mgAvailable;\n+};\n+\n+void *init(const std::string &id);\n+void terminate(void *closure);\n+libcamera::ControlInfoMap::Map updateControls(void *closure, libcamera::ControlInfoMap &ctrls);\n+void queueRequest(void *closure, libcamera::Request *request);\n+libcamera::ControlList requestCompleted([[maybe_unused]] void *closure, libcamera::Request *request);\ndiff --git a/src/layer/inject_controls/meson.build b/src/layer/inject_controls/meson.build\nnew file mode 100644\nindex 000000000000..d1e402746990\n--- /dev/null\n+++ b/src/layer/inject_controls/meson.build\n@@ -0,0 +1,16 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+layer_name = 'layer_inject_controls'\n+\n+layer_inject_sources = files([\n+    'inject_controls.h',\n+    'inject_controls.cpp',\n+])\n+\n+mod = shared_module(layer_name, layer_inject_sources,\n+                    name_prefix : '',\n+                    include_directories : [layer_includes],\n+                    dependencies : libcamera_public,\n+                    gnu_symbol_visibility: 'hidden',\n+                    install : true,\n+                    install_dir : layer_install_dir)\ndiff --git a/src/layer/meson.build b/src/layer/meson.build\nindex 45dded512a13..3d8b70ad2cd2 100644\n--- a/src/layer/meson.build\n+++ b/src/layer/meson.build\n@@ -12,3 +12,5 @@ config_h.set('LAYER_DIR',\n layers_env = environment()\n layers_env.set('LIBCAMERA_LAYER_PATH', meson.current_build_dir())\n meson.add_devenv(layers_env)\n+\n+subdir('inject_controls')\n",
    "prefixes": [
        "v6",
        "7/8"
    ]
}