async device open and create from device, passphrase

- passphrase entry on host added, requires early listener setting monero pull #5355
- wallet open and create from device shows splash to indicate possible long process
- create from device is async to support passphrase entry
This commit is contained in:
Dusan Klinec 2019-03-27 09:28:42 +01:00
parent 19c2208dc4
commit 1a2675b246
No known key found for this signature in database
GPG key ID: 6337E118CCBCE103
10 changed files with 607 additions and 14 deletions

View file

@ -0,0 +1,327 @@
// Copyright (c) 2014-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.4
import QtQuick.Window 2.0
import "../components" as MoneroComponents
Item {
id: root
visible: false
z: parent.z + 2
property bool isHidden: true
property alias passphrase: passphaseInput1.text
property string walletName
property string errorText
// same signals as Dialog has
signal accepted()
signal rejected()
signal closeCallback()
function open(walletName, errorText) {
inactiveOverlay.visible = true
root.walletName = walletName ? walletName : ""
root.errorText = errorText ? errorText : "";
leftPanel.enabled = false
middlePanel.enabled = false
titleBar.enabled = false
show();
root.visible = true;
passphaseInput1.text = "";
passphaseInput2.text = "";
passphaseInput1.focus = true
}
function close() {
inactiveOverlay.visible = false
leftPanel.enabled = true
middlePanel.enabled = true
titleBar.enabled = true
root.visible = false;
closeCallback();
}
function toggleIsHidden() {
passphaseInput1.echoMode = isHidden ? TextInput.Normal : TextInput.Password;
passphaseInput2.echoMode = isHidden ? TextInput.Normal : TextInput.Password;
isHidden = !isHidden;
}
function showError(errorText) {
open(root.walletName, errorText);
}
// TODO: implement without hardcoding sizes
width: 480
height: 360
// Make window draggable
MouseArea {
anchors.fill: parent
property point lastMousePos: Qt.point(0, 0)
onPressed: { lastMousePos = Qt.point(mouseX, mouseY); }
onMouseXChanged: root.x += (mouseX - lastMousePos.x)
onMouseYChanged: root.y += (mouseY - lastMousePos.y)
}
ColumnLayout {
z: inactiveOverlay.z + 1
id: mainLayout
spacing: 10
anchors { fill: parent; margins: 35 * scaleRatio }
ColumnLayout {
id: column
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
Layout.maximumWidth: 400 * scaleRatio
Label {
text: root.walletName.length > 0 ? qsTr("Please enter wallet device passphrase for: ") + root.walletName : qsTr("Please enter wallet device passphrase")
Layout.fillWidth: true
font.pixelSize: 16 * scaleRatio
font.family: MoneroComponents.Style.fontLight.name
color: MoneroComponents.Style.defaultFontColor
}
Label {
text: qsTr("Warning: passphrase entry on host is a security risk as it can be captured by malware. It is advised to prefer device-based passphrase entry.");
Layout.fillWidth: true
wrapMode: Text.Wrap
font.pixelSize: 14 * scaleRatio
font.family: MoneroComponents.Style.fontLight.name
color: MoneroComponents.Style.warningColor
}
Label {
text: root.errorText
visible: root.errorText
color: MoneroComponents.Style.errorColor
font.pixelSize: 16 * scaleRatio
font.family: MoneroComponents.Style.fontLight.name
Layout.fillWidth: true
wrapMode: Text.Wrap
}
TextField {
id : passphaseInput1
Layout.topMargin: 6
Layout.fillWidth: true
horizontalAlignment: TextInput.AlignLeft
verticalAlignment: TextInput.AlignVCenter
font.family: MoneroComponents.Style.fontLight.name
font.pixelSize: 24 * scaleRatio
echoMode: TextInput.Password
bottomPadding: 10
leftPadding: 10
topPadding: 10
color: MoneroComponents.Style.defaultFontColor
selectionColor: MoneroComponents.Style.dimmedFontColor
selectedTextColor: MoneroComponents.Style.defaultFontColor
KeyNavigation.tab: passphaseInput2
background: Rectangle {
radius: 2
border.color: Qt.rgba(255, 255, 255, 0.35)
border.width: 1
color: "black"
Image {
width: 26 * scaleRatio
height: 26 * scaleRatio
opacity: 0.7
fillMode: Image.PreserveAspectFit
source: isHidden ? "../images/eyeShow.png" : "../images/eyeHide.png"
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 20
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
toggleIsHidden()
}
onEntered: {
parent.opacity = 0.9
parent.width = 28 * scaleRatio
parent.height = 28 * scaleRatio
}
onExited: {
parent.opacity = 0.7
parent.width = 26 * scaleRatio
parent.height = 26 * scaleRatio
}
}
}
}
Keys.onEscapePressed: {
root.close()
root.rejected()
}
}
// padding
Rectangle {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
height: 10
opacity: 0
color: "black"
}
Label {
text: qsTr("Please re-enter")
Layout.fillWidth: true
font.pixelSize: 16 * scaleRatio
font.family: MoneroComponents.Style.fontLight.name
color: MoneroComponents.Style.defaultFontColor
}
TextField {
id : passphaseInput2
Layout.topMargin: 6
Layout.fillWidth: true
horizontalAlignment: TextInput.AlignLeft
verticalAlignment: TextInput.AlignVCenter
font.family: MoneroComponents.Style.fontLight.name
font.pixelSize: 24 * scaleRatio
echoMode: TextInput.Password
KeyNavigation.tab: okButton
bottomPadding: 10
leftPadding: 10
topPadding: 10
color: MoneroComponents.Style.defaultFontColor
selectionColor: MoneroComponents.Style.dimmedFontColor
selectedTextColor: MoneroComponents.Style.defaultFontColor
background: Rectangle {
radius: 2
border.color: Qt.rgba(255, 255, 255, 0.35)
border.width: 1
color: "black"
Image {
width: 26 * scaleRatio
height: 26 * scaleRatio
opacity: 0.7
fillMode: Image.PreserveAspectFit
source: isHidden ? "../images/eyeShow.png" : "../images/eyeHide.png"
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 20
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: {
toggleIsHidden()
}
onEntered: {
parent.opacity = 0.9
parent.width = 28 * scaleRatio
parent.height = 28 * scaleRatio
}
onExited: {
parent.opacity = 0.7
parent.width = 26 * scaleRatio
parent.height = 26 * scaleRatio
}
}
}
}
Keys.onReturnPressed: {
if (passphaseInput1.text === passphaseInput2.text) {
root.close()
root.accepted()
}
}
Keys.onEscapePressed: {
root.close()
root.rejected()
}
}
// padding
Rectangle {
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
height: 10
opacity: 0
color: "black"
}
// Ok/Cancel buttons
RowLayout {
id: buttons
spacing: 16 * scaleRatio
Layout.topMargin: 16
Layout.alignment: Qt.AlignRight
MoneroComponents.StandardButton {
id: cancelButton
text: qsTr("Cancel") + translationManager.emptyString
KeyNavigation.tab: passphaseInput1
onClicked: {
root.close()
root.rejected()
}
}
MoneroComponents.StandardButton {
id: okButton
text: qsTr("Continue")
KeyNavigation.tab: cancelButton
enabled: passphaseInput1.text === passphaseInput2.text
onClicked: {
root.close()
root.accepted()
}
}
}
}
}
}

