Patch Detail
Show a patch.
GET /api/patches/18363/?format=api
{ "id": 18363, "url": "https://patchwork.libcamera.org/api/patches/18363/?format=api", "web_url": "https://patchwork.libcamera.org/patch/18363/", "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": "<20230309142601.70556-3-tomi.valkeinen@ideasonboard.com>", "date": "2023-03-09T14:25:48", "name": "[libcamera-devel,v4,02/15] py: New event handling", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "c5687edcc156337a753be0de389089b287b38877", "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/18363/mbox/", "series": [ { "id": 3796, "url": "https://patchwork.libcamera.org/api/series/3796/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3796", "date": "2023-03-09T14:25:46", "name": "py: New python bindings event handling", "version": 4, "mbox": "https://patchwork.libcamera.org/series/3796/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/18363/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/18363/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 2EF1AC3262\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 9 Mar 2023 14:26:23 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E0851626E3;\n\tThu, 9 Mar 2023 15:26:18 +0100 (CET)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1305A626D5\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 9 Mar 2023 15:26:17 +0100 (CET)", "from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 97ECF589;\n\tThu, 9 Mar 2023 15:26:16 +0100 (CET)" ], "DKIM-Signature": [ "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1678371978;\n\tbh=YKfZjDmtlRzCbiYHT8kiSClzauOYlnIxVzkNSZoTeJo=;\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=iGdzelFH9dVeQouBmmZlgYmDzkCRj9me1WRm/qrqMccPrjX1GLpE1KM5iAKAH/bSu\n\t85bbusTcLUGmzlZsnuVJ0RfhwcsPUe8GAdNsoHok+/NMNRbWHb3hvi9YomtBc5f71U\n\tvCsXr+VI4ykmiK279kAjYKtVG6PLjWQGHuZWsh6ftc04dO7gczodKfE7pfHR3KMb2v\n\td5ba3bDox2HUnRTGcPm6rIpcKIb/lMrzU91/BeQcLoK8I+tn6G5Qv36w5pHkUnyJGw\n\tp4iynd8rETLn77XPmYMQflVyJ8TMFlOhAhHOBA0wQutt9SyWRbyoBxmcyaDG2LReYC\n\tq1NKvWbt1lcgQ==", "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1678371976;\n\tbh=YKfZjDmtlRzCbiYHT8kiSClzauOYlnIxVzkNSZoTeJo=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=o3rB1vhuk1UCD3j1NGJcLcZSXKouYFZco8FdBZd4xi5K0Olw2tXCvCbUnM+z2z00b\n\t6FxtRzyjYSYZVIoAaWS4Ci/Ujkr8/USqlMtIvk9VIcP9GPsPjiAWP0Lsc+f+bha1qe\n\tzyvm16dzMl/UTBorWNLopI1l4tI3cr5+O/sRqCF0=" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"o3rB1vhu\"; dkim-atps=neutral", "To": "libcamera-devel@lists.libcamera.org", "Date": "Thu, 9 Mar 2023 16:25:48 +0200", "Message-Id": "<20230309142601.70556-3-tomi.valkeinen@ideasonboard.com>", "X-Mailer": "git-send-email 2.34.1", "In-Reply-To": "<20230309142601.70556-1-tomi.valkeinen@ideasonboard.com>", "References": "<20230309142601.70556-1-tomi.valkeinen@ideasonboard.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "Subject": "[libcamera-devel] [PATCH v4 02/15] 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 patch 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 like get_ready_requests(), but instead of\nreturning only Requests from requestCompleted events, it returns all\nevent types using a new Event class.\n\nAdditionally camera.stop() has been changed to return events for that\ncamera. This serves two purposes: first, it removes the requestCompleted\nevents from the event queue, thus preventing the old events being\nreturned when the camera is started again, and second, it allows the\nuser to process those events if it so wants.\n\nAll other event types are always collected and returned, except\nbufferCompleted which needs to be enabled by setting the\nCameraManager.buffer_completed_active to True. This is to reduce the\noverhead a bit. It is not clear if this is a necessary optimization or\nnot.\n\nTODO: Documentation.\n\nSigned-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n---\n src/py/libcamera/py_camera_manager.cpp | 195 +++++++++++++++++++++++--\n src/py/libcamera/py_camera_manager.h | 66 ++++++++-\n src/py/libcamera/py_main.cpp | 44 +++++-\n 3 files changed, 281 insertions(+), 24 deletions(-)", "diff": "diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp\nindex 9ccb7aad..7d6dded4 100644\n--- a/src/py/libcamera/py_camera_manager.cpp\n+++ b/src/py/libcamera/py_camera_manager.cpp\n@@ -5,6 +5,7 @@\n \n #include \"py_camera_manager.h\"\n \n+#include <algorithm>\n #include <errno.h>\n #include <memory>\n #include <sys/eventfd.h>\n@@ -35,11 +36,17 @@ PyCameraManager::PyCameraManager()\n \tif (ret)\n \t\tthrow std::system_error(-ret, std::generic_category(),\n \t\t\t\t\t\"Failed to start CameraManager\");\n+\n+\tcameraManager_->cameraAdded.connect(this, &PyCameraManager::handleCameraAdded);\n+\tcameraManager_->cameraRemoved.connect(this, &PyCameraManager::handleCameraRemoved);\n }\n \n PyCameraManager::~PyCameraManager()\n {\n \tLOG(Python, Debug) << \"~PyCameraManager()\";\n+\n+\tcameraManager_->cameraAdded.disconnect();\n+\tcameraManager_->cameraRemoved.disconnect();\n }\n \n py::list PyCameraManager::cameras()\n@@ -60,6 +67,43 @@ py::list PyCameraManager::cameras()\n \treturn l;\n }\n \n+PyCameraEvent PyCameraManager::convertEvent(const CameraEvent &event)\n+{\n+\t/*\n+\t * We need to set a keep-alive here so that the camera keeps the\n+\t * camera manager alive.\n+\t */\n+\tpy::object py_cm = py::cast(this);\n+\tpy::object py_cam = py::cast(event.camera_);\n+\tpy::detail::keep_alive_impl(py_cam, py_cm);\n+\n+\tPyCameraEvent pyevent(event.type_, py_cam);\n+\n+\tswitch (event.type_) {\n+\tcase CameraEventType::CameraAdded:\n+\tcase CameraEventType::CameraRemoved:\n+\tcase CameraEventType::Disconnect:\n+\t\t/* No additional parameters to add */\n+\t\tbreak;\n+\n+\tcase CameraEventType::BufferCompleted:\n+\t\tpyevent.request_ = py::cast(event.request_);\n+\t\tpyevent.fb_ = py::cast(event.fb_);\n+\t\tbreak;\n+\n+\tcase CameraEventType::RequestCompleted:\n+\t\tpyevent.request_ = py::cast(event.request_);\n+\n+\t\t/* Decrease the ref increased in Camera.queue_request() */\n+\t\tpyevent.request_.dec_ref();\n+\n+\t\tbreak;\n+\t}\n+\n+\treturn pyevent;\n+}\n+\n+/* DEPRECATED */\n std::vector<py::object> PyCameraManager::getReadyRequests()\n {\n \tint ret = readFd();\n@@ -72,21 +116,134 @@ 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\tif (ev.type_ != CameraEventType::RequestCompleted)\n+\t\t\tcontinue;\n+\n+\t\tPyCameraEvent pyev = convertEvent(ev);\n+\t\tpy_reqs.push_back(pyev.request_);\n \t}\n \n \treturn py_reqs;\n }\n \n+std::vector<PyCameraEvent> PyCameraManager::getPyEvents()\n+{\n+\tint ret = readFd();\n+\n+\tif (ret == EAGAIN) {\n+\t\tLOG(Python, Debug) << \"No events\";\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) << \"Got \" << events.size() << \" events\";\n+\n+\tstd::vector<PyCameraEvent> pyevents;\n+\tpyevents.reserve(events.size());\n+\n+\tstd::transform(events.begin(), events.end(), std::back_inserter(pyevents),\n+\t\t [this](const CameraEvent &ev) {\n+\t\t\t return convertEvent(ev);\n+\t\t });\n+\n+\treturn pyevents;\n+}\n+\n+static bool isCameraSpecificEvent(const CameraEvent &event, std::shared_ptr<Camera> &camera)\n+{\n+\treturn event.camera_ == camera &&\n+\t (event.type_ == CameraEventType::RequestCompleted ||\n+\t\tevent.type_ == CameraEventType::BufferCompleted ||\n+\t\tevent.type_ == CameraEventType::Disconnect);\n+}\n+\n+std::vector<PyCameraEvent> PyCameraManager::getPyCameraEvents(std::shared_ptr<Camera> camera)\n+{\n+\tstd::vector<CameraEvent> events;\n+\tsize_t unhandled_size;\n+\n+\t{\n+\t\tMutexLocker guard(eventsMutex_);\n+\n+\t\t/*\n+\t\t * Collect events related to the given camera and remove them\n+\t\t * from the events_ vector.\n+\t\t */\n+\n+\t\tauto it = events_.begin();\n+\t\twhile (it != events_.end()) {\n+\t\t\tif (isCameraSpecificEvent(*it, camera)) {\n+\t\t\t\tevents.push_back(*it);\n+\t\t\t\tit = events_.erase(it);\n+\t\t\t} else {\n+\t\t\t\tit++;\n+\t\t\t}\n+\t\t}\n+\n+\t\tunhandled_size = events_.size();\n+\t}\n+\n+\t/* Convert events to Python events */\n+\n+\tstd::vector<PyCameraEvent> pyevents;\n+\n+\tfor (const auto &event : events) {\n+\t\tPyCameraEvent pyev = convertEvent(event);\n+\t\tpyevents.push_back(pyev);\n+\t}\n+\n+\tLOG(Python, Debug) << \"Got \" << pyevents.size() << \" camera events, \"\n+\t\t\t << unhandled_size << \" unhandled events left\";\n+\n+\treturn pyevents;\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+\tif (!bufferCompletedEventActive_)\n+\t\treturn;\n+\n+\tCameraEvent ev(CameraEventType::BufferCompleted, cam, req, 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(CameraEventType::RequestCompleted, cam, 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(CameraEventType::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(CameraEventType::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(CameraEventType::CameraRemoved, cam);\n+\n+\tpushEvent(ev);\n }\n \n void PyCameraManager::writeFd()\n@@ -116,16 +273,24 @@ int PyCameraManager::readFd()\n \t\treturn -EIO;\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+\t{\n+\t\tMutexLocker guard(eventsMutex_);\n+\t\tevents_.push_back(ev);\n+\t}\n+\n+\twriteFd();\n+\n+\tLOG(Python, Debug) << \"Queued events: \" << events_.size();\n }\n \n-std::vector<Request *> PyCameraManager::getCompletedRequests()\n+std::vector<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(eventsMutex_);\n+\tswap(v, events_);\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..757f6d8e 100644\n--- a/src/py/libcamera/py_camera_manager.h\n+++ b/src/py/libcamera/py_camera_manager.h\n@@ -13,6 +13,48 @@\n \n using namespace libcamera;\n \n+enum class CameraEventType {\n+\tCameraAdded,\n+\tCameraRemoved,\n+\tDisconnect,\n+\tRequestCompleted,\n+\tBufferCompleted,\n+};\n+\n+/*\n+ * This event struct is used internally to queue the events we receive from\n+ * other threads.\n+ */\n+struct CameraEvent {\n+\tCameraEvent(CameraEventType type, std::shared_ptr<Camera> camera,\n+\t\t Request *request = nullptr, FrameBuffer *fb = nullptr)\n+\t\t: type_(type), camera_(camera), request_(request), fb_(fb)\n+\t{\n+\t}\n+\n+\tCameraEventType type_;\n+\tstd::shared_ptr<Camera> camera_;\n+\tRequest *request_;\n+\tFrameBuffer *fb_;\n+};\n+\n+/*\n+ * This event struct is passed to Python. We need to use pybind11::object here\n+ * instead of a C++ pointer so that we keep a ref to the Request, and a\n+ * keep-alive from the camera to the camera manager.\n+ */\n+struct PyCameraEvent {\n+\tPyCameraEvent(CameraEventType type, pybind11::object camera)\n+\t\t: type_(type), camera_(camera)\n+\t{\n+\t}\n+\n+\tCameraEventType type_;\n+\tpybind11::object camera_;\n+\tpybind11::object request_;\n+\tpybind11::object fb_;\n+};\n+\n class PyCameraManager\n {\n public:\n@@ -26,20 +68,30 @@ public:\n \n \tint eventFd() const { return eventFd_.get(); }\n \n-\tstd::vector<pybind11::object> getReadyRequests();\n+\tstd::vector<pybind11::object> getReadyRequests(); /* DEPRECATED */\n+\tstd::vector<PyCameraEvent> getPyEvents();\n+\tstd::vector<PyCameraEvent> getPyCameraEvents(std::shared_ptr<Camera> camera);\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 handleRequestCompleted(Request *req);\n+\tbool bufferCompletedEventActive_ = false;\n \n private:\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 eventsMutex_;\n+\tstd::vector<CameraEvent> events_\n+\t\tLIBCAMERA_TSA_GUARDED_BY(eventsMutex_);\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+\n+\tPyCameraEvent convertEvent(const CameraEvent &event);\n };\ndiff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp\nindex 1d4c23b3..0fffc030 100644\n--- a/src/py/libcamera/py_main.cpp\n+++ b/src/py/libcamera/py_main.cpp\n@@ -61,6 +61,7 @@ PYBIND11_MODULE(_libcamera, m)\n \t * https://pybind11.readthedocs.io/en/latest/advanced/misc.html#avoiding-c-types-in-docstrings\n \t */\n \n+\tauto pyEvent = py::class_<PyCameraEvent>(m, \"Event\");\n \tauto pyCameraManager = py::class_<PyCameraManager>(m, \"CameraManager\");\n \tauto pyCamera = py::class_<Camera>(m, \"Camera\");\n \tauto pyCameraConfiguration = py::class_<CameraConfiguration>(m, \"CameraConfiguration\");\n@@ -93,6 +94,20 @@ PYBIND11_MODULE(_libcamera, m)\n \tm.def(\"log_set_level\", &logSetLevel);\n \n \t/* Classes */\n+\n+\tpy::enum_<CameraEventType>(pyEvent, \"Type\")\n+\t\t.value(\"CameraAdded\", CameraEventType::CameraAdded)\n+\t\t.value(\"CameraRemoved\", CameraEventType::CameraRemoved)\n+\t\t.value(\"Disconnect\", CameraEventType::Disconnect)\n+\t\t.value(\"RequestCompleted\", CameraEventType::RequestCompleted)\n+\t\t.value(\"BufferCompleted\", CameraEventType::BufferCompleted);\n+\n+\tpyEvent\n+\t\t.def_readonly(\"type\", &PyCameraEvent::type_)\n+\t\t.def_readonly(\"camera\", &PyCameraEvent::camera_)\n+\t\t.def_readonly(\"request\", &PyCameraEvent::request_)\n+\t\t.def_readonly(\"fb\", &PyCameraEvent::fb_);\n+\n \tpyCameraManager\n \t\t.def_static(\"singleton\", []() {\n \t\t\tstd::shared_ptr<PyCameraManager> cm = gCameraManager.lock();\n@@ -110,7 +125,13 @@ 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(\"get_events\", &PyCameraManager::getPyEvents)\n+\n+\t\t.def_readwrite(\"buffer_completed_active\", &PyCameraManager::bufferCompletedEventActive_);\n \n \tpyCamera\n \t\t.def_property_readonly(\"id\", &Camera::id)\n@@ -133,7 +154,17 @@ 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\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\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+\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 \n \t\t\tControlList controlList(self.controls());\n \n@@ -154,10 +185,19 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t\tint ret = self.stop();\n \n \t\t\tself.requestCompleted.disconnect();\n+\t\t\tself.bufferCompleted.disconnect();\n+\t\t\tself.disconnected.disconnect();\n+\n+\t\t\tauto cm = gCameraManager.lock();\n+\t\t\tASSERT(cm);\n+\n+\t\t\tauto events = cm->getPyCameraEvents(self.shared_from_this());\n \n \t\t\tif (ret)\n \t\t\t\tthrow std::system_error(-ret, std::generic_category(),\n \t\t\t\t\t\t\t\"Failed to stop camera\");\n+\n+\t\t\treturn events;\n \t\t})\n \n \t\t.def(\"__str__\", [](Camera &self) {\n", "prefixes": [ "libcamera-devel", "v4", "02/15" ] }