Show a patch.

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

{
    "id": 16344,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/16344/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/16344/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/1.1/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": "<20220623144736.78537-7-tomi.valkeinen@ideasonboard.com>",
    "date": "2022-06-23T14:47:35",
    "name": "[libcamera-devel,RFC,v1,6/7] py: New event handling",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": false,
    "hash": "64cb721a8f6927c3525c5062ec38c36a6cd03942",
    "submitter": {
        "id": 109,
        "url": "https://patchwork.libcamera.org/api/1.1/people/109/?format=api",
        "name": "Tomi Valkeinen",
        "email": "tomi.valkeinen@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/16344/mbox/",
    "series": [
        {
            "id": 3213,
            "url": "https://patchwork.libcamera.org/api/1.1/series/3213/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3213",
            "date": "2022-06-23T14:47:29",
            "name": "Python bindings event handling",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/3213/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/16344/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/16344/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 F1582BD808\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 23 Jun 2022 14:48:07 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 63F2265645;\n\tThu, 23 Jun 2022 16:48:07 +0200 (CEST)",
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 95EB465635\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 23 Jun 2022 16:48:00 +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 EA8586BB;\n\tThu, 23 Jun 2022 16:47:59 +0200 (CEST)"
        ],
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1655995687;\n\tbh=M6fjLGQzQLE2U11ZyzyAjSmclu1VWp3cZ+RqxZSq8Hw=;\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=bnuu7ipLJU0B6NRWCe5YPLe5CKglRXvpFrR/ypA6vA7PFedD89JDJ1RmZd3SJw+zs\n\tj40wvRkBEP0pEPbBckLaVazIMw+xafnuVuJfClOJy2/yOrr5wRNrJ29kubWTVPwwfg\n\trLdSLzRtfwWzidBFRef31QRj0faVGDb/7s9OOUrgNteW81PvAnr/Fs6w8889g2wDe4\n\tRqRreUmac6tqNGLM1oATtUbp4+Qaj1J3WTh4hXdpmtl5lly25UjODhk2CPefhzn84H\n\tp5lSjSHzFqa3QEwlK5ut+KPLrvPwohRYH2Qrtht6NaOsCmi9fZp0PGJqI/9REb/qSH\n\tZcN7zdYV9fAVg==",
            "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1655995680;\n\tbh=M6fjLGQzQLE2U11ZyzyAjSmclu1VWp3cZ+RqxZSq8Hw=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=vUVhrQhhjpNu93j9Fc776/SFwXQF7qxk4PROcGCR4ynLhl/ViAzsG3dwhkOAF1iiS\n\tKG6meXzFrC/JHNMldUXF2r1eE7ynTlN1oLB88aXYtZxA57/FuOobhjZ1SMpvvnfG+H\n\tZd8Xjd8vPq4Cr8B6lC6eWfoWZt8jwq7bOMGW/FrU="
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"vUVhrQhh\"; 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": "Thu, 23 Jun 2022 17:47:35 +0300",
        "Message-Id": "<20220623144736.78537-7-tomi.valkeinen@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.34.1",
        "In-Reply-To": "<20220623144736.78537-1-tomi.valkeinen@ideasonboard.com>",
        "References": "<20220623144736.78537-1-tomi.valkeinen@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [RFC v1 6/7] 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 disconnec 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 | 291 +++++++++++++++++++++++--\n src/py/libcamera/py_camera_manager.h   |  41 +++-\n src/py/libcamera/py_main.cpp           |  87 +++++++-\n 3 files changed, 397 insertions(+), 22 deletions(-)",
    "diff": "diff --git a/src/py/libcamera/py_camera_manager.cpp b/src/py/libcamera/py_camera_manager.cpp\nindex ba45f713..bbadb9ee 100644\n--- a/src/py/libcamera/py_camera_manager.cpp\n+++ b/src/py/libcamera/py_camera_manager.cpp\n@@ -15,6 +15,37 @@ namespace py = pybind11;\n \n using namespace libcamera;\n \n+struct CameraEvent {\n+\tenum class EventType {\n+\t\tUndefined = 0,\n+\t\tCameraAdded,\n+\t\tCameraRemoved,\n+\t\tDisconnect,\n+\t\tRequestCompleted,\n+\t\tBufferCompleted,\n+\t};\n+\n+\tCameraEvent(EventType type)\n+\t\t: type(type)\n+\t{\n+\t}\n+\n+\tEventType type;\n+\n+\tstd::shared_ptr<Camera> cam;\n+\n+\tunion {\n+\t\tstruct {\n+\t\t\tRequest *req;\n+\t\t\tFrameBuffer *fb;\n+\t\t} buf_completed;\n+\n+\t\tstruct {\n+\t\t\tRequest *req;\n+\t\t} req_completed;\n+\t};\n+};\n+\n PyCameraManager::PyCameraManager()\n {\n \tint fd = eventfd(0, 0);\n@@ -56,33 +87,261 @@ py::list PyCameraManager::getCameras()\n \treturn l;\n }\n \n+/* DEPRECATED */\n std::vector<py::object> PyCameraManager::getReadyRequests(bool nonBlocking)\n {\n \tif (!nonBlocking || hasEvents())\n \t\treadFd();\n \n-\tstd::vector<Request *> v;\n-\tgetRequests(v);\n+\tstd::vector<CameraEvent> v;\n+\tgetEvents(v);\n \n \tstd::vector<py::object> ret;\n \n-\tfor (Request *req : v) {\n-\t\tpy::object o = py::cast(req);\n-\t\t/* Decrease the ref increased in Camera.queue_request() */\n-\t\to.dec_ref();\n-\t\tret.push_back(o);\n+\tfor (const auto &ev : v) {\n+\t\tswitch (ev.type) {\n+\t\tcase CameraEvent::EventType::RequestCompleted: {\n+\t\t\tRequest *req = ev.req_completed.req;\n+\t\t\tpy::object o = py::cast(req);\n+\t\t\t/* Decrease the ref increased in Camera.queue_request() */\n+\t\t\to.dec_ref();\n+\t\t\tret.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 ret;\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+\tCameraEvent ev(CameraEvent::EventType::BufferCompleted);\n+\tev.cam = cam;\n+\tev.buf_completed.req = req;\n+\tev.buf_completed.fb = fb;\n+\n+\tpushEvent(ev);\n+\twriteFd();\n+}\n+\n+/* Note: Called from another thread */\n+void PyCameraManager::handleRequestCompleted(std::shared_ptr<Camera> cam, Request *req)\n+{\n+\tCameraEvent ev(CameraEvent::EventType::RequestCompleted);\n+\tev.cam = cam;\n+\tev.req_completed.req = req;\n+\n+\tpushEvent(ev);\n+\twriteFd();\n+}\n+\n+/* Note: Called from another thread */\n+void PyCameraManager::handleDisconnected(std::shared_ptr<Camera> cam)\n {\n-\tpushRequest(req);\n+\tCameraEvent ev(CameraEvent::EventType::Disconnect);\n+\tev.cam = cam;\n+\n+\tpushEvent(ev);\n \twriteFd();\n }\n \n+/* Note: Called from another thread */\n+void PyCameraManager::handleCameraAdded(std::shared_ptr<Camera> cam)\n+{\n+\tCameraEvent ev(CameraEvent::EventType::CameraAdded);\n+\tev.cam = cam;\n+\n+\tpushEvent(ev);\n+\twriteFd();\n+}\n+\n+/* Note: Called from another thread */\n+void PyCameraManager::handleCameraRemoved(std::shared_ptr<Camera> cam)\n+{\n+\tCameraEvent ev(CameraEvent::EventType::CameraRemoved);\n+\tev.cam = cam;\n+\n+\tpushEvent(ev);\n+\twriteFd();\n+}\n+\n+void PyCameraManager::dispatchEvents(bool nonBlocking)\n+{\n+\tif (!nonBlocking || hasEvents())\n+\t\treadFd();\n+\n+\tstd::vector<CameraEvent> v;\n+\tgetEvents(v);\n+\n+\tLOG(Python, Debug) << \"Dispatch \" << v.size() << \" events\";\n+\n+\tfor (const auto &ev : v) {\n+\t\tswitch (ev.type) {\n+\t\tcase CameraEvent::EventType::CameraAdded: {\n+\t\t\tstd::shared_ptr<Camera> cam = ev.cam;\n+\n+\t\t\tif (cameraAddedHandler_)\n+\t\t\t\tcameraAddedHandler_(cam);\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase CameraEvent::EventType::CameraRemoved: {\n+\t\t\tstd::shared_ptr<Camera> cam = ev.cam;\n+\n+\t\t\tif (cameraRemovedHandler_)\n+\t\t\t\tcameraRemovedHandler_(cam);\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase CameraEvent::EventType::BufferCompleted: {\n+\t\t\tstd::shared_ptr<Camera> cam = ev.cam;\n+\n+\t\t\tauto cb = getBufferCompleted(cam.get());\n+\n+\t\t\tif (cb)\n+\t\t\t\tcb(cam, ev.buf_completed.req, ev.buf_completed.fb);\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase CameraEvent::EventType::RequestCompleted: {\n+\t\t\tstd::shared_ptr<Camera> cam = ev.cam;\n+\n+\t\t\tauto cb = getRequestCompleted(cam.get());\n+\n+\t\t\tif (cb)\n+\t\t\t\tcb(cam, ev.req_completed.req);\n+\n+\t\t\t/* Decrease the ref increased in Camera.queue_request() */\n+\t\t\tpy::object o = py::cast(ev.req_completed.req);\n+\t\t\to.dec_ref();\n+\n+\t\t\tbreak;\n+\t\t}\n+\t\tcase CameraEvent::EventType::Disconnect: {\n+\t\t\tstd::shared_ptr<Camera> cam = ev.cam;\n+\n+\t\t\tauto cb = getDisconnected(cam.get());\n+\n+\t\t\tif (cb)\n+\t\t\t\tcb(cam);\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+\tif (hasEvents())\n+\t\treadFd();\n+\n+\tstd::vector<CameraEvent> v;\n+\tgetEvents(v);\n+\n+\tLOG(Python, Debug) << \"Discard \" << v.size() << \" events\";\n+\n+\tfor (const auto &ev : v) {\n+\t\tif (ev.type != CameraEvent::EventType::RequestCompleted)\n+\t\t\tcontinue;\n+\n+\t\tstd::shared_ptr<Camera> cam = ev.cam;\n+\n+\t\t/* Decrease the ref increased in Camera.queue_request() */\n+\t\tpy::object o = py::cast(ev.req_completed.req);\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>)> fun)\n+{\n+\tif (cameraAddedHandler_)\n+\t\tcameraAdded.disconnect();\n+\n+\tcameraAddedHandler_ = fun;\n+\n+\tif (fun)\n+\t\tcameraAdded.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>)> fun)\n+{\n+\tif (cameraRemovedHandler_)\n+\t\tcameraRemoved.disconnect();\n+\n+\tcameraRemovedHandler_ = fun;\n+\n+\tif (fun)\n+\t\tcameraRemoved.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 *)> fun)\n+{\n+\tif (fun)\n+\t\tcameraRequestCompletedHandlers_[cam] = fun;\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 *)> fun)\n+{\n+\tif (fun)\n+\t\tcameraBufferCompletedHandlers_[cam] = fun;\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>)> fun)\n+{\n+\tif (fun)\n+\t\tcameraDisconnectHandlers_[cam] = fun;\n+\telse\n+\t\tcameraDisconnectHandlers_.erase(cam);\n+}\n+\n void PyCameraManager::writeFd()\n {\n \tuint64_t v = 1;\n@@ -104,16 +363,18 @@ void PyCameraManager::readFd()\n \t\tthrow std::system_error(errno, std::generic_category());\n }\n \n-void PyCameraManager::pushRequest(Request *req)\n+void PyCameraManager::pushEvent(const CameraEvent &ev)\n {\n-\tstd::lock_guard guard(reqlist_mutex_);\n-\treqList_.push_back(req);\n+\tstd::lock_guard guard(reqlistMutex_);\n+\tcameraEvents_.push_back(ev);\n+\n+\tLOG(Python, Debug) << \"Queued events: \" << cameraEvents_.size();\n }\n \n-void PyCameraManager::getRequests(std::vector<Request *> &v)\n+void PyCameraManager::getEvents(std::vector<CameraEvent> &v)\n {\n-\tstd::lock_guard guard(reqlist_mutex_);\n-\tswap(v, reqList_);\n+\tstd::lock_guard guard(reqlistMutex_);\n+\tswap(v, cameraEvents_);\n }\n \n bool PyCameraManager::hasEvents()\ndiff --git a/src/py/libcamera/py_camera_manager.h b/src/py/libcamera/py_camera_manager.h\nindex 2396d236..fd28291b 100644\n--- a/src/py/libcamera/py_camera_manager.h\n+++ b/src/py/libcamera/py_camera_manager.h\n@@ -13,6 +13,8 @@\n \n using namespace libcamera;\n \n+struct CameraEvent;\n+\n class PyCameraManager : public CameraManager\n {\n public:\n@@ -27,15 +29,46 @@ 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(bool nonBlocking = false);\n+\tvoid discardEvents();\n+\n+\tstd::function<void(std::shared_ptr<Camera>)> getCameraAdded() const;\n+\tvoid setCameraAdded(std::function<void(std::shared_ptr<Camera>)> fun);\n+\n+\tstd::function<void(std::shared_ptr<Camera>)> getCameraRemoved() const;\n+\tvoid setCameraRemoved(std::function<void(std::shared_ptr<Camera>)> fun);\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 *)> fun);\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 *)> fun);\n+\n+\tstd::function<void(std::shared_ptr<Camera>)> getDisconnected(Camera *cam);\n+\tvoid setDisconnected(Camera *cam, std::function<void(std::shared_ptr<Camera>)> fun);\n+\n private:\n \tint eventFd_ = -1;\n-\tstd::mutex reqlist_mutex_;\n+\tstd::mutex reqlistMutex_;\n \tstd::vector<Request *> reqList_;\n+\tstd::vector<CameraEvent> 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 \tvoid readFd();\n-\tvoid pushRequest(Request *req);\n-\tvoid getRequests(std::vector<Request *> &v);\n-\n+\tvoid pushEvent(const CameraEvent &ev);\n+\tvoid getEvents(std::vector<CameraEvent> &v);\n \tbool hasEvents();\n };\ndiff --git a/src/py/libcamera/py_main.cpp b/src/py/libcamera/py_main.cpp\nindex ee4ecb9b..b6fd9a9d 100644\n--- a/src/py/libcamera/py_main.cpp\n+++ b/src/py/libcamera/py_main.cpp\n@@ -103,8 +103,20 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t.def_property_readonly(\"cameras\", &PyCameraManager::getCameras)\n \n \t\t.def_property_readonly(\"event_fd\", &PyCameraManager::eventFd)\n+\t\t/* DEPRECATED */\n \t\t.def(\"get_ready_requests\", &PyCameraManager::getReadyRequests,\n-\t\t     py::arg(\"nonblocking\") = false);\n+\t\t     py::arg(\"nonblocking\") = false)\n+\t\t.def(\"dispatch_events\", &PyCameraManager::dispatchEvents,\n+\t\t     py::arg(\"nonblocking\") = false)\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@@ -117,9 +129,13 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t\tauto cm = gCameraManager.lock();\n \t\t\tassert(cm);\n \n-\t\t\tself.requestCompleted.connect(&self, [cm=std::move(cm)](Request *req) {\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\t\tcm->handleRequestCompleted(req);\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@@ -148,6 +164,71 @@ PYBIND11_MODULE(_libcamera, m)\n \t\t\treturn 0;\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",
        "RFC",
        "v1",
        "6/7"
    ]
}