View file

@ -17,6 +17,7 @@ QtObject {
property string defaultFontColor: "white" property string defaultFontColor: "white"
property string dimmedFontColor: "#BBBBBB" property string dimmedFontColor: "#BBBBBB"
property string lightGreyFontColor: "#DFDFDF" property string lightGreyFontColor: "#DFDFDF"
property string warningColor: "#963E00"
property string errorColor: "#FA6800" property string errorColor: "#FA6800"
property string inputBoxBackground: "black" property string inputBoxBackground: "black"
property string inputBoxBackgroundError: "#FFDDDD" property string inputBoxBackgroundError: "#FFDDDD"

View file

@ -85,6 +85,8 @@ ApplicationWindow {
property int disconnectedEpoch: 0 property int disconnectedEpoch: 0
property int estimatedBlockchainSize: 75 // GB property int estimatedBlockchainSize: 75 // GB
property alias viewState: rootItem.state property alias viewState: rootItem.state
property string prevSplashText;
property bool splashDisplayedBeforeButtonRequest;
property string remoteNodeService: { property string remoteNodeService: {
// support user-defined remote node aggregators // support user-defined remote node aggregators
@ -266,6 +268,8 @@ ApplicationWindow {
wallet_path = moneroAccountsDir + wallet_path; wallet_path = moneroAccountsDir + wallet_path;
// console.log("opening wallet at: ", wallet_path, "with password: ", appWindow.walletPassword); // console.log("opening wallet at: ", wallet_path, "with password: ", appWindow.walletPassword);
console.log("opening wallet at: ", wallet_path, ", network type: ", persistentSettings.nettype == NetworkType.MAINNET ? "mainnet" : persistentSettings.nettype == NetworkType.TESTNET ? "testnet" : "stagenet"); console.log("opening wallet at: ", wallet_path, ", network type: ", persistentSettings.nettype == NetworkType.MAINNET ? "mainnet" : persistentSettings.nettype == NetworkType.TESTNET ? "testnet" : "stagenet");
this.onWalletOpening();
walletManager.openWalletAsync(wallet_path, walletPassword, walletManager.openWalletAsync(wallet_path, walletPassword,
persistentSettings.nettype, persistentSettings.kdfRounds); persistentSettings.nettype, persistentSettings.kdfRounds);
} }
@ -286,6 +290,8 @@ ApplicationWindow {
currentWallet.unconfirmedMoneyReceived.disconnect(onWalletUnconfirmedMoneyReceived) currentWallet.unconfirmedMoneyReceived.disconnect(onWalletUnconfirmedMoneyReceived)
currentWallet.transactionCreated.disconnect(onTransactionCreated) currentWallet.transactionCreated.disconnect(onTransactionCreated)
currentWallet.connectionStatusChanged.disconnect(onWalletConnectionStatusChanged) currentWallet.connectionStatusChanged.disconnect(onWalletConnectionStatusChanged)
currentWallet.deviceButtonRequest.disconnect(onDeviceButtonRequest);
currentWallet.deviceButtonPressed.disconnect(onDeviceButtonPressed);
middlePanel.paymentClicked.disconnect(handlePayment); middlePanel.paymentClicked.disconnect(handlePayment);
middlePanel.sweepUnmixableClicked.disconnect(handleSweepUnmixable); middlePanel.sweepUnmixableClicked.disconnect(handleSweepUnmixable);
middlePanel.getProofClicked.disconnect(handleGetProof); middlePanel.getProofClicked.disconnect(handleGetProof);
@ -341,6 +347,8 @@ ApplicationWindow {
currentWallet.unconfirmedMoneyReceived.connect(onWalletUnconfirmedMoneyReceived) currentWallet.unconfirmedMoneyReceived.connect(onWalletUnconfirmedMoneyReceived)
currentWallet.transactionCreated.connect(onTransactionCreated) currentWallet.transactionCreated.connect(onTransactionCreated)
currentWallet.connectionStatusChanged.connect(onWalletConnectionStatusChanged) currentWallet.connectionStatusChanged.connect(onWalletConnectionStatusChanged)
currentWallet.deviceButtonRequest.connect(onDeviceButtonRequest);
currentWallet.deviceButtonPressed.connect(onDeviceButtonPressed);
middlePanel.paymentClicked.connect(handlePayment); middlePanel.paymentClicked.connect(handlePayment);
middlePanel.sweepUnmixableClicked.connect(handleSweepUnmixable); middlePanel.sweepUnmixableClicked.connect(handleSweepUnmixable);
middlePanel.getProofClicked.connect(handleGetProof); middlePanel.getProofClicked.connect(handleGetProof);
@ -422,7 +430,26 @@ ApplicationWindow {
} }
} }
function onDeviceButtonRequest(code){
prevSplashText = splash.messageText;
splashDisplayedBeforeButtonRequest = splash.visible;
appWindow.showProcessingSplash(qsTr("Please proceed to the device..."));
}
function onDeviceButtonPressed(){
if (splashDisplayedBeforeButtonRequest){
appWindow.showProcessingSplash(prevSplashText);
} else {
hideProcessingSplash();
}
}
function onWalletOpening(){
appWindow.showProcessingSplash(qsTr("Opening wallet ..."));
}
function onWalletOpened(wallet) { function onWalletOpened(wallet) {
hideProcessingSplash();
walletName = usefulName(wallet.path) walletName = usefulName(wallet.path)
console.log(">>> wallet opened: " + wallet) console.log(">>> wallet opened: " + wallet)
if (wallet.status !== Wallet.Status_Ok) { if (wallet.status !== Wallet.Status_Ok) {
@ -470,9 +497,27 @@ ApplicationWindow {
} }
function onWalletClosed(walletAddress) { function onWalletClosed(walletAddress) {
hideProcessingSplash();
console.log(">>> wallet closed: " + walletAddress) console.log(">>> wallet closed: " + walletAddress)
} }
function onWalletPassphraseNeeded(){
if(rootItem.state !== "normal") return;
hideProcessingSplash();
console.log(">>> wallet passphrase needed: ")
passphraseDialog.onAcceptedCallback = function() {
walletManager.onPassphraseEntered(passphraseDialog.passphrase);
this.onWalletOpening();
}
passphraseDialog.onRejectedCallback = function() {
walletManager.onPassphraseEntered("", true);
this.onWalletOpening();
}
passphraseDialog.open()
}
function onWalletUpdate() { function onWalletUpdate() {
console.log(">>> wallet updated") console.log(">>> wallet updated")
updateBalance(); updateBalance();
@ -1017,7 +1062,10 @@ ApplicationWindow {
// //
walletManager.walletOpened.connect(onWalletOpened); walletManager.walletOpened.connect(onWalletOpened);
walletManager.walletClosed.connect(onWalletClosed); walletManager.walletClosed.connect(onWalletClosed);
walletManager.deviceButtonRequest.connect(onDeviceButtonRequest);
walletManager.deviceButtonPressed.connect(onDeviceButtonPressed);
walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete); walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete);
walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeeded);
if(typeof daemonManager != "undefined") { if(typeof daemonManager != "undefined") {
daemonManager.daemonStarted.connect(onDaemonStarted); daemonManager.daemonStarted.connect(onDaemonStarted);
@ -1245,6 +1293,23 @@ ApplicationWindow {
} }
} }
PassphraseDialog {
id: passphraseDialog
visible: false
z: parent.z + 1
anchors.fill: parent
property var onAcceptedCallback
property var onRejectedCallback
onAccepted: {
if (onAcceptedCallback)
onAcceptedCallback();
}
onRejected: {
if (onRejectedCallback)
onRejectedCallback();
}
}
PasswordDialog { PasswordDialog {
id: passwordDialog id: passwordDialog
visible: false visible: false

View file

@ -117,6 +117,7 @@
<file>pages/TxKey.qml</file> <file>pages/TxKey.qml</file>
<file>pages/SharedRingDB.qml</file> <file>pages/SharedRingDB.qml</file>
<file>components/IconButton.qml</file> <file>components/IconButton.qml</file>
<file>components/PassphraseDialog.qml</file>
<file>components/PasswordDialog.qml</file> <file>components/PasswordDialog.qml</file>
<file>components/NewPasswordDialog.qml</file> <file>components/NewPasswordDialog.qml</file>
<file>components/InputDialog.qml</file> <file>components/InputDialog.qml</file>

View file

@ -75,6 +75,16 @@ public:
emit m_wallet->refreshed(); emit m_wallet->refreshed();
} }
virtual void onDeviceButtonRequest(uint64_t code) override
{
emit m_wallet->deviceButtonRequest(code);
}
virtual void onDeviceButtonPressed() override
{
emit m_wallet->deviceButtonPressed();
}
private: private:
Wallet * m_wallet; Wallet * m_wallet;
}; };

View file

@ -322,6 +322,8 @@ signals:
void newBlock(quint64 height, quint64 targetHeight); void newBlock(quint64 height, quint64 targetHeight);
void historyModelChanged() const; void historyModelChanged() const;
void walletCreationHeightChanged(); void walletCreationHeightChanged();
void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed();
// emitted when transaction is created async // emitted when transaction is created async
void transactionCreated(PendingTransaction * transaction, QString address, QString paymentId, quint32 mixinCount); void transactionCreated(PendingTransaction * transaction, QString address, QString paymentId, quint32 mixinCount);

View file

@ -13,6 +13,57 @@
#include <QMutexLocker> #include <QMutexLocker>
#include <QString> #include <QString>
class WalletPassphraseListenerImpl : public Monero::WalletListener
{
public:
WalletPassphraseListenerImpl(WalletManager * mgr): m_mgr(mgr), m_wallet(nullptr) {}
virtual void moneySpent(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; };
virtual void moneyReceived(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; };
virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override { (void)txId; (void)amount; };
virtual void newBlock(uint64_t height) override { (void) height; };
virtual void updated() override {};
virtual void refreshed() override {};
virtual Monero::optional<std::string> onDevicePassphraseRequest(bool on_device) override
{
qDebug() << __FUNCTION__;
if (on_device) return Monero::optional<std::string>();
m_mgr->onWalletPassphraseNeeded(m_wallet);
if (m_mgr->m_passphrase_abort)
{
throw std::runtime_error("Passphrase entry abort");
}
auto tmpPass = m_mgr->m_passphrase.toStdString();
m_mgr->m_passphrase = QString::null;
return Monero::optional<std::string>(tmpPass);
}
virtual void onDeviceButtonRequest(uint64_t code) override
{
emit m_mgr->deviceButtonRequest(code);
}
virtual void onDeviceButtonPressed() override
{
emit m_mgr->deviceButtonPressed();
}
virtual void onSetWallet(Monero::Wallet * wallet) override
{
qDebug() << __FUNCTION__;
m_wallet = wallet;
}
private:
Monero::Wallet * m_wallet;
WalletManager * m_mgr;
};
WalletManager * WalletManager::m_instance = nullptr; WalletManager * WalletManager::m_instance = nullptr;
WalletManager *WalletManager::instance() WalletManager *WalletManager::instance()
@ -41,6 +92,8 @@ Wallet *WalletManager::createWallet(const QString &path, const QString &password
Wallet *WalletManager::openWallet(const QString &path, const QString &password, NetworkType::Type nettype, quint64 kdfRounds) Wallet *WalletManager::openWallet(const QString &path, const QString &password, NetworkType::Type nettype, quint64 kdfRounds)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
WalletPassphraseListenerImpl tmpListener(this);
if (m_currentWallet) { if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet; qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet; delete m_currentWallet;
@ -48,7 +101,9 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password,
qDebug("%s: opening wallet at %s, nettype = %d ", qDebug("%s: opening wallet at %s, nettype = %d ",
__PRETTY_FUNCTION__, qPrintable(path), nettype); __PRETTY_FUNCTION__, qPrintable(path), nettype);
Monero::Wallet * w = m_pimpl->openWallet(path.toStdString(), password.toStdString(), static_cast<Monero::NetworkType>(nettype), kdfRounds); Monero::Wallet * w = m_pimpl->openWallet(path.toStdString(), password.toStdString(), static_cast<Monero::NetworkType>(nettype), kdfRounds, &tmpListener);
w->setListener(nullptr);
qDebug("%s: opened wallet: %s, status: %d", __PRETTY_FUNCTION__, w->address(0, 0).c_str(), w->status()); qDebug("%s: opened wallet: %s, status: %d", __PRETTY_FUNCTION__, w->address(0, 0).c_str(), w->status());
m_currentWallet = new Wallet(w); m_currentWallet = new Wallet(w);
@ -108,17 +163,48 @@ Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString
const QString &deviceName, quint64 restoreHeight, const QString &subaddressLookahead) const QString &deviceName, quint64 restoreHeight, const QString &subaddressLookahead)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
WalletPassphraseListenerImpl tmpListener(this);
if (m_currentWallet) { if (m_currentWallet) {
qDebug() << "Closing open m_currentWallet" << m_currentWallet; qDebug() << "Closing open m_currentWallet" << m_currentWallet;
delete m_currentWallet; delete m_currentWallet;
m_currentWallet = NULL; m_currentWallet = NULL;
} }
Monero::Wallet * w = m_pimpl->createWalletFromDevice(path.toStdString(), password.toStdString(), static_cast<Monero::NetworkType>(nettype), Monero::Wallet * w = m_pimpl->createWalletFromDevice(path.toStdString(), password.toStdString(), static_cast<Monero::NetworkType>(nettype),
deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString()); deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString(), 1, &tmpListener);
w->setListener(nullptr);
m_currentWallet = new Wallet(w); m_currentWallet = new Wallet(w);
// move wallet to the GUI thread. Otherwise it wont be emitting signals
if (m_currentWallet->thread() != qApp->thread()) {
m_currentWallet->moveToThread(qApp->thread());
}
return m_currentWallet; return m_currentWallet;
} }
void WalletManager::createWalletFromDeviceAsync(const QString &path, const QString &password, NetworkType::Type nettype,
const QString &deviceName, quint64 restoreHeight, const QString &subaddressLookahead)
{
auto lmbd = [=](){
return this->createWalletFromDevice(path, password, nettype, deviceName, restoreHeight, subaddressLookahead);
};
QFuture<Wallet *> future = QtConcurrent::run(lmbd);
QFutureWatcher<Wallet *> * watcher = new QFutureWatcher<Wallet *>();
connect(watcher, &QFutureWatcher<Wallet *>::finished,
this, [this, watcher]() {
QFuture<Wallet *> future = watcher->future();
watcher->deleteLater();
emit walletCreated(future.result());
});
watcher->setFuture(future);
}
QString WalletManager::closeWallet() QString WalletManager::closeWallet()
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
@ -419,3 +505,23 @@ WalletManager::WalletManager(QObject *parent) : QObject(parent)
{ {
m_pimpl = Monero::WalletManagerFactory::getWalletManager(); m_pimpl = Monero::WalletManagerFactory::getWalletManager();
} }
void WalletManager::onWalletPassphraseNeeded(Monero::Wallet * wallet)
{
m_mutex_pass.lock();
m_passphrase_abort = false;
emit this->walletPassphraseNeeded();
m_cond_pass.wait(&m_mutex_pass);
m_mutex_pass.unlock();
}
void WalletManager::onPassphraseEntered(const QString &passphrase, bool entry_abort)
{
m_mutex_pass.lock();
m_passphrase = passphrase;
m_passphrase_abort = entry_abort;
m_cond_pass.wakeAll();
m_mutex_pass.unlock();
}

