You've already forked floweepay-aur
d94905fe65
This backports a post release fix to support the latest ZXing, which came out just when the latest Pay release was made.
1157 lines
42 KiB
Diff
1157 lines
42 KiB
Diff
From 3d2911eb08a34f5df202ef1d9edee575ad89abaf Mon Sep 17 00:00:00 2001
|
|
From: TomZ <tom@flowee.org>
|
|
Date: Mon, 16 Feb 2026 14:34:28 +0100
|
|
Subject: [PATCH] Port to new ZXing version 3
|
|
|
|
The new ZXingCpp release is out, it is a major version (v3) and
|
|
it has broken source compatibility towards version 2.
|
|
|
|
The good news is that we can actually cut out quite a lot of
|
|
boring code which is now done in the upstream project.
|
|
|
|
But to actually benefit from better readability I think the best
|
|
approach is the "isolate the old" idea. So this copies the v2
|
|
compatible file to CameraController_zxing2.cpp QRCreator_zxing2.cpp
|
|
We'll have code duplication that way, but it will never be compiled
|
|
into the same binary and indeed we'll just be cleanly able to
|
|
delete the old support when that time comes.
|
|
---
|
|
CMakeLists.txt | 4 +-
|
|
src/CMakeLists.txt | 23 +-
|
|
src/CameraController.cpp | 129 +-----
|
|
src/CameraController_zxing2.cpp | 725 ++++++++++++++++++++++++++++++++
|
|
src/QRCreator.cpp | 35 +-
|
|
src/QRCreator_zxing2.cpp | 71 ++++
|
|
6 files changed, 835 insertions(+), 152 deletions(-)
|
|
create mode 100644 src/CameraController_zxing2.cpp
|
|
create mode 100644 src/QRCreator_zxing2.cpp
|
|
|
|
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
index 3ffa53e..a0709dc 100644
|
|
--- a/CMakeLists.txt
|
|
+++ b/CMakeLists.txt
|
|
@@ -60,6 +60,7 @@ if (ANDROID)
|
|
set_property(TARGET ZXing::ZXing PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES
|
|
"/opt/android-zxing/lib/libZXing.a")
|
|
set (ZXing_FOUND TRUE)
|
|
+ set (ZXing_VERSION_MAJOR 2)
|
|
else ()
|
|
find_package(ZXing REQUIRED)
|
|
endif()
|
|
@@ -67,6 +68,7 @@ else ()
|
|
find_package(Qt6 COMPONENTS DBus LinguistTools)
|
|
find_package(ZXing REQUIRED)
|
|
endif()
|
|
+
|
|
if (CMAKE_VERSION VERSION_GREATER "3.29.9")
|
|
# use the upstream boost info instead of the cmake-shipped one for finding
|
|
# this policy was introduced in cmake 3.30
|
|
@@ -261,7 +263,7 @@ if (build_desktop_pay)
|
|
)
|
|
add_executable(pay ${SOURCES_PAY})
|
|
set_target_properties(pay PROPERTIES COMPILE_DEFINITIONS "${COMPILE_DEFINITIONS} DESKTOP")
|
|
- target_link_libraries(pay PRIVATE pay_lib Qt6::Svg ${PAY_MODULES_LIBS})
|
|
+ target_link_libraries(pay PRIVATE Qt6::Svg ${PAY_MODULES_LIBS})
|
|
install(TARGETS pay DESTINATION bin)
|
|
endif()
|
|
|
|
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
|
|
index 78e7db6..822794f 100644
|
|
--- a/src/CMakeLists.txt
|
|
+++ b/src/CMakeLists.txt
|
|
@@ -1,5 +1,5 @@
|
|
# This file is part of the Flowee project
|
|
-# Copyright (C) 2020-2025 Tom Zander <tom@flowee.org>
|
|
+# Copyright (C) 2020-2026 Tom Zander <tom@flowee.org>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -43,7 +43,6 @@ set (PAY_SOURCES
|
|
PriceHistoryDataProvider.cpp
|
|
QMLClipboardHelper.cpp
|
|
QMLImportHelper.cpp
|
|
- QRCreator.cpp
|
|
QRScanner.cpp
|
|
RepeatPaymentDetails.cpp
|
|
RepeatPaymentsModel.cpp
|
|
@@ -91,15 +90,29 @@ else ()
|
|
)
|
|
endif ()
|
|
|
|
-if (NetworkLogClient)
|
|
- list(APPEND PAY_SOURCES NetworkLogClient.cpp)
|
|
+if (ZXing_VERSION_MAJOR EQUAL 2)
|
|
+ message(STATUS "Using ZXing 2.x")
|
|
+ list(APPEND PAY_SOURCES QRCreator_zxing2.cpp)
|
|
+elseif (ZXing_VERSION_MAJOR EQUAL 3)
|
|
+ message(STATUS "Using ZXing 3.x")
|
|
+ list(APPEND PAY_SOURCES QRCreator.cpp)
|
|
+else ()
|
|
+ message(FATAL_ERROR "Unsupported ZXing version: ${ZXing_VERSION}")
|
|
endif ()
|
|
|
|
if (${Qt6Multimedia_FOUND})
|
|
- list(APPEND PAY_SOURCES CameraController.cpp)
|
|
+ if (ZXing_VERSION_MAJOR EQUAL 2)
|
|
+ list(APPEND PAY_SOURCES CameraController_zxing2.cpp)
|
|
+ else ()
|
|
+ list(APPEND PAY_SOURCES CameraController.cpp)
|
|
+ endif ()
|
|
list(APPEND PayLib_PRIVATE_LIBS Qt6::Multimedia)
|
|
endif ()
|
|
|
|
+if (NetworkLogClient)
|
|
+ list(APPEND PAY_SOURCES NetworkLogClient.cpp)
|
|
+endif ()
|
|
+
|
|
add_library(pay_lib STATIC ${PAY_SOURCES})
|
|
|
|
target_link_libraries(pay_lib
|
|
diff --git a/src/CameraController.cpp b/src/CameraController.cpp
|
|
index 9d69952..7ec27dc 100644
|
|
--- a/src/CameraController.cpp
|
|
+++ b/src/CameraController.cpp
|
|
@@ -1,7 +1,6 @@
|
|
/*
|
|
* This file is part of the Flowee project
|
|
- * Copyright (C) 2022-2025 Tom Zander <tom@flowee.org>
|
|
- * Copyright (C) 2020 Axel Waggershauser <awagger@gmail.com>
|
|
+ * Copyright (C) 2022-2026 Tom Zander <tom@flowee.org>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
@@ -21,8 +20,6 @@
|
|
#include "base58.h"
|
|
#include <Logger.h>
|
|
|
|
-#include <ZXing/ReadBarcode.h>
|
|
-
|
|
#include <QImage>
|
|
#include <QVideoFrame>
|
|
#include <QVideoSink>
|
|
@@ -34,6 +31,10 @@
|
|
#include <QThread>
|
|
#include <QTimer>
|
|
|
|
+#include <ZXing/ZXingQt.h>
|
|
+// force Qt to moc this external header file.
|
|
+#include "ZXing/moc_ZXingQt.cpp"
|
|
+
|
|
enum AskingState {
|
|
NotAsked,
|
|
Denied,
|
|
@@ -85,14 +86,13 @@ public:
|
|
protected:
|
|
void run();
|
|
private:
|
|
+#if ZXING_VERSION_MAJOR < 3
|
|
std::vector<ZXing::Result> readBarcodes(const QImage &img) const;
|
|
std::vector<ZXing::Result> readBarcodes(QVideoFrame &frame) const;
|
|
+#endif
|
|
|
|
CameraControllerPrivate *m_parent;
|
|
- // notice that since ZXIng 2.2.0 this is renamed to 'ReaderOptions'.
|
|
- // this was released in December 2023, so we'll be using the old stuff
|
|
- // for quite a bit longer to keep stuff compiling on older systems.
|
|
- ZXing::DecodeHints m_decodeHints;
|
|
+ ZXing::ReaderOptions m_decodeHints;
|
|
};
|
|
|
|
|
|
@@ -278,7 +278,8 @@ void QRScanningThread::run()
|
|
return;
|
|
|
|
lastFrameScanned = time(nullptr);
|
|
- auto results = readBarcodes(frame);
|
|
+
|
|
+ const auto results = ZXingQt::ReadBarcodes(frame);
|
|
for (const auto &result : results) {
|
|
const auto &bytes = result.bytes();
|
|
// logInfo(10005) << "result:" << QString::fromUtf8(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
@@ -288,7 +289,7 @@ void QRScanningThread::run()
|
|
// first, when it starts with 'bch-wif:' this helps, but needs to be cut off.
|
|
const bool wifPrefix = bytes.size() >= 58 && bytes.size() < 63
|
|
&& (bytes[8] == 'K' || bytes[8] == 'L')
|
|
- && 0 == memcmp(&bytes[0], "bch-wif:", 8);
|
|
+ && 0 == memcmp(bytes.constData(), "bch-wif:", 8);
|
|
|
|
if (wifPrefix || (bytes.size() >= 50 && bytes.size() < 55 && (bytes[0] == 'K' || bytes[0] == 'L'))) {
|
|
// might be one!!
|
|
@@ -378,114 +379,6 @@ void QRScanningThread::run()
|
|
}
|
|
}
|
|
|
|
-std::vector<ZXing::Result> QRScanningThread::readBarcodes(const QImage &img) const
|
|
-{
|
|
- auto imageFormat = img.format();
|
|
- if (imageFormat == QImage::Format_Invalid) // likely a damaged frame in the feed
|
|
- return {};
|
|
- auto zxImageFormat = ZXing::ImageFormat::None;
|
|
- switch (imageFormat) {
|
|
- case QImage::Format_ARGB32:
|
|
- case QImage::Format_RGB32:
|
|
-#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
- zxImageFormat = ZXing::ImageFormat::BGRX;
|
|
-#else
|
|
- zxImageFormat = ImageFormat::XRGB;
|
|
-#endif
|
|
- break;
|
|
- case QImage::Format_RGB888: zxImageFormat = ZXing::ImageFormat::RGB; break;
|
|
- case QImage::Format_RGBX8888:
|
|
- case QImage::Format_RGBA8888: zxImageFormat = ZXing::ImageFormat::RGBX; break;
|
|
- case QImage::Format_Grayscale8: zxImageFormat = ZXing::ImageFormat::Lum; break;
|
|
- default: break;
|
|
- }
|
|
-
|
|
- if (zxImageFormat == ZXing::ImageFormat::None) {
|
|
- QImage gray = img.convertToFormat(QImage::Format_Grayscale8) ;
|
|
- return ZXing::ReadBarcodes({gray.bits(), gray.width(), gray.height(), ZXing::ImageFormat::Lum, static_cast<int>(gray.bytesPerLine())}, m_decodeHints);
|
|
- }
|
|
-
|
|
- ZXing::ImageView buf(img.bits(), img.width(), img.height(), zxImageFormat, static_cast<int>(img.bytesPerLine()));
|
|
- return ZXing::ReadBarcodes(buf, m_decodeHints);
|
|
-}
|
|
-
|
|
-std::vector<ZXing::Result> QRScanningThread::readBarcodes(QVideoFrame &frame) const
|
|
-{
|
|
- ZXing::ImageFormat fmt = ZXing::ImageFormat::None;
|
|
- int pixStride = 0;
|
|
- int pixOffset = 0;
|
|
-
|
|
- // note that the comments behind the values are the Qt5 formats.
|
|
- switch (frame.pixelFormat()) {
|
|
- case QVideoFrameFormat::Format_ARGB8888: // ARGB32
|
|
- case QVideoFrameFormat::Format_ARGB8888_Premultiplied: // ARGB32_Premultiplied
|
|
- case QVideoFrameFormat::Format_RGBX8888: // RGB32
|
|
- fmt = ZXing::ImageFormat::BGRX;
|
|
- break;
|
|
- case QVideoFrameFormat::Format_BGRA8888: // BGRA32
|
|
- case QVideoFrameFormat::Format_BGRA8888_Premultiplied: // BGRA32_Premultiplied
|
|
- case QVideoFrameFormat::Format_BGRX8888: // BGR32
|
|
-#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
- fmt = ZXing::ImageFormat::RGBX;
|
|
-#else
|
|
- fmt = ImageFormat::XBGR;
|
|
-#endif
|
|
- break;
|
|
- case QVideoFrameFormat::Format_P010:
|
|
- case QVideoFrameFormat::Format_P016:
|
|
- fmt = ZXing::ImageFormat::Lum, pixStride = 1; break;
|
|
- case QVideoFrameFormat::Format_AYUV: // AYUV444
|
|
- case QVideoFrameFormat::Format_AYUV_Premultiplied: // AYUV444_Premultiplied
|
|
-#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
- fmt = ZXing::ImageFormat::Lum, pixStride = 4, pixOffset = 3;
|
|
-#else
|
|
- fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2;
|
|
-#endif
|
|
- break;
|
|
- case QVideoFrameFormat::Format_YUV420P: // YUV420P
|
|
- case QVideoFrameFormat::Format_NV12: // NV12
|
|
- case QVideoFrameFormat::Format_NV21: // NV21
|
|
- case QVideoFrameFormat::Format_IMC1: // IMC1
|
|
- case QVideoFrameFormat::Format_IMC2: // IMC2
|
|
- case QVideoFrameFormat::Format_IMC3: // IMC3
|
|
- case QVideoFrameFormat::Format_IMC4: // IMC4
|
|
- case QVideoFrameFormat::Format_YV12: // YV12
|
|
- case QVideoFrameFormat::Format_Y8: // Y8
|
|
- case QVideoFrameFormat::Format_YUV422P: // YUV422P
|
|
- fmt = ZXing::ImageFormat::Lum; break;
|
|
- case QVideoFrameFormat::Format_UYVY: // UYVY
|
|
- fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
|
- case QVideoFrameFormat::Format_YUYV: // YUYV
|
|
- fmt = ZXing::ImageFormat::Lum, pixStride = 2; break;
|
|
- case QVideoFrameFormat::Format_Y16: // Y16
|
|
- fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
|
-
|
|
- case QVideoFrameFormat::Format_ABGR8888: // ABGR32
|
|
-#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
- fmt = ZXing::ImageFormat::RGBX;
|
|
-#else
|
|
- fmt = ZXing::ImageFormat::XBGR;
|
|
-#endif
|
|
- break;
|
|
- default: break;
|
|
- }
|
|
-
|
|
- if (fmt != ZXing::ImageFormat::None) {
|
|
- if (!frame.isValid() || !frame.map(QVideoFrame::ReadOnly)){
|
|
- logDebug(10005) << "invalid QVideoFrame: could not map memory";
|
|
- return {};
|
|
- }
|
|
- QScopeGuard unmap([&] { frame.unmap(); });
|
|
-
|
|
- constexpr int FirstPlane = 0;
|
|
- return ZXing::ReadBarcodes(
|
|
- {frame.bits(FirstPlane) + pixOffset, frame.width(), frame.height(), fmt, frame.bytesPerLine(FirstPlane), pixStride}, m_decodeHints);
|
|
- } else {
|
|
- return readBarcodes(frame.toImage());
|
|
- }
|
|
-}
|
|
-
|
|
-
|
|
// --------------------------------------------------------------------
|
|
|
|
CameraController::CameraController(QObject *parent)
|
|
diff --git a/src/CameraController_zxing2.cpp b/src/CameraController_zxing2.cpp
|
|
new file mode 100644
|
|
index 0000000..1ee6ad3
|
|
--- /dev/null
|
|
+++ b/src/CameraController_zxing2.cpp
|
|
@@ -0,0 +1,725 @@
|
|
+/*
|
|
+ * This file is part of the Flowee project
|
|
+ * Copyright (C) 2022-2025 Tom Zander <tom@flowee.org>
|
|
+ * Copyright (C) 2020 Axel Waggershauser <awagger@gmail.com>
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, either version 3 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
+#include "CameraController.h"
|
|
+#include "QRScanner.h"
|
|
+#include "base58.h"
|
|
+#include <Logger.h>
|
|
+
|
|
+#include <ZXing/ReadBarcode.h>
|
|
+
|
|
+#include <QImage>
|
|
+#include <QVideoFrame>
|
|
+#include <QVideoSink>
|
|
+#include <QCamera>
|
|
+#include <QGuiApplication>
|
|
+#include <QClipboard>
|
|
+#include <QPointer>
|
|
+#include <QMutex>
|
|
+#include <QThread>
|
|
+#include <QTimer>
|
|
+
|
|
+enum AskingState {
|
|
+ NotAsked,
|
|
+ Denied,
|
|
+ Authorized
|
|
+};
|
|
+
|
|
+class QRScanningThread;
|
|
+
|
|
+class CameraControllerPrivate
|
|
+{
|
|
+public:
|
|
+ explicit CameraControllerPrivate(CameraController *qq);
|
|
+ // Configure the camera
|
|
+ void initCamera();
|
|
+ // check if we need to load the camera.
|
|
+ void checkState();
|
|
+
|
|
+ AskingState state;
|
|
+ QObject *camera = nullptr;
|
|
+ QObject *videoSink = nullptr;
|
|
+ QString helpText;
|
|
+
|
|
+ QPointer<QRScanner> scanRequest;
|
|
+
|
|
+ mutable QMutex lock;
|
|
+ QVideoFrame currentFrame;
|
|
+ QCameraFormat preferredFormat;
|
|
+
|
|
+ bool cameraLoaded = false;
|
|
+ bool cameraStarted = false;
|
|
+ bool visible = false;
|
|
+ bool torchEnabled = false;
|
|
+ int streamWidth = -1;
|
|
+ int streamHeight = -1;
|
|
+
|
|
+ QRScanningThread *scanningThread = nullptr;
|
|
+
|
|
+ CameraController *q;
|
|
+};
|
|
+
|
|
+class QRScanningThread : public QThread
|
|
+{
|
|
+public:
|
|
+ explicit QRScanningThread(CameraControllerPrivate *parent);
|
|
+
|
|
+ QString text;
|
|
+ QRScanner::ScanType scanType;
|
|
+
|
|
+protected:
|
|
+ void run();
|
|
+private:
|
|
+ std::vector<ZXing::Result> readBarcodes(const QImage &img) const;
|
|
+ std::vector<ZXing::Result> readBarcodes(QVideoFrame &frame) const;
|
|
+
|
|
+ CameraControllerPrivate *m_parent;
|
|
+ // notice that since ZXIng 2.2.0 this is renamed to 'ReaderOptions'.
|
|
+ // this was released in December 2023, so we'll be using the old stuff
|
|
+ // for quite a bit longer to keep stuff compiling on older systems.
|
|
+ ZXing::DecodeHints m_decodeHints;
|
|
+};
|
|
+
|
|
+
|
|
+// --------------------------------------------------------------------
|
|
+
|
|
+CameraControllerPrivate::CameraControllerPrivate(CameraController *qq)
|
|
+ : state(NotAsked),
|
|
+ q(qq)
|
|
+{
|
|
+#ifdef TARGET_OS_Android
|
|
+ auto guiApp = qobject_cast<QGuiApplication*>(QCoreApplication::instance());
|
|
+ assert(guiApp);
|
|
+ QObject::connect(guiApp, &QGuiApplication::applicationStateChanged, qq, [=](Qt::ApplicationState appState) {
|
|
+ // the application state will turn to "Inactive" when the Android dialog
|
|
+ // asking permissions is put on top, and when the user enters data, we are
|
|
+ // turned into active again.
|
|
+ // So check if we are already authorized as that avoid us turning off
|
|
+ // the camera just after turning it on the first time.
|
|
+ if (state == Authorized && appState == Qt::ApplicationInactive) {
|
|
+ logInfo(10005) << "App state inactive, turning off camera";
|
|
+ // when the user leaves the app screen, the permissions granted to us
|
|
+ // may have changed, so we need to re-ask.
|
|
+ state = NotAsked;
|
|
+ if (cameraStarted) {
|
|
+ // QtMultimedia doesn't like us not turning off the camera when we get small.
|
|
+ // so make sure we do, we also cancel the scan request.
|
|
+ lock.lock();
|
|
+ cameraStarted = false;
|
|
+ lock.unlock();
|
|
+ emit q->cameraActiveChanged();
|
|
+ }
|
|
+ }
|
|
+ });
|
|
+#endif
|
|
+}
|
|
+
|
|
+void CameraControllerPrivate::initCamera()
|
|
+{
|
|
+ QCamera *cam = qobject_cast<QCamera *>(camera);
|
|
+ if (!cam)
|
|
+ return;
|
|
+ QCameraFormat preferred;
|
|
+ bool preferredIsCheap = false;
|
|
+ for (const auto &format : cam->cameraDevice().videoFormats()) {
|
|
+ bool formatIsCheap; // if true, we don't need to go via QImage (we avoid double conversion)
|
|
+ switch (format.pixelFormat()) {
|
|
+ case QVideoFrameFormat::Format_Invalid:
|
|
+ case QVideoFrameFormat::Format_XRGB8888:
|
|
+ case QVideoFrameFormat::Format_XBGR8888:
|
|
+ case QVideoFrameFormat::Format_RGBA8888:
|
|
+ case QVideoFrameFormat::Format_SamplerExternalOES:
|
|
+ case QVideoFrameFormat::Format_Jpeg:
|
|
+ case QVideoFrameFormat::Format_SamplerRect:
|
|
+ case QVideoFrameFormat::Format_YUV420P10:
|
|
+ formatIsCheap = false;
|
|
+ break;
|
|
+ default:
|
|
+ formatIsCheap = true;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ logInfo(10005) << " + " << format.resolution().width() << "x" << format.resolution().height()
|
|
+ << "::" << format.pixelFormat() << formatIsCheap << "framerate:"
|
|
+ << format.minFrameRate() << "-" << format.maxFrameRate();
|
|
+
|
|
+ if (preferred.isNull()) {
|
|
+ preferred = format;
|
|
+ preferredIsCheap = formatIsCheap;
|
|
+ logInfo(10005) << "picked";
|
|
+ }
|
|
+ else {
|
|
+ auto size = format.resolution();
|
|
+ auto oldSize = preferred.resolution();
|
|
+ if (preferredIsCheap && !formatIsCheap)
|
|
+ continue;
|
|
+ // avoid going for the biggest feed, but not too small either.
|
|
+ if (oldSize.width() < 800 || (size.width() < oldSize.width() && size.width() >= 800)
|
|
+ || (size.width() == oldSize.width() && size.height() > oldSize.height() && size.height() < 1000)) {
|
|
+ preferred = format;
|
|
+ logInfo(10005) << "picked";
|
|
+ }
|
|
+ else if (size == oldSize && format.maxFrameRate() < preferred.maxFrameRate()) {
|
|
+ preferred = format;
|
|
+ logInfo(10005) << "picked";
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ logCritical(10005).nospace() << "Selected camera resolution: " << preferred.resolution().width() << "x" << preferred.resolution().height();
|
|
+ preferredFormat = preferred;
|
|
+}
|
|
+
|
|
+void CameraControllerPrivate::checkState()
|
|
+{
|
|
+ if (state != Authorized)
|
|
+ return;
|
|
+ if (!cameraLoaded || !visible) {
|
|
+ cameraLoaded = true;
|
|
+ visible = true;
|
|
+ emit q->visibleChanged();
|
|
+ emit q->loadCameraChanged();
|
|
+
|
|
+ // then wait an event before turning on the actual camera
|
|
+ QTimer::singleShot(30, q, SLOT(checkState()));
|
|
+ return;
|
|
+ }
|
|
+ if (camera && videoSink && !cameraStarted && scanRequest.get()) {
|
|
+ QCamera *cam = qobject_cast<QCamera *>(camera);
|
|
+ auto sink = qobject_cast<QVideoSink*>(videoSink);
|
|
+ if (!cam || !sink) { // here to detect bug in QML
|
|
+ logFatal(10005) << "invalid or no camera or sink object set";
|
|
+ return;
|
|
+ }
|
|
+ if (cam->error() != QCamera::NoError)
|
|
+ logFatal(10005) << "CameraController found cam error:" << cam->errorString();
|
|
+
|
|
+ if (!preferredFormat.isNull())
|
|
+ cam->setCameraFormat(preferredFormat);
|
|
+ // best too least-acceptable focus mode.
|
|
+ constexpr QCamera::FocusMode f_modes[] = { QCamera::FocusModeAutoNear, QCamera::FocusModeAuto,
|
|
+ QCamera::FocusModeHyperfocal, QCamera::FocusModeInfinity };
|
|
+ for (auto m : f_modes) {
|
|
+ if (cam->isFocusModeSupported(m)) {
|
|
+ cam->setFocusMode(m);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ constexpr QCamera::WhiteBalanceMode w_modess[] = { QCamera::WhiteBalanceShade, QCamera::WhiteBalanceAuto };
|
|
+ for (auto m : w_modess) {
|
|
+ if (cam->isWhiteBalanceModeSupported(m)) {
|
|
+ cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ constexpr QCamera::ExposureMode e_modes[] = { QCamera::ExposureBarcode,
|
|
+ QCamera::ExposurePortrait, QCamera::ExposureAuto };
|
|
+ for (auto m : e_modes) {
|
|
+ if (cam->isExposureModeSupported(m)) {
|
|
+ cam->setExposureMode(m);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ cameraStarted = true;
|
|
+ QObject::connect(sink, &QVideoSink::videoFrameChanged, q, [=](const QVideoFrame &frame) {
|
|
+ currentFrame = frame;
|
|
+ });
|
|
+
|
|
+ assert(scanningThread == nullptr);
|
|
+ scanningThread = new QRScanningThread(this);
|
|
+ QObject::connect (scanningThread, SIGNAL(finished()), q, SLOT(qrScanFinished()), Qt::QueuedConnection);
|
|
+ scanningThread->start();
|
|
+
|
|
+ logDebug(10005) << "Camera active is now true";
|
|
+ emit q->cameraActiveChanged(); // this emit makes QML activate the camera
|
|
+ }
|
|
+}
|
|
+
|
|
+// --------------------------------------------------------------------
|
|
+
|
|
+QRScanningThread::QRScanningThread(CameraControllerPrivate *parent)
|
|
+ : scanType(QRScanner::InvalidType),
|
|
+ m_parent(parent)
|
|
+{
|
|
+ m_decodeHints.setFormats(ZXing::BarcodeFormat::QRCode);
|
|
+ m_decodeHints.setTryHarder(true);
|
|
+}
|
|
+
|
|
+void QRScanningThread::run()
|
|
+{
|
|
+ auto lastFrameScanned = 0;
|
|
+ while (true) {
|
|
+ const auto now = time(nullptr);
|
|
+ auto sleep = 34 - (now - lastFrameScanned); // assume 30 - FPS
|
|
+ // Sleep if we are too fast and (assuming 33.3 ms per frame) we would end up
|
|
+ // parsing the same frame twice.
|
|
+ if (sleep > 0)
|
|
+ QThread::msleep(sleep);
|
|
+
|
|
+ m_parent->lock.lock();
|
|
+ bool exit = !m_parent->cameraStarted;
|
|
+ QVideoFrame frame = m_parent->currentFrame;
|
|
+ m_parent->lock.unlock();
|
|
+ if (exit)
|
|
+ return;
|
|
+
|
|
+ lastFrameScanned = time(nullptr);
|
|
+ auto results = readBarcodes(frame);
|
|
+ for (const auto &result : results) {
|
|
+ const auto &bytes = result.bytes();
|
|
+ // logInfo(10005) << "result:" << QString::fromUtf8(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
+
|
|
+ // Test if the QR held a Private key
|
|
+ // We support WIF encoded private keys.
|
|
+ // first, when it starts with 'bch-wif:' this helps, but needs to be cut off.
|
|
+ const bool wifPrefix = bytes.size() >= 58 && bytes.size() < 63
|
|
+ && (bytes[8] == 'K' || bytes[8] == 'L')
|
|
+ && 0 == memcmp(&bytes[0], "bch-wif:", 8);
|
|
+
|
|
+ if (wifPrefix || (bytes.size() >= 50 && bytes.size() < 55 && (bytes[0] == 'K' || bytes[0] == 'L'))) {
|
|
+ // might be one!!
|
|
+ const size_t prefixSize = wifPrefix ? 8 : 0;
|
|
+ const std::string str(reinterpret_cast<const char*>(bytes.data() + prefixSize), bytes.size() - prefixSize);
|
|
+ std::vector<uint8_t> dummy;
|
|
+ if (Base58::decodeCheck(str, dummy)) {
|
|
+ // good enough for me. Further checking is done by the app, we just exit scanning now.
|
|
+ scanType = QRScanner::PrivateKeyWIF;
|
|
+ text = QString::fromUtf8(str);
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Ok, what about a bip21 style url, or a plain address?
|
|
+ // -> starts with bitcoincash: (which is 12 chars, including that colon)
|
|
+ if (bytes.size() > 12 + 40 && memcmp("bitcoincash:", bytes.data(), 12) == 0) {
|
|
+ text = QString::fromUtf8(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
+ scanType = QRScanner::PaymentDetails;
|
|
+ return;
|
|
+ }
|
|
+ if (bytes.size() > 40 && bytes.size() < 45 && (bytes[0] == 'q' || bytes[0] == 'p')) {
|
|
+ // possibly a raw bitcoin cash address.
|
|
+ text = QString::fromUtf8(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
+ scanType = QRScanner::PaymentDetails;
|
|
+ return;
|
|
+ }
|
|
+ if (bytes.size() > 8 + 40 && memcmp("bchtest:", bytes.data(), 8) == 0) {
|
|
+ text = QString::fromUtf8(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
+ scanType = QRScanner::PaymentDetailsTestnet;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ // not those then, ok. Then check if its a seed :-)
|
|
+ /*
|
|
+ * The Seed QR would obviously just use the 12 or 24 words sentence in a QR.
|
|
+ * Simple.
|
|
+ *
|
|
+ * But a pretty big wallet has instead put a bit more in the QR which ends up having
|
|
+ * the following content:
|
|
+ *
|
|
+ * 1|this holds twelve words|livenet|m/44'/0'/0'|false
|
|
+ *
|
|
+ * No clue what the leading 1 and the trailing false are about. All wallets I tried
|
|
+ * have those :shrug:
|
|
+ *
|
|
+ * Lets try to recognize those two types of QR.
|
|
+ * ------
|
|
+ * While seeds can be verified with their checksum, here we just check if the general
|
|
+ * pattern fits.
|
|
+ * Only normal characters and spaces are allowed in the basic seed, and 12 words or 24 words.
|
|
+ */
|
|
+ if (bytes.size() > 12 * 3 + 11) { // at least enough chars for a seed.
|
|
+ QString possibleSeed = QString::fromUtf8(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
+ if (possibleSeed.startsWith("1|") && possibleSeed.endsWith("|livenet|m/44'/0'/0'|false"))
|
|
+ possibleSeed = possibleSeed.mid(2, possibleSeed.length() - 28);
|
|
+
|
|
+ int wordCount = 0;
|
|
+ bool seenSpace = false;
|
|
+ bool failedChecks = false;
|
|
+ for (auto i = 0; i < possibleSeed.size(); ++i) {
|
|
+ auto c = possibleSeed.at(i);
|
|
+ if (c.isDigit() || c.isSymbol()) {
|
|
+ failedChecks = true;
|
|
+ }
|
|
+ else if (c.isSpace()) {
|
|
+ if (seenSpace || i == 0)
|
|
+ failedChecks = true; // double space or leading space
|
|
+ seenSpace = true;
|
|
+ ++wordCount;
|
|
+ }
|
|
+ else if (c.isLetter()) {
|
|
+ seenSpace = false;
|
|
+ }
|
|
+ if (failedChecks)
|
|
+ break;
|
|
+ }
|
|
+ if (seenSpace == false && wordCount > 0)
|
|
+ ++wordCount; // one more word not registered due to lack of space after.
|
|
+ if (!failedChecks && (wordCount == 12 || wordCount == 24)) {
|
|
+ scanType = QRScanner::Seed;
|
|
+ text = possibleSeed;
|
|
+ return;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+std::vector<ZXing::Result> QRScanningThread::readBarcodes(const QImage &img) const
|
|
+{
|
|
+ auto imageFormat = img.format();
|
|
+ if (imageFormat == QImage::Format_Invalid) // likely a damaged frame in the feed
|
|
+ return {};
|
|
+ auto zxImageFormat = ZXing::ImageFormat::None;
|
|
+ switch (imageFormat) {
|
|
+ case QImage::Format_ARGB32:
|
|
+ case QImage::Format_RGB32:
|
|
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
+ zxImageFormat = ZXing::ImageFormat::BGRX;
|
|
+#else
|
|
+ zxImageFormat = ImageFormat::XRGB;
|
|
+#endif
|
|
+ break;
|
|
+ case QImage::Format_RGB888: zxImageFormat = ZXing::ImageFormat::RGB; break;
|
|
+ case QImage::Format_RGBX8888:
|
|
+ case QImage::Format_RGBA8888: zxImageFormat = ZXing::ImageFormat::RGBX; break;
|
|
+ case QImage::Format_Grayscale8: zxImageFormat = ZXing::ImageFormat::Lum; break;
|
|
+ default: break;
|
|
+ }
|
|
+
|
|
+ if (zxImageFormat == ZXing::ImageFormat::None) {
|
|
+ QImage gray = img.convertToFormat(QImage::Format_Grayscale8) ;
|
|
+ return ZXing::ReadBarcodes({gray.bits(), gray.width(), gray.height(), ZXing::ImageFormat::Lum, static_cast<int>(gray.bytesPerLine())}, m_decodeHints);
|
|
+ }
|
|
+
|
|
+ ZXing::ImageView buf(img.bits(), img.width(), img.height(), zxImageFormat, static_cast<int>(img.bytesPerLine()));
|
|
+ return ZXing::ReadBarcodes(buf, m_decodeHints);
|
|
+}
|
|
+
|
|
+std::vector<ZXing::Result> QRScanningThread::readBarcodes(QVideoFrame &frame) const
|
|
+{
|
|
+ ZXing::ImageFormat fmt = ZXing::ImageFormat::None;
|
|
+ int pixStride = 0;
|
|
+ int pixOffset = 0;
|
|
+
|
|
+ // note that the comments behind the values are the Qt5 formats.
|
|
+ switch (frame.pixelFormat()) {
|
|
+ case QVideoFrameFormat::Format_ARGB8888: // ARGB32
|
|
+ case QVideoFrameFormat::Format_ARGB8888_Premultiplied: // ARGB32_Premultiplied
|
|
+ case QVideoFrameFormat::Format_RGBX8888: // RGB32
|
|
+ fmt = ZXing::ImageFormat::BGRX;
|
|
+ break;
|
|
+ case QVideoFrameFormat::Format_BGRA8888: // BGRA32
|
|
+ case QVideoFrameFormat::Format_BGRA8888_Premultiplied: // BGRA32_Premultiplied
|
|
+ case QVideoFrameFormat::Format_BGRX8888: // BGR32
|
|
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
+ fmt = ZXing::ImageFormat::RGBX;
|
|
+#else
|
|
+ fmt = ImageFormat::XBGR;
|
|
+#endif
|
|
+ break;
|
|
+ case QVideoFrameFormat::Format_P010:
|
|
+ case QVideoFrameFormat::Format_P016:
|
|
+ fmt = ZXing::ImageFormat::Lum, pixStride = 1; break;
|
|
+ case QVideoFrameFormat::Format_AYUV: // AYUV444
|
|
+ case QVideoFrameFormat::Format_AYUV_Premultiplied: // AYUV444_Premultiplied
|
|
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
+ fmt = ZXing::ImageFormat::Lum, pixStride = 4, pixOffset = 3;
|
|
+#else
|
|
+ fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2;
|
|
+#endif
|
|
+ break;
|
|
+ case QVideoFrameFormat::Format_YUV420P: // YUV420P
|
|
+ case QVideoFrameFormat::Format_NV12: // NV12
|
|
+ case QVideoFrameFormat::Format_NV21: // NV21
|
|
+ case QVideoFrameFormat::Format_IMC1: // IMC1
|
|
+ case QVideoFrameFormat::Format_IMC2: // IMC2
|
|
+ case QVideoFrameFormat::Format_IMC3: // IMC3
|
|
+ case QVideoFrameFormat::Format_IMC4: // IMC4
|
|
+ case QVideoFrameFormat::Format_YV12: // YV12
|
|
+ case QVideoFrameFormat::Format_Y8: // Y8
|
|
+ case QVideoFrameFormat::Format_YUV422P: // YUV422P
|
|
+ fmt = ZXing::ImageFormat::Lum; break;
|
|
+ case QVideoFrameFormat::Format_UYVY: // UYVY
|
|
+ fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
|
+ case QVideoFrameFormat::Format_YUYV: // YUYV
|
|
+ fmt = ZXing::ImageFormat::Lum, pixStride = 2; break;
|
|
+ case QVideoFrameFormat::Format_Y16: // Y16
|
|
+ fmt = ZXing::ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
|
+
|
|
+ case QVideoFrameFormat::Format_ABGR8888: // ABGR32
|
|
+#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
+ fmt = ZXing::ImageFormat::RGBX;
|
|
+#else
|
|
+ fmt = ZXing::ImageFormat::XBGR;
|
|
+#endif
|
|
+ break;
|
|
+ default: break;
|
|
+ }
|
|
+
|
|
+ if (fmt != ZXing::ImageFormat::None) {
|
|
+ if (!frame.isValid() || !frame.map(QVideoFrame::ReadOnly)){
|
|
+ logDebug(10005) << "invalid QVideoFrame: could not map memory";
|
|
+ return {};
|
|
+ }
|
|
+ QScopeGuard unmap([&] { frame.unmap(); });
|
|
+
|
|
+ constexpr int FirstPlane = 0;
|
|
+ return ZXing::ReadBarcodes(
|
|
+ {frame.bits(FirstPlane) + pixOffset, frame.width(), frame.height(), fmt, frame.bytesPerLine(FirstPlane), pixStride}, m_decodeHints);
|
|
+ } else {
|
|
+ return readBarcodes(frame.toImage());
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+// --------------------------------------------------------------------
|
|
+
|
|
+CameraController::CameraController(QObject *parent)
|
|
+ : QObject(parent),
|
|
+ d(new CameraControllerPrivate(this))
|
|
+{
|
|
+ // The Android permissions requesting stuff returns results in a different thread,
|
|
+ // allow an easy way to move back to the main thread using a connection.
|
|
+ QObject::connect(this, SIGNAL(startCheckState()), this, SLOT(checkState()), Qt::QueuedConnection);
|
|
+ QObject::connect(this, SIGNAL(cameraPermissionReceived()), this, SLOT(handleCameraPermission()), Qt::QueuedConnection);
|
|
+}
|
|
+
|
|
+void CameraController::startRequest(QRScanner *request)
|
|
+{
|
|
+ assert(request);
|
|
+ d->scanRequest = request;
|
|
+ setHelpText(request->helpText());
|
|
+ emit isScanTypeChanged();
|
|
+
|
|
+ if (!d->visible) {
|
|
+ d->visible = true;
|
|
+ emit visibleChanged();
|
|
+ }
|
|
+
|
|
+ if (d->state == NotAsked || d->state == Denied) {
|
|
+#if TARGET_OS_Android
|
|
+ auto me = QJniObject(QNativeInterface::QAndroidApplication::context());
|
|
+ me.callObjectMethod("canUseCamera", "()V");
|
|
+#else
|
|
+ setCameraPermission(true);
|
|
+#endif
|
|
+ return;
|
|
+ }
|
|
+ // give the overlay screen time to appear before
|
|
+ // activating the camera.
|
|
+ // This avoids waiting with the overlay screen until the camera
|
|
+ // is ready to stream which (depending on drivers) may take half a second.
|
|
+ QTimer::singleShot(1, this, SLOT(checkState()));
|
|
+}
|
|
+
|
|
+void CameraController::abortRequest(QRScanner *request)
|
|
+{
|
|
+ if (request && d->scanRequest == request) {
|
|
+ // The scanning thread will abort and nicely shutdown on change of this variable
|
|
+ d->lock.lock();
|
|
+ d->cameraStarted = false;
|
|
+ d->lock.unlock();
|
|
+ emit cameraActiveChanged();
|
|
+
|
|
+ if (d->scanningThread == nullptr) {
|
|
+ // then the above would have no effect;
|
|
+ qrScanFinished();
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+void CameraController::abort()
|
|
+{
|
|
+ abortRequest(d->scanRequest);
|
|
+}
|
|
+
|
|
+bool CameraController::isPayment() const
|
|
+{
|
|
+ if (d->scanRequest == nullptr)
|
|
+ return false;
|
|
+ return d->scanRequest->isPayment();
|
|
+}
|
|
+
|
|
+bool CameraController::torchEnabled() const
|
|
+{
|
|
+ return d->torchEnabled;
|
|
+}
|
|
+
|
|
+void CameraController::setTorchEnabled(bool on)
|
|
+{
|
|
+ if (d->torchEnabled == on)
|
|
+ return;
|
|
+ if (!d->cameraStarted) {
|
|
+ assert(d->torchEnabled == false);
|
|
+ return;
|
|
+ }
|
|
+ QCamera *cam = qobject_cast<QCamera *>(d->camera);
|
|
+ if (cam == nullptr)
|
|
+ return;
|
|
+ if (cam->isTorchModeSupported(on ? QCamera::TorchOn : QCamera::TorchOff) == false) {
|
|
+ logWarning(10005) << "Trying to toggle torch, but the camera does not support that";
|
|
+ return;
|
|
+ }
|
|
+ d->torchEnabled = on;
|
|
+ cam->setTorchMode(on ? QCamera::TorchOn : QCamera::TorchOff);
|
|
+ logFatal(10005) << "toggling the torch";
|
|
+
|
|
+ if (on) {
|
|
+ if (cam->isWhiteBalanceModeSupported(QCamera::WhiteBalanceFlash))
|
|
+ cam->setWhiteBalanceMode(QCamera::WhiteBalanceFlash);
|
|
+ } else if (cam->whiteBalanceMode() == QCamera::WhiteBalanceFlash) {
|
|
+ // we should not just turn it off but also set it to the most appropriate normal mode.
|
|
+ constexpr QCamera::WhiteBalanceMode w_modess[] = { QCamera::WhiteBalanceShade, QCamera::WhiteBalanceAuto };
|
|
+ for (auto m : w_modess) {
|
|
+ if (cam->isWhiteBalanceModeSupported(m)) {
|
|
+ cam->setWhiteBalanceMode(QCamera::WhiteBalanceAuto);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ emit torchEnabledChanged();
|
|
+}
|
|
+
|
|
+void CameraController::setCamera(QObject *object)
|
|
+{
|
|
+ if (object == d->camera)
|
|
+ return;
|
|
+ d->camera = object;
|
|
+ emit cameraChanged();
|
|
+ QTimer::singleShot(10, this, SLOT(initCamera()));
|
|
+}
|
|
+
|
|
+QObject *CameraController::camera() const
|
|
+{
|
|
+ return d->camera;
|
|
+}
|
|
+
|
|
+void CameraController::setVideoSink(QObject *object)
|
|
+{
|
|
+ if (d->videoSink == object)
|
|
+ return;
|
|
+ auto old = qobject_cast<QVideoSink*>(d->videoSink);
|
|
+ if (old)
|
|
+ QObject::disconnect(old, nullptr, this, nullptr);
|
|
+ d->videoSink = object;
|
|
+ emit videoSinkChanged();
|
|
+}
|
|
+
|
|
+QObject *CameraController::videoSink() const
|
|
+{
|
|
+ return d->videoSink;
|
|
+}
|
|
+
|
|
+bool CameraController::loadCamera() const
|
|
+{
|
|
+ return d->cameraLoaded;
|
|
+}
|
|
+
|
|
+bool CameraController::cameraActive() const
|
|
+{
|
|
+ return d->cameraStarted;
|
|
+}
|
|
+
|
|
+bool CameraController::visible() const
|
|
+{
|
|
+ return d->visible;
|
|
+}
|
|
+
|
|
+void CameraController::qrScanFinished()
|
|
+{
|
|
+ QString resultText;
|
|
+ QRScanner::ScanType scanType = QRScanner::InvalidType;
|
|
+ if (d->scanningThread) {
|
|
+ resultText = d->scanningThread->text;
|
|
+ scanType = d->scanningThread->scanType;
|
|
+ d->scanningThread->deleteLater();
|
|
+ d->scanningThread = nullptr;
|
|
+ d->currentFrame = QVideoFrame();
|
|
+ }
|
|
+ // stop copying video frames
|
|
+ assert(d->videoSink);
|
|
+ QObject::disconnect(d->videoSink, nullptr, this, nullptr);
|
|
+
|
|
+ d->visible = false;
|
|
+ emit visibleChanged();
|
|
+ setHelpText(QString());
|
|
+ if (d->scanRequest) {
|
|
+ d->scanRequest->finishedScan(resultText, scanType);
|
|
+ d->scanRequest = nullptr;
|
|
+ }
|
|
+
|
|
+ QCamera *cam = qobject_cast<QCamera *>(d->camera);
|
|
+ if (cam)
|
|
+ cam->setTorchMode(QCamera::TorchOff);
|
|
+ if (d->torchEnabled) {
|
|
+ // don't use the simple setter as that one is doing much more.
|
|
+ d->torchEnabled = false;
|
|
+ emit torchEnabledChanged();
|
|
+ }
|
|
+ // Have a bit of delay with actually turning off the camera.
|
|
+ QTimer::singleShot(100, this, [=]() {
|
|
+ d->cameraStarted = false;
|
|
+ emit cameraActiveChanged(); // makes the QML 'stop()' the camera.
|
|
+ });
|
|
+}
|
|
+
|
|
+void CameraController::checkState()
|
|
+{
|
|
+ d->checkState();
|
|
+}
|
|
+
|
|
+void CameraController::initCamera()
|
|
+{
|
|
+ d->initCamera();
|
|
+}
|
|
+
|
|
+void CameraController::handleCameraPermission()
|
|
+{
|
|
+ if (d->state == Denied)
|
|
+ abort();
|
|
+ else
|
|
+ QTimer::singleShot(100, this, SLOT(checkState()));
|
|
+}
|
|
+
|
|
+QString CameraController::helpText() const
|
|
+{
|
|
+ return d->helpText;
|
|
+}
|
|
+
|
|
+QRScanner::ScanType CameraController::scanType() const
|
|
+{
|
|
+ if (d->scanRequest == nullptr)
|
|
+ return QRScanner::InvalidType;
|
|
+ return d->scanRequest->scanType();
|
|
+}
|
|
+
|
|
+void CameraController::setCameraPermission(bool allowed)
|
|
+{
|
|
+ // lets assume the native OS called this in a thread that is not us.
|
|
+ d->state = allowed ? Authorized : Denied;
|
|
+ emit cameraPermissionReceived();
|
|
+}
|
|
+
|
|
+void CameraController::setHelpText(const QString &text)
|
|
+{
|
|
+ if (d->helpText == text)
|
|
+ return;
|
|
+ d->helpText = text;
|
|
+ emit helpTextChanged();
|
|
+}
|
|
+
|
|
+#include "moc_CameraController.cpp"
|
|
diff --git a/src/QRCreator.cpp b/src/QRCreator.cpp
|
|
index 361ee18..67eee9c 100644
|
|
--- a/src/QRCreator.cpp
|
|
+++ b/src/QRCreator.cpp
|
|
@@ -19,9 +19,9 @@
|
|
|
|
#include <Logger.h>
|
|
// cmake ensures the presence of the ZXing lib.
|
|
-#include <ZXing/BarcodeFormat.h>
|
|
-#include <ZXing/MultiFormatWriter.h>
|
|
-#include <ZXing/BitMatrix.h>
|
|
+#include <ZXing/ZXingCpp.h>
|
|
+#include <ZXing/CreateBarcode.h>
|
|
+#include <ZXing/WriteBarcode.h>
|
|
|
|
#include <codecvt>
|
|
#include <locale>
|
|
@@ -43,29 +43,8 @@ QImage QRCreator::requestImage(const QString &id, QSize *size, const QSize &requ
|
|
} else if (m_type == RawString) {
|
|
data = id.toUtf8();
|
|
}
|
|
-
|
|
- auto writer = ZXing::MultiFormatWriter(ZXing::BarcodeFormat::QRCode).setMargin(16);
|
|
- /*
|
|
- * In newer versions of zxing there is a direct std::string version of the encode()
|
|
- * call which is nice to avoid the extra coversion.
|
|
- * For now we leave this extra code here as long as we are still able to compile and
|
|
- * run on Ubuntu 2022.04 (jammy) which doesn't have this new call.
|
|
- *
|
|
- * Ironically, the codecvt below code is deprecated in C++17, so you get warnings now.
|
|
- * Can't win this one, I guess...
|
|
- * But the promise is that it will be part of C++ till the 2026 release,
|
|
- * and I prefer it actually compiling on older zxing. So maybe lets just
|
|
- * plan to remove the wstring conversion in a year or so (TZ: Feb 2024)
|
|
- */
|
|
- std::wstring wdata = std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(data);
|
|
- ZXing::BitMatrix matrix = writer.encode(wdata, 250, 250);
|
|
-
|
|
- QImage result = QImage(matrix.height(), matrix.width(), QImage::Format_RGB32);
|
|
- constexpr uint black = 0xFF000000;
|
|
- constexpr uint white = 0xFFFFFFFF;
|
|
- for (int y = 0; y < matrix.height(); ++y)
|
|
- for (int x = 0; x < matrix.width(); ++x)
|
|
- result.setPixel(x, y, matrix.get(x, y) ? black : white);
|
|
-
|
|
- return result;
|
|
+ auto barcode = ZXing::CreateBarcodeFromText(data, ZXing::BarcodeFormat::QRCode);
|
|
+ auto image = ZXing::WriteBarcodeToImage(barcode);
|
|
+ QImage result(image.data(), image.width(), image.height(), image.width(), QImage::Format_Grayscale8);
|
|
+ return result.copy();
|
|
}
|
|
diff --git a/src/QRCreator_zxing2.cpp b/src/QRCreator_zxing2.cpp
|
|
new file mode 100644
|
|
index 0000000..361ee18
|
|
--- /dev/null
|
|
+++ b/src/QRCreator_zxing2.cpp
|
|
@@ -0,0 +1,71 @@
|
|
+/*
|
|
+ * This file is part of the Flowee project
|
|
+ * Copyright (C) 2018-2024 Tom Zander <tom@flowee.org>
|
|
+ *
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
+ * it under the terms of the GNU General Public License as published by
|
|
+ * the Free Software Foundation, either version 3 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ * GNU General Public License for more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License
|
|
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
+ */
|
|
+#include "QRCreator.h"
|
|
+
|
|
+#include <Logger.h>
|
|
+// cmake ensures the presence of the ZXing lib.
|
|
+#include <ZXing/BarcodeFormat.h>
|
|
+#include <ZXing/MultiFormatWriter.h>
|
|
+#include <ZXing/BitMatrix.h>
|
|
+
|
|
+#include <codecvt>
|
|
+#include <locale>
|
|
+
|
|
+QRCreator::QRCreator(QRType type)
|
|
+ : QQuickImageProvider(QQmlImageProviderBase::Image),
|
|
+ m_type(type)
|
|
+{
|
|
+}
|
|
+
|
|
+QImage QRCreator::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
|
|
+{
|
|
+ Q_UNUSED(size);
|
|
+ Q_UNUSED(requestedSize);
|
|
+ std::string data; // assumed utf8
|
|
+ if (m_type == URLEncoded) {
|
|
+ QUrl url(id); // go via URL to encode spaces and special chars
|
|
+ data = url.toEncoded(QUrl::EncodeSpaces);
|
|
+ } else if (m_type == RawString) {
|
|
+ data = id.toUtf8();
|
|
+ }
|
|
+
|
|
+ auto writer = ZXing::MultiFormatWriter(ZXing::BarcodeFormat::QRCode).setMargin(16);
|
|
+ /*
|
|
+ * In newer versions of zxing there is a direct std::string version of the encode()
|
|
+ * call which is nice to avoid the extra coversion.
|
|
+ * For now we leave this extra code here as long as we are still able to compile and
|
|
+ * run on Ubuntu 2022.04 (jammy) which doesn't have this new call.
|
|
+ *
|
|
+ * Ironically, the codecvt below code is deprecated in C++17, so you get warnings now.
|
|
+ * Can't win this one, I guess...
|
|
+ * But the promise is that it will be part of C++ till the 2026 release,
|
|
+ * and I prefer it actually compiling on older zxing. So maybe lets just
|
|
+ * plan to remove the wstring conversion in a year or so (TZ: Feb 2024)
|
|
+ */
|
|
+ std::wstring wdata = std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(data);
|
|
+ ZXing::BitMatrix matrix = writer.encode(wdata, 250, 250);
|
|
+
|
|
+ QImage result = QImage(matrix.height(), matrix.width(), QImage::Format_RGB32);
|
|
+ constexpr uint black = 0xFF000000;
|
|
+ constexpr uint white = 0xFFFFFFFF;
|
|
+ for (int y = 0; y < matrix.height(); ++y)
|
|
+ for (int x = 0; x < matrix.width(); ++x)
|
|
+ result.setPixel(x, y, matrix.get(x, y) ? black : white);
|
|
+
|
|
+ return result;
|
|
+}
|
|
--
|
|
2.53.0
|
|
|