Show a patch.

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

{
    "id": 790,
    "url": "https://patchwork.libcamera.org/api/1.1/patches/790/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/790/",
    "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": "<20190323073125.25497-3-laurent.pinchart@ideasonboard.com>",
    "date": "2019-03-23T07:31:25",
    "name": "[libcamera-devel,2/2] qcam: Add Qt-based GUI application",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "57f35f1b7ab658f37734472a6cd42e6af2254dab",
    "submitter": {
        "id": 2,
        "url": "https://patchwork.libcamera.org/api/1.1/people/2/?format=api",
        "name": "Laurent Pinchart",
        "email": "laurent.pinchart@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/790/mbox/",
    "series": [
        {
            "id": 217,
            "url": "https://patchwork.libcamera.org/api/1.1/series/217/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=217",
            "date": "2019-03-23T07:31:23",
            "name": "Qt-based libcamera viewer",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/217/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/790/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/790/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<laurent.pinchart@ideasonboard.com>",
        "Received": [
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 5C8896110A\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 23 Mar 2019 08:31:45 +0100 (CET)",
            "from pendragon.ideasonboard.com (30.net042126252.t-com.ne.jp\n\t[42.126.252.30])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C88BC2D0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 23 Mar 2019 08:31:43 +0100 (CET)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1553326305;\n\tbh=yEDJ6z+ubNrk5aZOHCmUY7FODPjbESncvNtjP5essJI=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=Jn/0SbEnMJ7Y2/cRTdXMQrs3Lf3A4QRIpS2GXIHwX+s+hF5CjT9C/i7DQcfrEQpJN\n\tikQJK6XFP4e0Op+UMwWBJMUWbkFXmiJ1Q6fk9+NZFcHFJDnW8n94BV+X0u/NLHmoHh\n\t5P1/mXF+SmMBUs+gofaX/xhFNol8p77qxVqrOTk8=",
        "From": "Laurent Pinchart <laurent.pinchart@ideasonboard.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Sat, 23 Mar 2019 09:31:25 +0200",
        "Message-Id": "<20190323073125.25497-3-laurent.pinchart@ideasonboard.com>",
        "X-Mailer": "git-send-email 2.19.2",
        "In-Reply-To": "<20190323073125.25497-1-laurent.pinchart@ideasonboard.com>",
        "References": "<20190323073125.25497-1-laurent.pinchart@ideasonboard.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH 2/2] qcam: Add Qt-based GUI application",
        "X-BeenThere": "libcamera-devel@lists.libcamera.org",
        "X-Mailman-Version": "2.1.23",
        "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>",
        "X-List-Received-Date": "Sat, 23 Mar 2019 07:31:45 -0000"
    },
    "content": "qcam is a sample camera GUI application based on Qt. It demonstrates\nintegration of the Qt event loop with libcamera.\n\nThe application lets the user select a camera through the GUI, and then\ncaptures a single stream from the camera and displays it in a window.\n\nSigned-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\n src/meson.build                  |   1 +\n src/qcam/format_converter.cpp    |  99 ++++++++++++++\n src/qcam/format_converter.h      |  14 ++\n src/qcam/main.cpp                |  75 +++++++++++\n src/qcam/main_window.cpp         | 223 +++++++++++++++++++++++++++++++\n src/qcam/main_window.h           |  54 ++++++++\n src/qcam/meson.build             |  19 +++\n src/qcam/qt_event_dispatcher.cpp | 145 ++++++++++++++++++++\n src/qcam/qt_event_dispatcher.h   |  62 +++++++++\n src/qcam/viewfinder.cpp          |  38 ++++++\n src/qcam/viewfinder.h            |  31 +++++\n 11 files changed, 761 insertions(+)\n create mode 100644 src/qcam/format_converter.cpp\n create mode 100644 src/qcam/format_converter.h\n create mode 100644 src/qcam/main.cpp\n create mode 100644 src/qcam/main_window.cpp\n create mode 100644 src/qcam/main_window.h\n create mode 100644 src/qcam/meson.build\n create mode 100644 src/qcam/qt_event_dispatcher.cpp\n create mode 100644 src/qcam/qt_event_dispatcher.h\n create mode 100644 src/qcam/viewfinder.cpp\n create mode 100644 src/qcam/viewfinder.h",
    "diff": "diff --git a/src/meson.build b/src/meson.build\nindex a7f2e75de158..4e41fd3e7887 100644\n--- a/src/meson.build\n+++ b/src/meson.build\n@@ -1,2 +1,3 @@\n subdir('libcamera')\n subdir('cam')\n+subdir('qcam')\ndiff --git a/src/qcam/format_converter.cpp b/src/qcam/format_converter.cpp\nnew file mode 100644\nindex 000000000000..0decad74f28e\n--- /dev/null\n+++ b/src/qcam/format_converter.cpp\n@@ -0,0 +1,99 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * format_convert.cpp - qcam - Convert buffer to RGB\n+ */\n+\n+#include <errno.h>\n+\n+#include <linux/videodev2.h>\n+\n+#include \"format_converter.h\"\n+\n+#define RGBSHIFT\t\t8\n+#ifndef MAX\n+#define MAX(a,b)\t\t((a)>(b)?(a):(b))\n+#endif\n+#ifndef MIN\n+#define MIN(a,b)\t\t((a)<(b)?(a):(b))\n+#endif\n+#ifndef CLAMP\n+#define CLAMP(a,low,high)\tMAX((low),MIN((high),(a)))\n+#endif\n+#ifndef CLIP\n+#define CLIP(x)\t\t\tCLAMP(x,0,255)\n+#endif\n+\n+static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b)\n+{\n+\tint c = y - 16;\n+\tint d = u - 128;\n+\tint e = v - 128;\n+\t*r = CLIP(( 298 * c           + 409 * e + 128) >> RGBSHIFT);\n+\t*g = CLIP(( 298 * c - 100 * d - 208 * e + 128) >> RGBSHIFT);\n+\t*b = CLIP(( 298 * c + 516 * d           + 128) >> RGBSHIFT);\n+}\n+\n+int buffer_to_rgb32(unsigned char *src, unsigned int format,\n+\t\t    unsigned int width, unsigned int height,\n+\t\t    unsigned char *dst)\n+{\n+\tunsigned int src_x, src_y, dst_x, dst_y;\n+\tunsigned int src_stride;\n+\tunsigned int dst_stride;\n+\tint y_pos, cb_pos, cr_pos;\n+\tint r, g, b, y, cr, cb;\n+\n+\tswitch (format) {\n+\tcase V4L2_PIX_FMT_VYUY:\n+\t\ty_pos = 1;\n+\t\tcb_pos = 2;\n+\t\tbreak;\n+\tcase V4L2_PIX_FMT_YVYU:\n+\t\ty_pos = 0;\n+\t\tcb_pos = 3;\n+\t\tbreak;\n+\tcase V4L2_PIX_FMT_UYVY:\n+\t\ty_pos = 1;\n+\t\tcb_pos = 0;\n+\t\tbreak;\n+\tcase V4L2_PIX_FMT_YUYV:\n+\t\ty_pos = 0;\n+\t\tcb_pos = 1;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -EINVAL;\n+\t};\n+\n+\tcr_pos = (cb_pos + 2) % 4;\n+\tsrc_stride = width * 2;\n+\tdst_stride = width * 4;\n+\n+\tfor (src_y = 0, dst_y = 0; dst_y < height; src_y++, dst_y++) {\n+\t\tfor (src_x = 0, dst_x = 0; dst_x < width; ) {\n+\t\t\tcb = src[src_y * src_stride + src_x * 4 + cb_pos];\n+\t\t\tcr = src[src_y * src_stride + src_x * 4 + cr_pos];\n+\n+\t\t\ty = src[src_y * src_stride + src_x * 4 + y_pos];\n+\t\t\tyuv_to_rgb(y, cb, cr, &r, &g, &b);\n+\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 0] = b;\n+\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 1] = g;\n+\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 2] = r;\n+\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff;\n+\t\t\tdst_x++;\n+\n+\t\t\ty = src[src_y * src_stride + src_x * 4 + y_pos + 2];\n+\t\t\tyuv_to_rgb(y, cb, cr, &r, &g, &b);\n+\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 0] = b;\n+\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 1] = g;\n+\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 2] = r;\n+\t\t\tdst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff;\n+\t\t\tdst_x++;\n+\n+\t\t\tsrc_x++;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/src/qcam/format_converter.h b/src/qcam/format_converter.h\nnew file mode 100644\nindex 000000000000..3904b139a4fd\n--- /dev/null\n+++ b/src/qcam/format_converter.h\n@@ -0,0 +1,14 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * format_convert.h - qcam - Convert buffer to RGB\n+ */\n+#ifndef __QCAM_FORMAT_CONVERTER_H__\n+#define __QCAM_FORMAT_CONVERTER_H__\n+\n+int buffer_to_rgb32(unsigned char *src, unsigned int format,\n+\t\t    unsigned int width, unsigned int height,\n+\t\t    unsigned char *dst);\n+\n+#endif /* __QCAM_FORMAT_CONVERTER_H__ */\ndiff --git a/src/qcam/main.cpp b/src/qcam/main.cpp\nnew file mode 100644\nindex 000000000000..106b8a162d9f\n--- /dev/null\n+++ b/src/qcam/main.cpp\n@@ -0,0 +1,75 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * main.cpp - cam - The libcamera swiss army knife\n+ */\n+\n+#include <iostream>\n+#include <signal.h>\n+#include <string.h>\n+\n+#include <QApplication>\n+\n+#include <libcamera/camera_manager.h>\n+\n+#include \"main_window.h\"\n+#include \"../cam/options.h\"\n+#include \"qt_event_dispatcher.h\"\n+\n+void signalHandler(int signal)\n+{\n+\tstd::cout << \"Exiting\" << std::endl;\n+\tqApp->quit();\n+}\n+\n+OptionsParser::Options parseOptions(int argc, char *argv[])\n+{\n+\tOptionsParser parser;\n+\tparser.addOption(OptCamera, OptionString,\n+\t\t\t \"Specify which camera to operate on\", \"camera\",\n+\t\t\t ArgumentRequired, \"camera\");\n+\tparser.addOption(OptHelp, OptionNone, \"Display this help message\",\n+\t\t\t \"help\");\n+\n+\tOptionsParser::Options options = parser.parse(argc, argv);\n+\tif (options.isSet(OptHelp))\n+\t\tparser.usage();\n+\n+\treturn options;\n+}\n+\n+int main(int argc, char **argv)\n+{\n+\tQApplication app(argc, argv);\n+\tint ret;\n+\n+\tOptionsParser::Options options = parseOptions(argc, argv);\n+\tif (!options.valid())\n+\t\treturn EXIT_FAILURE;\n+\tif (options.isSet(OptHelp))\n+\t\treturn 0;\n+\n+\tstruct sigaction sa = {};\n+\tsa.sa_handler = &signalHandler;\n+\tsigaction(SIGINT, &sa, nullptr);\n+\n+\tstd::unique_ptr<EventDispatcher> dispatcher(new QtEventDispatcher());\n+\tCameraManager *cm = CameraManager::instance();\n+\tcm->setEventDispatcher(std::move(dispatcher));\n+\n+\tret = cm->start();\n+\tif (ret) {\n+\t\tstd::cout << \"Failed to start camera manager: \"\n+\t\t\t  << strerror(-ret) << std::endl;\n+\t\treturn EXIT_FAILURE;\n+\t}\n+\n+\tMainWindow *mainWindow = new MainWindow(options);\n+\tmainWindow->show();\n+\tret = app.exec();\n+\tdelete mainWindow;\n+\n+\tcm->stop();\n+\treturn ret;\n+}\ndiff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp\nnew file mode 100644\nindex 000000000000..d39dead76b56\n--- /dev/null\n+++ b/src/qcam/main_window.cpp\n@@ -0,0 +1,223 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * main_window.cpp - qcam - Main application window\n+ */\n+\n+#include <iomanip>\n+#include <iostream>\n+#include <string>\n+\n+#include <QCoreApplication>\n+#include <QInputDialog>\n+#include <QTimer>\n+\n+#include <libcamera/camera_manager.h>\n+\n+#include \"main_window.h\"\n+#include \"viewfinder.h\"\n+\n+using namespace libcamera;\n+\n+MainWindow::MainWindow(const OptionsParser::Options &options)\n+\t: options_(options), isCapturing_(false)\n+{\n+\tint ret;\n+\n+\tviewfinder_ = new ViewFinder(this);\n+\tsetCentralWidget(viewfinder_);\n+\tviewfinder_->setFixedSize(500, 500);\n+\tadjustSize();\n+\n+\tret = openCamera();\n+\tif (!ret)\n+\t\tret = startCapture();\n+\n+\tif (ret < 0)\n+\t\tQTimer::singleShot(0, QCoreApplication::instance(),\n+\t\t\t\t   &QCoreApplication::quit);\n+}\n+\n+MainWindow::~MainWindow()\n+{\n+\tif (camera_) {\n+\t\tstopCapture();\n+\t\tcamera_->release();\n+\t\tcamera_.reset();\n+\t}\n+\n+\tCameraManager::instance()->stop();\n+}\n+\n+int MainWindow::openCamera()\n+{\n+\tCameraManager *cm = CameraManager::instance();\n+\tstd::string cameraName;\n+\n+\tif (!options_.isSet(OptCamera)) {\n+\t\tQStringList cameras;\n+\t\tbool result;\n+\n+\t\tfor (const std::shared_ptr<Camera> &cam : cm->cameras())\n+\t\t\tcameras.append(QString::fromStdString(cam->name()));\n+\n+\t\tQString name = QInputDialog::getItem(this, \"Select Camera\",\n+\t\t\t\t\t\t     \"Camera:\", cameras, 0,\n+\t\t\t\t\t\t     false, &result);\n+\t\tif (!result)\n+\t\t\treturn -EINVAL;\n+\n+\t\tcameraName = name.toStdString();\n+\t} else {\n+\t\tcameraName = static_cast<std::string>(options_[OptCamera]);\n+\t}\n+\n+\tcamera_ = cm->get(cameraName);\n+\tif (!camera_) {\n+\t\tstd::cout << \"Camera \" << cameraName << \" not found\"\n+\t\t\t  << std::endl;\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tif (camera_->acquire()) {\n+\t\tstd::cout << \"Failed to acquire camera\" << std::endl;\n+\t\tcamera_.reset();\n+\t\treturn -EBUSY;\n+\t}\n+\n+\tstd::cout << \"Using camera \" << camera_->name() << std::endl;\n+\n+\tcamera_->requestCompleted.connect(this, &MainWindow::requestComplete);\n+\n+\treturn 0;\n+}\n+\n+int MainWindow::startCapture()\n+{\n+\tint ret;\n+\n+\tStream *stream = *camera_->streams().begin();\n+\tstd::set<Stream *> streams{ stream };\n+\tconfig_ = camera_->streamConfiguration(streams);\n+\tret = camera_->configureStreams(config_);\n+\tif (ret < 0) {\n+\t\tstd::cout << \"Failed to configure camera\" << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\tconst StreamConfiguration &sconf = config_[stream];\n+\tviewfinder_->setFormat(sconf.pixelFormat, sconf.width, sconf.height);\n+\tadjustSize();\n+\n+\tret = camera_->allocateBuffers();\n+\tif (ret) {\n+\t\tstd::cerr << \"Failed to allocate buffers\"\n+\t\t\t  << std::endl;\n+\t\treturn ret;\n+\t}\n+\n+\tBufferPool &pool = stream->bufferPool();\n+\tstd::vector<Request *> requests;\n+\n+\tfor (Buffer &buffer : pool.buffers()) {\n+\t\tRequest *request = camera_->createRequest();\n+\t\tif (!request) {\n+\t\t\tstd::cerr << \"Can't create request\" << std::endl;\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\tstd::map<Stream *, Buffer *> map;\n+\t\tmap[stream] = &buffer;\n+\t\tret = request->setBuffers(map);\n+\t\tif (ret < 0) {\n+\t\t\tstd::cerr << \"Can't set buffers for request\" << std::endl;\n+\t\t\tgoto error;\n+\t\t}\n+\n+\t\trequests.push_back(request);\n+\t}\n+\n+\tret = camera_->start();\n+\tif (ret) {\n+\t\tstd::cout << \"Failed to start capture\" << std::endl;\n+\t\tgoto error;\n+\t}\n+\n+\tfor (Request *request : requests) {\n+\t\tret = camera_->queueRequest(request);\n+\t\tif (ret < 0) {\n+\t\t\tstd::cerr << \"Can't queue request\" << std::endl;\n+\t\t\tgoto error;\n+\t\t}\n+\t}\n+\n+\tisCapturing_ = true;\n+\treturn 0;\n+\n+error:\n+\tfor (Request *request : requests)\n+\t\tdelete request;\n+\n+\tcamera_->freeBuffers();\n+\treturn ret;\n+}\n+\n+void MainWindow::stopCapture()\n+{\n+\tif (!isCapturing_)\n+\t\treturn;\n+\n+\tint ret = camera_->stop();\n+\tif (ret)\n+\t\tstd::cout << \"Failed to stop capture\" << std::endl;\n+\n+\tcamera_->freeBuffers();\n+\tisCapturing_ = false;\n+}\n+\n+void MainWindow::requestComplete(Request *request,\n+\t\t\t\t const std::map<Stream *, Buffer *> &buffers)\n+{\n+\tstatic uint64_t last = 0;\n+\n+\tif (request->status() == Request::RequestCancelled)\n+\t\treturn;\n+\n+\tBuffer *buffer = buffers.begin()->second;\n+\n+\tdouble fps = buffer->timestamp() - last;\n+\tfps = last && fps ? 1000000000.0 / fps : 0.0;\n+\tlast = buffer->timestamp();\n+\n+\tstd::cout << \"seq: \" << std::setw(6) << std::setfill('0') << buffer->sequence()\n+\t\t  << \" buf: \" << buffer->index()\n+\t\t  << \" bytesused: \" << buffer->bytesused()\n+\t\t  << \" timestamp: \" << buffer->timestamp()\n+\t\t  << \" fps: \" << std::fixed << std::setprecision(2) << fps\n+\t\t  << std::endl;\n+\n+\tdisplay(buffer);\n+\n+\trequest = camera_->createRequest();\n+\tif (!request) {\n+\t\tstd::cerr << \"Can't create request\" << std::endl;\n+\t\treturn;\n+\t}\n+\n+\trequest->setBuffers(buffers);\n+\tcamera_->queueRequest(request);\n+}\n+\n+int MainWindow::display(Buffer *buffer)\n+{\n+\tif (buffer->planes().size() != 1)\n+\t\treturn -EINVAL;\n+\n+\tPlane &plane = buffer->planes().front();\n+\tunsigned char *raw = static_cast<unsigned char *>(plane.mem());\n+\tviewfinder_->display(raw);\n+\n+\treturn 0;\n+}\ndiff --git a/src/qcam/main_window.h b/src/qcam/main_window.h\nnew file mode 100644\nindex 000000000000..5e27a8fd6b4e\n--- /dev/null\n+++ b/src/qcam/main_window.h\n@@ -0,0 +1,54 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * main_window.h - qcam - Main application window\n+ */\n+#ifndef __QCAM_MAIN_WINDOW_H__\n+#define __QCAM_MAIN_WINDOW_H__\n+\n+#include <map>\n+\n+#include <QMainWindow>\n+\n+#include <libcamera/camera.h>\n+#include <libcamera/stream.h>\n+\n+#include \"../cam/options.h\"\n+\n+using namespace libcamera;\n+\n+class ViewFinder;\n+\n+enum {\n+\tOptCamera = 'c',\n+\tOptHelp = 'h',\n+};\n+\n+class MainWindow : public QMainWindow\n+{\n+public:\n+\tMainWindow(const OptionsParser::Options &options);\n+\t~MainWindow();\n+\n+private:\n+\tint openCamera();\n+\n+\tint startCapture();\n+\tint configureStreams(Camera *camera, std::set<Stream *> &streams);\n+\tvoid stopCapture();\n+\n+\tvoid requestComplete(Request *request,\n+\t\t\t     const std::map<Stream *, Buffer *> &buffers);\n+\tint display(Buffer *buffer);\n+\n+\tconst OptionsParser::Options &options_;\n+\n+\tstd::shared_ptr<Camera> camera_;\n+\tbool isCapturing_;\n+\tstd::map<Stream *, StreamConfiguration> config_;\n+\n+\tViewFinder *viewfinder_;\n+};\n+\n+#endif /* __QCAM_MAIN_WINDOW__ */\ndiff --git a/src/qcam/meson.build b/src/qcam/meson.build\nnew file mode 100644\nindex 000000000000..8a71cda3dfe5\n--- /dev/null\n+++ b/src/qcam/meson.build\n@@ -0,0 +1,19 @@\n+qcam_sources = files([\n+    'format_converter.cpp',\n+    'main.cpp',\n+    'main_window.cpp',\n+    '../cam/options.cpp',\n+    'qt_event_dispatcher.cpp',\n+    'viewfinder.cpp',\n+])\n+\n+import('qt5')\n+qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Widgets'], required : false)\n+\n+if qt5_dep.found()\n+    qcam  = executable('qcam', qcam_sources,\n+                       link_with : libcamera,\n+                       include_directories : libcamera_includes,\n+                       dependencies : qt5_dep,\n+                       cpp_args : '-DQT_NO_KEYWORDS')\n+endif\ndiff --git a/src/qcam/qt_event_dispatcher.cpp b/src/qcam/qt_event_dispatcher.cpp\nnew file mode 100644\nindex 000000000000..5ba451bf88ce\n--- /dev/null\n+++ b/src/qcam/qt_event_dispatcher.cpp\n@@ -0,0 +1,145 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * qt_event_dispatcher.cpp - qcam - Qt-based event dispatcher\n+ */\n+\n+#include <iostream>\n+\n+#include <QAbstractEventDispatcher>\n+#include <QCoreApplication>\n+#include <QSocketNotifier>\n+#include <QTimerEvent>\n+\n+#include <libcamera/event_notifier.h>\n+#include <libcamera/timer.h>\n+\n+#include \"qt_event_dispatcher.h\"\n+\n+using namespace libcamera;\n+\n+QtEventDispatcher::QtEventDispatcher()\n+{\n+}\n+\n+QtEventDispatcher::~QtEventDispatcher()\n+{\n+\tfor (auto &it : notifiers_) {\n+\t\tNotifierSet &set = it.second;\n+\t\tdelete set.read.qnotifier;\n+\t\tdelete set.write.qnotifier;\n+\t\tdelete set.exception.qnotifier;\n+\t}\n+}\n+\n+void QtEventDispatcher::registerEventNotifier(EventNotifier *notifier)\n+{\n+\tNotifierSet &set = notifiers_[notifier->fd()];\n+\tQSocketNotifier::Type qtype;\n+\tvoid (QtEventDispatcher::*method)(int);\n+\tNotifierPair *pair;\n+\n+\tswitch (notifier->type()) {\n+\tcase EventNotifier::Read:\n+\tdefault:\n+\t\tqtype = QSocketNotifier::Read;\n+\t\tmethod = &QtEventDispatcher::readNotifierActivated;\n+\t\tpair = &set.read;\n+\t\tbreak;\n+\n+\tcase EventNotifier::Write:\n+\t\tqtype = QSocketNotifier::Write;\n+\t\tmethod = &QtEventDispatcher::writeNotifierActivated;\n+\t\tpair = &set.write;\n+\t\tbreak;\n+\n+\tcase EventNotifier::Exception:\n+\t\tqtype = QSocketNotifier::Exception;\n+\t\tmethod = &QtEventDispatcher::exceptionNotifierActivated;\n+\t\tpair = &set.exception;\n+\t\tbreak;\n+\t}\n+\n+\tQSocketNotifier *qnotifier = new QSocketNotifier(notifier->fd(), qtype);\n+\tconnect(qnotifier, &QSocketNotifier::activated, this, method);\n+\tpair->notifier = notifier;\n+\tpair->qnotifier = qnotifier;\n+}\n+\n+void QtEventDispatcher::unregisterEventNotifier(EventNotifier *notifier)\n+{\n+\tNotifierSet &set = notifiers_[notifier->fd()];\n+\tNotifierPair *pair;\n+\n+\tswitch (notifier->type()) {\n+\tcase EventNotifier::Read:\n+\tdefault:\n+\t\tpair = &set.read;\n+\t\tbreak;\n+\n+\tcase EventNotifier::Write:\n+\t\tpair = &set.write;\n+\t\tbreak;\n+\n+\tcase EventNotifier::Exception:\n+\t\tpair = &set.exception;\n+\t\tbreak;\n+\t}\n+\n+\tdelete pair->qnotifier;\n+\tpair->qnotifier = nullptr;\n+\tpair->notifier = nullptr;\n+}\n+\n+void QtEventDispatcher::readNotifierActivated(int socket)\n+{\n+\tEventNotifier *notifier = notifiers_[socket].read.notifier;\n+\tnotifier->activated.emit(notifier);\n+}\n+\n+void QtEventDispatcher::writeNotifierActivated(int socket)\n+{\n+\tEventNotifier *notifier = notifiers_[socket].write.notifier;\n+\tnotifier->activated.emit(notifier);\n+}\n+\n+void QtEventDispatcher::exceptionNotifierActivated(int socket)\n+{\n+\tEventNotifier *notifier = notifiers_[socket].exception.notifier;\n+\tnotifier->activated.emit(notifier);\n+}\n+\n+void QtEventDispatcher::registerTimer(Timer *timer)\n+{\n+\tint timerId = startTimer(timer->interval());\n+\ttimers_[timerId] = timer;\n+\ttimerIds_[timer] = timerId;\n+}\n+\n+void QtEventDispatcher::unregisterTimer(Timer *timer)\n+{\n+\tauto it = timerIds_.find(timer);\n+\ttimers_.erase(it->second);\n+\tkillTimer(it->second);\n+\ttimerIds_.erase(it);\n+}\n+\n+void QtEventDispatcher::timerEvent(QTimerEvent *event)\n+{\n+\tauto it = timers_.find(event->timerId());\n+\ttimerIds_.erase(it->second);\n+\tkillTimer(it->first);\n+\ttimers_.erase(it);\n+}\n+\n+void QtEventDispatcher::processEvents()\n+{\n+\tstd::cout << \"QtEventDispatcher::processEvents() should not be called\"\n+\t\t  << std::endl;\n+}\n+\n+void QtEventDispatcher::interrupt()\n+{\n+\tQCoreApplication::eventDispatcher()->interrupt();\n+}\ndiff --git a/src/qcam/qt_event_dispatcher.h b/src/qcam/qt_event_dispatcher.h\nnew file mode 100644\nindex 000000000000..b0f123e52d06\n--- /dev/null\n+++ b/src/qcam/qt_event_dispatcher.h\n@@ -0,0 +1,62 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * qt_event_dispatcher.h - qcam - Qt-based event dispatcher\n+ */\n+#ifndef __QCAM_QT_EVENT_DISPATCHER_H__\n+#define __QCAM_QT_EVENT_DISPATCHER_H__\n+\n+#include <map>\n+\n+#include <libcamera/event_dispatcher.h>\n+\n+using namespace libcamera;\n+\n+class QSocketNotifier;\n+\n+class QtEventDispatcher final : public EventDispatcher, public QObject\n+{\n+public:\n+\tQtEventDispatcher();\n+\t~QtEventDispatcher();\n+\n+\tvoid registerEventNotifier(EventNotifier *notifier);\n+\tvoid unregisterEventNotifier(EventNotifier *notifier);\n+\n+\tvoid registerTimer(Timer *timer);\n+\tvoid unregisterTimer(Timer *timer);\n+\n+\tvoid processEvents();\n+\n+\tvoid interrupt();\n+\n+protected:\n+\tvoid timerEvent(QTimerEvent *event);\n+\n+private:\n+\tvoid readNotifierActivated(int socket);\n+\tvoid writeNotifierActivated(int socket);\n+\tvoid exceptionNotifierActivated(int socket);\n+\n+\tstruct NotifierPair {\n+\t\tNotifierPair()\n+\t\t\t: notifier(nullptr), qnotifier(nullptr)\n+\t\t{\n+\t\t}\n+\t\tEventNotifier *notifier;\n+\t\tQSocketNotifier *qnotifier;\n+\t};\n+\n+\tstruct NotifierSet {\n+\t\tNotifierPair read;\n+\t\tNotifierPair write;\n+\t\tNotifierPair exception;\n+\t};\n+\n+\tstd::map<int, NotifierSet> notifiers_;\n+\tstd::map<int, Timer *> timers_;\n+\tstd::map<Timer *, int> timerIds_;\n+};\n+\n+#endif /* __QCAM_QT_EVENT_DISPATCHER_H__ */\ndiff --git a/src/qcam/viewfinder.cpp b/src/qcam/viewfinder.cpp\nnew file mode 100644\nindex 000000000000..5db55ebbf098\n--- /dev/null\n+++ b/src/qcam/viewfinder.cpp\n@@ -0,0 +1,38 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * viewfinder.cpp - qcam - Viewfinder\n+ */\n+\n+#include <QImage>\n+#include <QPixmap>\n+\n+#include \"format_converter.h\"\n+#include \"viewfinder.h\"\n+\n+ViewFinder::ViewFinder(QWidget *parent)\n+\t: QLabel(parent), format_(0), width_(0), height_(0), image_(nullptr)\n+{\n+}\n+\n+void ViewFinder::display(unsigned char *raw)\n+{\n+\tbuffer_to_rgb32(raw, format_, width_, height_, image_->bits());\n+\n+\tQPixmap pixmap = QPixmap::fromImage(*image_);\n+\tsetPixmap(pixmap);\n+}\n+\n+void ViewFinder::setFormat(unsigned int format, unsigned int width,\n+\t\t\t   unsigned int height)\n+{\n+\tformat_ = format;\n+\twidth_ = width;\n+\theight_ = height;\n+\n+\tsetFixedSize(width, height);\n+\n+\tdelete image_;\n+\timage_ = new QImage(width, height, QImage::Format_RGB32);\n+}\ndiff --git a/src/qcam/viewfinder.h b/src/qcam/viewfinder.h\nnew file mode 100644\nindex 000000000000..ecae290a5043\n--- /dev/null\n+++ b/src/qcam/viewfinder.h\n@@ -0,0 +1,31 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/*\n+ * Copyright (C) 2019, Google Inc.\n+ *\n+ * viewfinder.h - qcam - Viewfinder\n+ */\n+#ifndef __QCAM_VIEWFINDER_H__\n+#define __QCAM_VIEWFINDER_H__\n+\n+#include <QLabel>\n+\n+class QImage;\n+\n+class ViewFinder : public QLabel\n+{\n+public:\n+\tViewFinder(QWidget *parent);\n+\n+\tvoid setFormat(unsigned int format, unsigned int width,\n+\t\t       unsigned int height);\n+\tvoid display(unsigned char *rgb);\n+\n+private:\n+\tunsigned int format_;\n+\tunsigned int width_;\n+\tunsigned int height_;\n+\n+\tQImage *image_;\n+};\n+\n+#endif /* __QCAM_VIEWFINDER__ */\n",
    "prefixes": [
        "libcamera-devel",
        "2/2"
    ]
}