View file

@ -7,6 +7,8 @@
#include <wallet/api/wallet2_api.h> #include <wallet/api/wallet2_api.h>
#include <QMutex> #include <QMutex>
#include <QPointer> #include <QPointer>
#include <QWaitCondition>
#include <QMutex>
#include "NetworkType.h" #include "NetworkType.h"
class Wallet; class Wallet;
@ -70,6 +72,13 @@ public:
const QString &deviceName, const QString &deviceName,
quint64 restoreHeight = 0, quint64 restoreHeight = 0,
const QString &subaddressLookahead = ""); const QString &subaddressLookahead = "");
Q_INVOKABLE void createWalletFromDeviceAsync(const QString &path,
const QString &password,
NetworkType::Type nettype,
const QString &deviceName,
quint64 restoreHeight = 0,
const QString &subaddressLookahead = "");
/*! /*!
* \brief closeWallet - closes current open wallet and frees memory * \brief closeWallet - closes current open wallet and frees memory
* \return wallet address * \return wallet address
@ -152,14 +161,22 @@ public:
// clear/rename wallet cache // clear/rename wallet cache
Q_INVOKABLE bool clearWalletCache(const QString &fileName) const; Q_INVOKABLE bool clearWalletCache(const QString &fileName) const;
Q_INVOKABLE void onWalletPassphraseNeeded(Monero::Wallet * wallet);
Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool entry_abort=false);
signals: signals:
void walletOpened(Wallet * wallet); void walletOpened(Wallet * wallet);
void walletCreated(Wallet * wallet);
void walletPassphraseNeeded();
void deviceButtonRequest(quint64 buttonCode);
void deviceButtonPressed();
void walletClosed(const QString &walletAddress); void walletClosed(const QString &walletAddress);
void checkUpdatesComplete(const QString &result) const; void checkUpdatesComplete(const QString &result) const;
public slots: public slots:
private: private:
friend class WalletPassphraseListenerImpl;
explicit WalletManager(QObject *parent = 0); explicit WalletManager(QObject *parent = 0);
static WalletManager * m_instance; static WalletManager * m_instance;
@ -167,6 +184,10 @@ private:
QMutex m_mutex; QMutex m_mutex;
QPointer<Wallet> m_currentWallet; QPointer<Wallet> m_currentWallet;
QWaitCondition m_cond_pass;
QMutex m_mutex_pass;
QString m_passphrase;
bool m_passphrase_abort;
}; };
#endif // WALLETMANAGER_H #endif // WALLETMANAGER_H

View file

@ -44,6 +44,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
signal useMoneroClicked() signal useMoneroClicked()
signal walletCreatedFromDevice(bool success)
function restart() { function restart() {
wizardStateView.state = "wizardHome" wizardStateView.state = "wizardHome"
@ -65,6 +66,7 @@ Rectangle {
wizardController.walletRestoreMode = 'seed' wizardController.walletRestoreMode = 'seed'
wizardController.walletOptionsSubaddressLookahead = ''; wizardController.walletOptionsSubaddressLookahead = '';
wizardController.remoteNodes = {}; wizardController.remoteNodes = {};
disconnect();
} }
property var m_wallet; property var m_wallet;
@ -366,6 +368,28 @@ Rectangle {
return success; return success;
} }
function disconnect(){
walletManager.walletCreated.disconnect(onWalletCreated);
walletManager.walletPassphraseNeeded.disconnect(onWalletPassphraseNeeded);
walletManager.deviceButtonRequest.disconnect(onDeviceButtonRequest);
walletManager.deviceButtonPressed.disconnect(onDeviceButtonPressed);
}
function connect(){
walletManager.walletCreated.connect(onWalletCreated);
walletManager.walletPassphraseNeeded.connect(onWalletPassphraseNeeded);
walletManager.deviceButtonRequest.connect(onDeviceButtonRequest);
walletManager.deviceButtonPressed.connect(onDeviceButtonPressed);
}
function deviceAttentionSplash(){
appWindow.showProcessingSplash(qsTr("Please proceed to the device..."));
}
function creatingWalletDeviceSplash(){
appWindow.showProcessingSplash(qsTr("Creating wallet from device..."));
}
function createWalletFromDevice() { function createWalletFromDevice() {
// TODO: create wallet in temporary filename and a) move it to the path specified by user after the final // TODO: create wallet in temporary filename and a) move it to the path specified by user after the final
// page submitted or b) delete it when program closed before reaching final page // page submitted or b) delete it when program closed before reaching final page
@ -376,30 +400,61 @@ Rectangle {
console.log("deleting wallet") console.log("deleting wallet")
} }
var tmp_wallet_filename = oshelper.temporaryFilename(); tmpWalletFilename = oshelper.temporaryFilename();
console.log("Creating temporary wallet", tmp_wallet_filename) console.log("Creating temporary wallet", tmpWalletFilename)
var nettype = persistentSettings.nettype; var nettype = persistentSettings.nettype;
var restoreHeight = wizardController.walletOptionsRestoreHeight; var restoreHeight = wizardController.walletOptionsRestoreHeight;
var subaddressLookahead = wizardController.walletOptionsSubaddressLookahead; var subaddressLookahead = wizardController.walletOptionsSubaddressLookahead;
var deviceName = wizardController.walletOptionsDeviceName; var deviceName = wizardController.walletOptionsDeviceName;
var wallet = walletManager.createWalletFromDevice(tmp_wallet_filename, "", nettype, deviceName, restoreHeight, subaddressLookahead); connect();
walletManager.createWalletFromDeviceAsync(tmpWalletFilename, "", nettype, deviceName, restoreHeight, subaddressLookahead);
creatingWalletDeviceSplash();
}
function onWalletCreated(wallet) {
splash.close()
var success = wallet.status === Wallet.Status_Ok; var success = wallet.status === Wallet.Status_Ok;
if (success) { if (success) {
wizardController.m_wallet = wallet; wizardController.m_wallet = wallet;
wizardController.walletOptionsIsRecoveringFromDevice = true; wizardController.walletOptionsIsRecoveringFromDevice = true;
wizardController.tmpWalletFilename = tmp_wallet_filename;
if (!wizardController.walletOptionsDeviceIsRestore) { if (!wizardController.walletOptionsDeviceIsRestore) {
// User creates a hardware wallet for the first time. Use a recent block height from API. // User creates a hardware wallet for the first time. Use a recent block height from API.
wizardController.walletOptionsRestoreHeight = wizardController.m_wallet.walletCreationHeight; wizardController.walletOptionsRestoreHeight = wizardController.m_wallet.walletCreationHeight;
} }
} else { } else {
console.log(wallet.errorString) console.log(wallet.errorString)
wizardController.tmpWalletFilename = '';
appWindow.showStatusMessage(qsTr(wallet.errorString), 5); appWindow.showStatusMessage(qsTr(wallet.errorString), 5);
walletManager.closeWallet(); walletManager.closeWallet();
} }
return success;
disconnect();
walletCreatedFromDevice(success);
}
function onWalletPassphraseNeeded(){
splash.close()
console.log(">>> wallet passphrase needed: ");
passphraseDialog.onAcceptedCallback = function() {
walletManager.onPassphraseEntered(passphraseDialog.passphrase);
creatingWalletDeviceSplash();
}
passphraseDialog.onRejectedCallback = function() {
walletManager.onPassphraseEntered("", true);
creatingWalletDeviceSplash();
}
passphraseDialog.open()
}
function onDeviceButtonRequest(code){
deviceAttentionSplash();
}
function onDeviceButtonPressed(){
creatingWalletDeviceSplash();
} }
function openWallet(){ function openWallet(){

View file

@ -207,13 +207,9 @@ Rectangle {
} }
wizardController.walletOptionsRestoreHeight = _restoreHeight; wizardController.walletOptionsRestoreHeight = _restoreHeight;
} }
var written = wizardController.createWalletFromDevice();
if(written){ wizardController.walletCreatedFromDevice.connect(onCreateWalletFromDeviceCompleted);
wizardController.walletOptionsIsRecoveringFromDevice = true; wizardController.createWalletFromDevice();
wizardStateView.state = "wizardCreateWallet2";
} else {
errorMsg.text = qsTr("Error writing wallet from hardware device. Check application logs.") + translationManager.emptyString;
}
} }
} }
} }
@ -230,4 +226,13 @@ Rectangle {
walletInput.reset(); walletInput.reset();
} }
} }
function onCreateWalletFromDeviceCompleted(written){
if(written){
wizardStateView.state = "wizardCreateWallet2";
} else {
errorMsg.text = qsTr("Error writing wallet from hardware device. Check application logs.") + translationManager.emptyString;
}
wizardController.walletCreatedFromDevice.disconnect(onCreateWalletFromDeviceCompleted);
}
} }