Show a patch.

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

{
    "id": 16417,
    "url": "https://patchwork.libcamera.org/api/patches/16417/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/16417/",
    "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": "<20220629070416.17550-12-tomi.valkeinen@ideasonboard.com>",
    "date": "2022-06-29T07:04:13",
    "name": "[libcamera-devel,v2,11/14] py: New event handling",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "10c0dc83ce2cc5deedfb5e9cd05dcc2497b5101b",
    "submitter": {
        "id": 109,
        "url": "https://patchwork.libcamera.org/api/people/109/?format=api",
        "name": "Tomi Valkeinen",
        "email": "tomi.valkeinen@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/16417/mbox/",
    "series": [
        {
            "id": 3230,
            "url": "https://patchwork.libcamera.org/api/series/3230/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3230",
            "date": "2022-06-29T07:04:02",
            "name": "Python bindings event handling",
            "version": 2,
            "mbox": "https://patchwork.libcamera.org/series/3230/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/16417/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/16417/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 8F546BE173\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 29 Jun 2022 07:04:58 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 247B26565F;\n\tWed, 29 Jun 2022 09:04:58 +0200 (CEST)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B60E96563A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 29 Jun 2022 09:04:46 +0200 (CEST)",
            "from deskari.lan (91-158-154-79.elisa-laajakaista.fi\n\t[91.158.154.79])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 0BAE76D1;\n\tWed, 29 Jun 2022 09:04:46 +0200 (CEST)"
        ],
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1656486298;\n\tbh=Bq8k8rOb0O7mkL+i6snxoUc9XlhC/lVk2HbitiQO87o=;\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=KjQhtcvFrysNDoQOXGrdSx3/767GS5u8uEsP66T3mp+KCflKjDe1l4/sfgQCvDkk8\n\t7TrWsGYJOraXGqQIiezfpEG8QR9V4EPpJLe5uY6/u2EWGuaJXBuW4hiwkgjMub83Gf\n\tLRZPLvAdipTbYdA/7bn+EpHS7RX/AG4i/uPpAXPGyz+otEaNaqSJLfgQ25/G5y61TP\n\taMZNk/x5CjBLJZm7cxSkvRXZtR69vFJSLC55yDNdgXv2yaDre/q1W1iu7O/I1UmLUw\n\tc8Xam5xMhEr9/zX48s3FoWypK8L0/idEViGNKM3pKwZUZbNcBjEmIhX/wjTo8vazYK\n\tQR2hx++HNkbQA==",
            "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1656486286;\n\tbh=Bq8k8rOb0O7mkL+i6snxoUc9XlhC/lVk2HbitiQO87o=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=Tt6OapQP0crZ850xpZkMpjqBpwqVb1YOKHdQNM+U7qz0NQWPFtFyOkCvcLDZphm+u\n\totUNiqJ/LGj0LB9Wk1FV7+G2GNddk4IdWUOzCm6sUy6dZsFO1sCWeJXT2TUxMy1NHg\n\tAzMUAMYlaEMzUfWq/hFr3vNe6B++vt8qKvGhGY/8="
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"Tt6OapQP\"; dkim-atps=neutral",
        "To": "libcamera-devel@lists.libcamera.org,\n\tDavid Plowman <david.plowman@raspberrypi.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tJacopo Mondi <jacopo@jmondi.org>",
        "Date": "Wed, 29 Jun 2022 10:04:13 +0300",
        "Message-Id": "<20220629070416.17550-12-tomi.valkeinen@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.34.1",
        "In-Reply-To": "<20220629070416.17550-1-tomi.valkeinen@ideasonboard.com>",
        "References": "<20220629070416.17550-1-tomi.valkeinen@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v2 11/14] py: New event handling",
        "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": "Tomi Valkeinen via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>",
        "Reply-To": "Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "At the moment the Python bindings only handle the requestCompleted\nevents. But we have others, bufferCompleted and disconnect from the\nCamera, and cameraAdded and cameraRemoved from the CameraManager.\n\nThis makes all those events available to the users.\n\nThe get_ready_requests() method is now deprecated, but available to keep\nbackward compatibility.\n\nThe new event handling works as follows:\n\nThe user sets callbacks to the CameraManager or Cameras (e.g.\nCamera.buffer_completed). When the event_fd informs of an event, the\nuser must call CameraManager.dispatch_events() which gets the queued\nevents and calls the relevant callbacks for each queued event.\n\nAdditionally there is CameraManager.discard_events() if the user does\nnot want to process the events but wants to clear the event queue (e.g.\nafter stopping the cameras or when exiting the app).\n\nI'm not very happy with this patch. It works fine, but there's a lot of\nrepetition of almost-the-same code. Perhaps some template magics might\nreduce the repetition, but I also fear that it can easily make the code\nmore difficult to read.\n\nTODO: Documentation.\n\nSigned-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n---\n src/py/libcamera/py_camera_manager.cpp | 250 +++++++++++++++++++++++--\n src/py/libcamera/py_camera_manager.h   |  64 ++++++-\n src/py/libcamera/py_main.cpp           |  89 ++++++++-\n 3 files changed, 381 insertions(+), 22 deletions(-)",
    "diff": "diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp\nindex 3dd8668e..599a9f7e 100644\n--- a/src/py/libcamera/py_camera_manager.cpp\n+++ b/src/py/libcamera/py_camera_manager.cpp\n@@ -58,6 +58,7 @@ py::list PyCameraManager::cameras()\n \treturn l;\n }\n \n+/* DEPRECATED */\n std::vector<py::object> PyCameraManager::getReadyRequests()\n {\n \tint ret = readFd();\n@@ -70,21 +71,234 @@ std::vector<py::object> PyCameraManager::getReadyRequests()\n \n \tstd::vector<py::object> py_reqs;\n \n-\tfor (Request *request : getCompletedRequests()) {\n-\t\tpy::object o = py::cast(request);\n-\t\t/* Decrease the ref increased in Camera.queue_request() */\n-\t\to.dec_ref();\n-\t\tpy_reqs.push_back(o);\n+\tfor (const auto &ev : getEvents()) {\n+\t\tswitch (ev.type_) {\n+\t\tcase CameraEvent::Type::RequestCompleted: {\n+\t\t\tpy::object o = py::cast(ev.request_);\n+\t\t\t/* Decrease the ref increased in Camera.queue_request() */\n+\t\t\to.dec_ref();\n+\t\t\tpy_reqs.push_back(o);\n+\t\t}\n+\t\tdefault:\n+\t\t\t/* ignore */\n+\t\t\tbreak;\n+\t\t}\n \t}\n \n \treturn py_reqs;\n }\n \n /* Note: Called from another thread */\n-void PyCameraManager::handleRequestCompleted(Request *req)\n+void PyCameraManager::handleBufferCompleted(std::shared_ptr<Camera> cam, Request *req, FrameBuffer *fb)\n {\n-\tpushRequest(req);\n-\twriteFd();\n+\tCameraEvent ev(CameraEvent::Type::BufferCompleted, cam);\n+\tev.request_ = req;\n+\tev.fb_ = fb;\n+\n+\tpushEvent(ev);\n+}\n+\n+/* Note: Called from another thread */\n+void PyCameraManager::handleRequestCompleted(std::shared_ptr<Camera> cam, Request *req)\n+{\n+\tCameraEvent ev(CameraEvent::Type::RequestCompleted, cam);\n+\tev.request_ = req;\n+\n+\tpushEvent(ev);\n+}\n+\n+/* Note: Called from another thread */\n+void PyCameraManager::handleDisconnected(std::shared_ptr<Camera> cam)\n+{\n+\tCameraEvent ev(CameraEvent::Type::Disconnect, cam);\n+\n+\tpushEvent(ev);\n+}\n+\n+/* Note: Called from another thread */\n+void PyCameraManager::handleCameraAdded(std::shared_ptr<Camera> cam)\n+{\n+\tCameraEvent ev(CameraEvent::Type::CameraAdded, cam);\n+\n+\tpushEvent(ev);\n+}\n+\n+/* Note: Called from another thread */\n+void PyCameraManager::handleCameraRemoved(std::shared_ptr<Camera> cam)\n+{\n+\tCameraEvent ev(CameraEvent::Type::CameraRemoved, cam);\n+\n+\tpushEvent(ev);\n+}\n+\n+void PyCameraManager::dispatchEvents()\n+{\n+\tint ret = readFd();\n+\n+\tif (ret == EAGAIN) {\n+\t\tLOG(Python, Debug) << \"No events to dispatch\";\n+\t\treturn;\n+\t}\n+\n+\tif (ret != 0)\n+\t\tthrow std::system_error(ret, std::generic_category());\n+\n+\tstd::vector<CameraEvent> events = getEvents();\n+\n+\tLOG(Python, Debug) << \"Dispatch \" << events.size() << \" events\";\n+\n+\tfor (const auto &event : events) {\n+\t\tstd::shared_ptr<Camera> camera = event.camera_;\n+\n+\t\tswitch (event.type_) {\n+\t\tcase CameraEvent::Type::CameraAdded: {\n+\t\t\tif (cameraAddedHandler_)\n+\t\t\t\tcameraAddedHandler_(camera);\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase CameraEvent::Type::CameraRemoved: {\n+\t\t\tif (cameraRemovedHandler_)\n+\t\t\t\tcameraRemovedHandler_(camera);\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase CameraEvent::Type::BufferCompleted: {\n+\t\t\tauto cb = getBufferCompleted(camera.get());\n+\t\t\tif (cb)\n+\t\t\t\tcb(camera, event.request_, event.fb_);\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase CameraEvent::Type::RequestCompleted: {\n+\t\t\tauto cb = getRequestCompleted(camera.get());\n+\t\t\tif (cb)\n+\t\t\t\tcb(camera, event.request_);\n+\n+\t\t\t/* Decrease the ref increased in Camera.queue_request() */\n+\t\t\tpy::object o = py::cast(event.request_);\n+\t\t\to.dec_ref();\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase CameraEvent::Type::Disconnect: {\n+\t\t\tauto cb = getDisconnected(camera.get());\n+\t\t\tif (cb)\n+\t\t\t\tcb(camera);\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tdefault:\n+\t\t\tASSERT(false);\n+\t\t}\n+\t}\n+}\n+\n+void PyCameraManager::discardEvents()\n+{\n+\tint ret = readFd();\n+\n+\tif (ret == EAGAIN)\n+\t\treturn;\n+\n+\tif (ret != 0)\n+\t\tthrow std::system_error(ret, std::generic_category());\n+\n+\tstd::vector<CameraEvent> v = getEvents();\n+\n+\tLOG(Python, Debug) << \"Discard \" << v.size() << \" events\";\n+\n+\tfor (const auto &ev : v) {\n+\t\tif (ev.type_ != CameraEvent::Type::RequestCompleted)\n+\t\t\tcontinue;\n+\n+\t\t/* Decrease the ref increased in Camera.queue_request() */\n+\t\tpy::object o = py::cast(ev.request_);\n+\t\to.dec_ref();\n+\t}\n+}\n+\n+std::function<void(std::shared_ptr<Camera>)> PyCameraManager::getCameraAdded() const\n+{\n+\treturn cameraAddedHandler_;\n+}\n+\n+void PyCameraManager::setCameraAdded(std::function<void(std::shared_ptr<Camera>)> func)\n+{\n+\tif (cameraAddedHandler_)\n+\t\tcameraManager_->cameraAdded.disconnect();\n+\n+\tcameraAddedHandler_ = func;\n+\n+\tif (func)\n+\t\tcameraManager_->cameraAdded.connect(this, &PyCameraManager::handleCameraAdded);\n+}\n+\n+std::function<void(std::shared_ptr<Camera>)> PyCameraManager::getCameraRemoved() const\n+{\n+\treturn cameraRemovedHandler_;\n+}\n+\n+void PyCameraManager::setCameraRemoved(std::function<void(std::shared_ptr<Camera>)> func)\n+{\n+\tif (cameraRemovedHandler_)\n+\t\tcameraManager_->cameraRemoved.disconnect();\n+\n+\tcameraRemovedHandler_ = func;\n+\n+\tif (func)\n+\t\tcameraManager_->cameraRemoved.connect(this, &PyCameraManager::handleCameraRemoved);\n+}\n+\n+std::function<void(std::shared_ptr<Camera>, Request *)> PyCameraManager::getRequestCompleted(Camera *cam)\n+{\n+\tif (auto it = cameraRequestCompletedHandlers_.find(cam);\n+\t    it != cameraRequestCompletedHandlers_.end())\n+\t\treturn it->second;\n+\n+\treturn nullptr;\n+}\n+\n+void PyCameraManager::setRequestCompleted(Camera *cam, std::function<void(std::shared_ptr<Camera>, Request *)> func)\n+{\n+\tif (func)\n+\t\tcameraRequestCompletedHandlers_[cam] = func;\n+\telse\n+\t\tcameraRequestCompletedHandlers_.erase(cam);\n+}\n+\n+std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> PyCameraManager::getBufferCompleted(Camera *cam)\n+{\n+\tif (auto it = cameraBufferCompletedHandlers_.find(cam);\n+\t    it != cameraBufferCompletedHandlers_.end())\n+\t\treturn it->second;\n+\n+\treturn nullptr;\n+}\n+\n+void PyCameraManager::setBufferCompleted(Camera *cam, std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> func)\n+{\n+\tif (func)\n+\t\tcameraBufferCompletedHandlers_[cam] = func;\n+\telse\n+\t\tcameraBufferCompletedHandlers_.erase(cam);\n+}\n+\n+std::function<void(std::shared_ptr<Camera>)> PyCameraManager::getDisconnected(Camera *cam)\n+{\n+\tif (auto it = cameraDisconnectHandlers_.find(cam);\n+\t    it != cameraDisconnectHandlers_.end())\n+\t\treturn it->second;\n+\n+\treturn nullptr;\n+}\n+\n+void PyCameraManager::setDisconnected(Camera *cam, std::function<void(std::shared_ptr<Camera>)> func)\n+{\n+\tif (func)\n+\t\tcameraDisconnectHandlers_[cam] = func;\n+\telse\n+\t\tcameraDisconnectHandlers_.erase(cam);\n }\n \n void PyCameraManager::writeFd()\n@@ -110,16 +324,22 @@ int PyCameraManager::readFd()\n \treturn 0;\n }\n \n-void PyCameraManager::pushRequest(Request *req)\n+void PyCameraManager::pushEvent(const CameraEvent &ev)\n {\n-\tMutexLocker guard(completedRequestsMutex_);\n-\tcompletedRequests_.push_back(req);\n+\tMutexLocker guard(cameraEventsMutex_);\n+\tcameraEvents_.push_back(ev);\n+\n+\twriteFd();\n+\n+\tLOG(Python, Debug) << \"Queued events: \" << cameraEvents_.size();\n }\n \n-std::vector<Request *> PyCameraManager::getCompletedRequests()\n+std::vector<PyCameraManager::CameraEvent> PyCameraManager::getEvents()\n {\n-\tstd::vector<Request *> v;\n-\tMutexLocker guard(completedRequestsMutex_);\n-\tswap(v, completedRequests_);\n+\tstd::vector<CameraEvent> v;\n+\n+\tMutexLocker guard(cameraEventsMutex_);\n+\tswap(v, cameraEvents_);\n+\n \treturn v;\n }\ndiff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h\nindex 3525057d..aa51a6bc 100644\n--- a/src/py/libcamera/py_camera_manager.h\n+++ b/src/py/libcamera/py_camera_manager.h\n@@ -30,16 +30,70 @@ public:\n \n \tvoid handleRequestCompleted(Request *req);\n \n+\tvoid handleBufferCompleted(std::shared_ptr<Camera> cam, Request *req, FrameBuffer *fb);\n+\tvoid handleRequestCompleted(std::shared_ptr<Camera> cam, Request *req);\n+\tvoid handleDisconnected(std::shared_ptr<Camera> cam);\n+\tvoid handleCameraAdded(std::shared_ptr<Camera> cam);\n+\tvoid handleCameraRemoved(std::shared_ptr<Camera> cam);\n+\n+\tvoid dispatchEvents();\n+\tvoid discardEvents();\n+\n+\tstd::function<void(std::shared_ptr<Camera>)> getCameraAdded() const;\n+\tvoid setCameraAdded(std::function<void(std::shared_ptr<Camera>)> func);\n+\n+\tstd::function<void(std::shared_ptr<Camera>)> getCameraRemoved() const;\n+\tvoid setCameraRemoved(std::function<void(std::shared_ptr<Camera>)> func);\n+\n+\tstd::function<void(std::shared_ptr<Camera>, Request *)> getRequestCompleted(Camera *cam);\n+\tvoid setRequestCompleted(Camera *cam, std::function<void(std::shared_ptr<Camera>, Request *)> func);\n+\n+\tstd::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> getBufferCompleted(Camera *cam);\n+\tvoid setBufferCompleted(Camera *cam, std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> func);\n+\n+\tstd::function<void(std::shared_ptr<Camera>)> getDisconnected(Camera *cam);\n+\tvoid setDisconnected(Camera *cam, std::function<void(std::shared_ptr<Camera>)> func);\n+\n private:\n+\tstruct CameraEvent {\n+\t\tenum class Type {\n+\t\t\tUndefined = 0,\n+\t\t\tCameraAdded,\n+\t\t\tCameraRemoved,\n+\t\t\tDisconnect,\n+\t\t\tRequestCompleted,\n+\t\t\tBufferCompleted,\n+\t\t};\n+\n+\t\tCameraEvent(Type type, std::shared_ptr<Camera> camera)\n+\t\t\t: type_(type), camera_(camera)\n+\t\t{\n+\t\t}\n+\n+\t\tType type_;\n+\n+\t\tstd::shared_ptr<Camera> camera_;\n+\n+\t\tRequest *request_ = nullptr;\n+\t\tFrameBuffer *fb_ = nullptr;\n+\t};\n+\n \tstd::unique_ptr<CameraManager> cameraManager_;\n \n \tUniqueFD eventFd_;\n-\tlibcamera::Mutex completedRequestsMutex_;\n-\tstd::vector<Request *> completedRequests_\n-\t\tLIBCAMERA_TSA_GUARDED_BY(completedRequestsMutex_);\n+\tlibcamera::Mutex cameraEventsMutex_;\n+\tstd::vector<CameraEvent> cameraEvents_\n+\t\tLIBCAMERA_TSA_GUARDED_BY(cameraEvents_);\n+\n+\tstd::function<void(std::shared_ptr<Camera>)> cameraAddedHandler_;\n+\tstd::function<void(std::shared_ptr<Camera>)> cameraRemovedHandler_;\n+\n+\tstd::map<Camera *, std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)>> cameraBufferCompletedHandlers_;\n+\tstd::map<Camera *, std::function<void(std::shared_ptr<Camera>, Request *)>> cameraRequestCompletedHandlers_;\n+\tstd::map<Camera *, std::function<void(std::shared_ptr<Camera>)>> cameraDisconnectHandlers_;\n \n \tvoid writeFd();\n \tint readFd();\n-\tvoid pushRequest(Request *req);\n-\tstd::vector<Request *> getCompletedRequests();\n+\tvoid pushEvent(const CameraEvent &ev);\n+\tstd::vector<CameraEvent> getEvents();\n };\ndiff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp\nindex 1ef1384e..a07f06c4 100644\n--- a/src/py/libcamera/py_main.cpp\n+++ b/src/py/libcamera/py_main.cpp\n@@ -107,7 +107,20 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t.def_property_readonly(\"cameras\", &PyCameraManager::cameras)\n \n \t\t.def_property_readonly(\"event_fd\", &PyCameraManager::eventFd)\n-\t\t.def(\"get_ready_requests\", &PyCameraManager::getReadyRequests);\n+\n+\t\t/* DEPRECATED */\n+\t\t.def(\"get_ready_requests\", &PyCameraManager::getReadyRequests)\n+\n+\t\t.def(\"dispatch_events\", &PyCameraManager::dispatchEvents)\n+\t\t.def(\"discard_events\", &PyCameraManager::discardEvents)\n+\n+\t\t.def_property(\"camera_added\",\n+\t\t              &PyCameraManager::getCameraAdded,\n+\t\t              &PyCameraManager::setCameraAdded)\n+\n+\t\t.def_property(\"camera_removed\",\n+\t\t              &PyCameraManager::getCameraRemoved,\n+\t\t              &PyCameraManager::setCameraRemoved);\n \n \tpyCamera\n \t\t.def_property_readonly(\"id\", &Camera::id)\n@@ -131,7 +144,14 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t\tauto cm = gCameraManager.lock();\n \t\t\tASSERT(cm);\n \n-\t\t\tself.requestCompleted.connect(cm.get(), &PyCameraManager::handleRequestCompleted);\n+\t\t\t/*\n+\t\t\t * Note: We always subscribe requestComplete, as the bindings\n+\t\t\t * use requestComplete event to decrement the Request refcount-\n+\t\t\t */\n+\n+\t\t\tself.requestCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req) {\n+\t\t\t\tcm->handleRequestCompleted(camera, req);\n+\t\t\t});\n \n \t\t\tControlList controlList(self.controls());\n \n@@ -159,6 +179,71 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t\t\t                        \"Failed to start camera\");\n \t\t})\n \n+\t\t.def_property(\"request_completed\",\n+\t\t[](Camera &self) {\n+\t\t\tauto cm = gCameraManager.lock();\n+\t\t\tASSERT(cm);\n+\n+\t\t\treturn cm->getRequestCompleted(&self);\n+\t\t},\n+\t\t[](Camera &self, std::function<void(std::shared_ptr<Camera>, Request *)> f) {\n+\t\t\tauto cm = gCameraManager.lock();\n+\t\t\tASSERT(cm);\n+\n+\t\t\tcm->setRequestCompleted(&self, f);\n+\n+\t\t\t/*\n+\t\t\t * Note: We do not subscribe requestComplete here, as we\n+\t\t\t * do that in the start() method.\n+\t\t\t */\n+\t\t})\n+\n+\t\t.def_property(\"buffer_completed\",\n+\t\t[](Camera &self) -> std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> {\n+\t\t\tauto cm = gCameraManager.lock();\n+\t\t\tASSERT(cm);\n+\n+\t\t\treturn cm->getBufferCompleted(&self);\n+\t\t},\n+\t\t[](Camera &self, std::function<void(std::shared_ptr<Camera>, Request *, FrameBuffer *)> f) {\n+\t\t\tauto cm = gCameraManager.lock();\n+\t\t\tASSERT(cm);\n+\n+\t\t\tcm->setBufferCompleted(&self, f);\n+\n+\t\t\tself.bufferCompleted.disconnect();\n+\n+\t\t\tif (!f)\n+\t\t\t\treturn;\n+\n+\t\t\tself.bufferCompleted.connect(&self, [cm, camera=self.shared_from_this()](Request *req, FrameBuffer *fb) {\n+\t\t\t\tcm->handleBufferCompleted(camera, req, fb);\n+\t\t\t});\n+\t\t})\n+\n+\t\t.def_property(\"disconnected\",\n+\t\t[](Camera &self) -> std::function<void(std::shared_ptr<Camera>)> {\n+\t\t\tauto cm = gCameraManager.lock();\n+\t\t\tASSERT(cm);\n+\n+\t\t\treturn cm->getDisconnected(&self);\n+\t\t},\n+\t\t[](Camera &self, std::function<void(std::shared_ptr<Camera>)> f) {\n+\t\t\tauto cm = gCameraManager.lock();\n+\t\t\tASSERT(cm);\n+\n+\t\t\tcm->setDisconnected(&self, f);\n+\n+\t\t\tself.disconnected.disconnect();\n+\n+\t\t\tif (!f)\n+\t\t\t\treturn;\n+\n+\t\t\tself.disconnected.connect(&self, [cm, camera=self.shared_from_this()]() {\n+\t\t\t\tcm->handleDisconnected(camera);\n+\t\t\t});\n+\t\t})\n+\n \t\t.def(\"__str__\", [](Camera &self) {\n \t\t\treturn \"<libcamera.Camera '\" + self.id() + \"'>\";\n \t\t})\n",
    "prefixes": [
        "libcamera-devel",
        "v2",
        "11/14"
    ]
}