From fedc178827c5648b0ccbdd2cd08dcebc920ac416 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 28 Jun 2023 17:00:34 -0400 Subject: [PATCH] Port the reCAPTCHA dialog to QML --- CMakeLists.txt | 5 +- resources/qml/Root.qml | 13 ++++ resources/qml/dialogs/ReCaptchaDialog.qml | 63 +++++++++++++++++++ src/ReCaptcha.cpp | 28 +++++++++ src/ReCaptcha.h | 32 ++++++++++ src/dialogs/ReCaptcha.cpp | 74 ----------------------- src/dialogs/ReCaptcha.h | 29 --------- src/ui/UIA.cpp | 28 +++------ src/ui/UIA.h | 5 +- 9 files changed, 153 insertions(+), 124 deletions(-) create mode 100644 resources/qml/dialogs/ReCaptchaDialog.qml create mode 100644 src/ReCaptcha.cpp create mode 100644 src/ReCaptcha.h delete mode 100644 src/dialogs/ReCaptcha.cpp delete mode 100644 src/dialogs/ReCaptcha.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 50940246..de5df88b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -353,8 +353,6 @@ set(SRC_FILES # Dialogs src/dialogs/FallbackAuth.cpp src/dialogs/FallbackAuth.h - src/dialogs/ReCaptcha.cpp - src/dialogs/ReCaptcha.h # Emoji src/emoji/Provider.cpp @@ -485,6 +483,8 @@ set(SRC_FILES src/PowerlevelsEditModels.h src/ReadReceiptsModel.cpp src/ReadReceiptsModel.h + src/ReCaptcha.cpp + src/ReCaptcha.h src/RegisterPage.cpp src/RegisterPage.h src/RoomDirectoryModel.cpp @@ -776,6 +776,7 @@ set(QML_SOURCES resources/qml/dialogs/PowerLevelSpacesApplyDialog.qml resources/qml/dialogs/RawMessageDialog.qml resources/qml/dialogs/ReadReceipts.qml + resources/qml/dialogs/ReCaptchaDialog.qml resources/qml/dialogs/RoomDirectory.qml resources/qml/dialogs/RoomMembers.qml resources/qml/dialogs/AllowedRoomsSettingsDialog.qml diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index e26b386a..1fd0c610 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -360,6 +360,7 @@ Pane { onAccepted: UIA.continue3pidReceived() } + Connections { function onConfirm3pidToken() { uiaConfirmationLinkDialog.open(); @@ -381,6 +382,18 @@ Pane { function onPrompt3pidToken() { uiaTokenPrompt.show(); } + function onReCaptcha(recaptcha) { + var component = Qt.createComponent("qrc:/resources/qml/dialogs/ReCaptchaDialog.qml"); + if (component.status == Component.Ready) { + var dialog = component.createObject(timelineRoot, { + "recaptcha": recaptcha + }); + dialog.show(); + destroyOnClose(dialog); + } else { + console.error("Failed to create component: " + component.errorString()); + } + } target: UIA } diff --git a/resources/qml/dialogs/ReCaptchaDialog.qml b/resources/qml/dialogs/ReCaptchaDialog.qml new file mode 100644 index 00000000..0da62cbc --- /dev/null +++ b/resources/qml/dialogs/ReCaptchaDialog.qml @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick +import QtQuick.Controls +import im.nheko + +ApplicationWindow { + id: recaptchaRoot + + required property ReCaptcha recaptcha + + function accept() { + recaptcha.confirm(); + recaptchaRoot.close(); + } + + function reject() { + recaptcha.cancel(); + recaptchaRoot.close(); + } + + color: palette.window + title: recaptcha.context + flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint | Qt.WindowTitleHint + height: msg.implicitHeight + footer.implicitHeight + width: Math.max(msg.implicitWidth, footer.implicitWidth) + + Shortcut { + sequence: StandardKey.Cancel + onActivated: recaptchaRoot.reject() + } + + Label { + id: msg + + anchors.fill: parent + padding: 8 + text: qsTr("Solve the reCAPTCHA and press the confirm button") + } + + footer: DialogButtonBox { + onAccepted: recaptchaRoot.accept() + onRejected: recaptchaRoot.reject() + + Button { + text: qsTr("Open reCAPTCHA") + onClicked: recaptcha.openReCaptcha() + } + + Button { + text: qsTr("Cancel") + DialogButtonBox.buttonRole: DialogButtonBox.RejectRole + } + + Button { + text: qsTr("Confirm") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + } + } + +} diff --git a/src/ReCaptcha.cpp b/src/ReCaptcha.cpp new file mode 100644 index 00000000..ffd202f5 --- /dev/null +++ b/src/ReCaptcha.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ReCaptcha.h" + +#include +#include + +#include "MatrixClient.h" + +ReCaptcha::ReCaptcha(const QString &session, const QString &context, QObject *parent) + : QObject{parent}, + m_session{session}, + m_context{context} +{ +} + +void ReCaptcha::openReCaptcha() +{ + const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/" + "fallback/web?session=%3") + .arg(QString::fromStdString(http::client()->server())) + .arg(http::client()->port()) + .arg(m_session); + + QDesktopServices::openUrl(url); +} diff --git a/src/ReCaptcha.h b/src/ReCaptcha.h new file mode 100644 index 00000000..84d65478 --- /dev/null +++ b/src/ReCaptcha.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +class ReCaptcha : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + + Q_PROPERTY(QString context MEMBER m_context CONSTANT) + Q_PROPERTY(QString session MEMBER m_session CONSTANT) + +public: + ReCaptcha(const QString &session, const QString &context, QObject *parent = nullptr); + + Q_INVOKABLE void openReCaptcha(); + Q_INVOKABLE void confirm() { emit confirmation(); } + Q_INVOKABLE void cancel() { emit cancelled(); } + +signals: + void confirmation(); + void cancelled(); + +private: + QString m_session; + QString m_context; +}; diff --git a/src/dialogs/ReCaptcha.cpp b/src/dialogs/ReCaptcha.cpp deleted file mode 100644 index 2abf7c07..00000000 --- a/src/dialogs/ReCaptcha.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-FileCopyrightText: Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include - -#include "dialogs/ReCaptcha.h" - -#include "Config.h" -#include "MatrixClient.h" - -using namespace dialogs; - -ReCaptcha::ReCaptcha(const QString &session, QWidget *parent) - : QWidget(parent) -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setContentsMargins(conf::modals::WIDGET_MARGIN, - conf::modals::WIDGET_MARGIN, - conf::modals::WIDGET_MARGIN, - conf::modals::WIDGET_MARGIN); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setContentsMargins(0, 0, 0, 0); - buttonLayout->setSpacing(8); - - openCaptchaBtn_ = new QPushButton(tr("Open reCAPTCHA"), this); - cancelBtn_ = new QPushButton(tr("Cancel"), this); - confirmBtn_ = new QPushButton(tr("Confirm"), this); - confirmBtn_->setDefault(true); - - buttonLayout->addStretch(1); - buttonLayout->addWidget(openCaptchaBtn_); - buttonLayout->addWidget(cancelBtn_); - buttonLayout->addWidget(confirmBtn_); - - QFont font; - font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO); - - auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this); - label->setFont(font); - - layout->addWidget(label); - layout->addLayout(buttonLayout); - - connect(openCaptchaBtn_, &QPushButton::clicked, [session]() { - const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/" - "fallback/web?session=%3") - .arg(QString::fromStdString(http::client()->server())) - .arg(http::client()->port()) - .arg(session); - - QDesktopServices::openUrl(url); - }); - - connect(confirmBtn_, &QPushButton::clicked, this, [this]() { - emit confirmation(); - close(); - }); - connect(cancelBtn_, &QPushButton::clicked, this, [this]() { - emit cancel(); - close(); - }); -} diff --git a/src/dialogs/ReCaptcha.h b/src/dialogs/ReCaptcha.h deleted file mode 100644 index 53c7453e..00000000 --- a/src/dialogs/ReCaptcha.h +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -class QPushButton; - -namespace dialogs { - -class ReCaptcha final : public QWidget -{ - Q_OBJECT - -public: - ReCaptcha(const QString &session, QWidget *parent = nullptr); - -signals: - void confirmation(); - void cancel(); - -private: - QPushButton *openCaptchaBtn_; - QPushButton *confirmBtn_; - QPushButton *cancelBtn_; -}; -} // dialogs diff --git a/src/ui/UIA.cpp b/src/ui/UIA.cpp index 2b6b059a..ba35df9b 100644 --- a/src/ui/UIA.cpp +++ b/src/ui/UIA.cpp @@ -14,12 +14,12 @@ #include "Logging.h" #include "MatrixClient.h" #include "dialogs/FallbackAuth.h" -#include "dialogs/ReCaptcha.h" +#include "ReCaptcha.h" UIA * UIA::instance() { - static UIA uia; + static UIA uia{nullptr}; return &uia; } @@ -99,24 +99,16 @@ UIA::genericHandler(QString context) } else if (current_stage == mtx::user_interactive::auth_types::msisdn) { emit phoneNumber(); } else if (current_stage == mtx::user_interactive::auth_types::recaptcha) { - auto captchaDialog = - new dialogs::ReCaptcha(QString::fromStdString(u.session), nullptr); - captchaDialog->setWindowTitle(context); - - connect( - captchaDialog, &dialogs::ReCaptcha::confirmation, this, [captchaDialog, h, u]() { - captchaDialog->close(); - captchaDialog->deleteLater(); - h.next(mtx::user_interactive::Auth{u.session, - mtx::user_interactive::auth::Fallback{}}); - }); - - connect(captchaDialog, &dialogs::ReCaptcha::cancel, this, [this]() { + auto captcha = new ReCaptcha(QString::fromStdString(u.session), context, nullptr); + QQmlEngine::setObjectOwnership(captcha, QQmlEngine::JavaScriptOwnership); + connect(captcha, &ReCaptcha::confirmation, this, [h, u]() { + h.next(mtx::user_interactive::Auth{u.session, + mtx::user_interactive::auth::Fallback{}}); + }); + connect(captcha, &ReCaptcha::cancelled, this, [this]() { emit error(tr("Registration aborted")); }); - - QTimer::singleShot(0, this, [captchaDialog]() { captchaDialog->show(); }); - + emit reCaptcha(captcha); } else if (current_stage == mtx::user_interactive::auth_types::dummy) { h.next( mtx::user_interactive::Auth{u.session, mtx::user_interactive::auth::Dummy{}}); diff --git a/src/ui/UIA.h b/src/ui/UIA.h index 414cb804..5b5eb9f4 100644 --- a/src/ui/UIA.h +++ b/src/ui/UIA.h @@ -9,6 +9,8 @@ #include +#include "ReCaptcha.h" + class UIA final : public QObject { Q_OBJECT @@ -39,7 +41,7 @@ public: return instance(); } - UIA(QObject *parent = nullptr) + UIA(QObject *parent) : QObject(parent) { } @@ -59,6 +61,7 @@ signals: void password(); void email(); void phoneNumber(); + void reCaptcha(ReCaptcha *recaptcha); void confirm3pidToken(); void prompt3pidToken();