From 35a0f25b5702a13696aec4bc679d0c25b576ca17 Mon Sep 17 00:00:00 2001 From: selsta Date: Mon, 1 Jul 2019 11:44:33 +0200 Subject: [PATCH] PasswordDialog: merge components and bug fixes This commit merges PasswordDialog, PassphraseDialog and NewPasswordDialog. Also the following bugs were fixed: - Wizard pages still being active when opening a wallet from wizard. - Capslock detection was buggy when copy pasting password, I replaced it with native implementations for each platform. - isAlpha could throw errors when using special characters. --- components/NewPasswordDialog.qml | 287 -------------------------- components/PassphraseDialog.qml | 321 ------------------------------ components/PasswordDialog.qml | 260 +++++++++++++++++++----- js/Utils.js | 19 -- main.qml | 49 ++--- monero-wallet-gui.pro | 9 +- oshelper.cpp | 35 ++++ oshelper.h | 1 + pages/settings/SettingsWallet.qml | 2 +- qml.qrc | 2 - src/qt/macoshelper.h | 40 ++++ src/qt/macoshelper.mm | 49 +++++ wizard/WizardController.qml | 8 +- 13 files changed, 369 insertions(+), 713 deletions(-) delete mode 100644 components/NewPasswordDialog.qml delete mode 100644 components/PassphraseDialog.qml create mode 100644 src/qt/macoshelper.h create mode 100644 src/qt/macoshelper.mm diff --git a/components/NewPasswordDialog.qml b/components/NewPasswordDialog.qml deleted file mode 100644 index 3f3c4f38..00000000 --- a/components/NewPasswordDialog.qml +++ /dev/null @@ -1,287 +0,0 @@ -// 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.9 -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 FontAwesome 1.0 - -import "../components" as MoneroComponents - -Item { - id: root - visible: false - z: parent.z + 2 - - property bool isHidden: true - property alias password: passwordInput1.text - - // same signals as Dialog has - signal accepted() - signal rejected() - signal closeCallback() - - function open() { - isHidden = true - passwordInput1.echoMode = TextInput.Password; - passwordInput2.echoMode = TextInput.Password; - inactiveOverlay.visible = true - leftPanel.enabled = false - middlePanel.enabled = false - titleBar.state = "essentials" - root.visible = true; - passwordInput1.text = ""; - passwordInput2.text = ""; - passwordInput1.focus = true - } - - function close() { - inactiveOverlay.visible = false - leftPanel.enabled = true - middlePanel.enabled = true - titleBar.state = "default" - root.visible = false; - closeCallback(); - } - - function toggleIsHidden() { - passwordInput1.echoMode = isHidden ? TextInput.Normal : TextInput.Password; - passwordInput2.echoMode = isHidden ? TextInput.Normal : TextInput.Password; - isHidden = !isHidden; - } - - // TODO: implement without hardcoding sizes - width: 480 - height: 360 - - ColumnLayout { - z: inactiveOverlay.z + 1 - id: mainLayout - spacing: 10 - anchors { fill: parent; margins: 35 } - - ColumnLayout { - id: column - - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: 400 - - Label { - text: qsTr("Please enter new password") + translationManager.emptyString - Layout.fillWidth: true - - font.pixelSize: 16 - font.family: MoneroComponents.Style.fontLight.name - - color: MoneroComponents.Style.defaultFontColor - } - - TextField { - id : passwordInput1 - Layout.topMargin: 6 - Layout.fillWidth: true - horizontalAlignment: TextInput.AlignLeft - verticalAlignment: TextInput.AlignVCenter - font.family: MoneroComponents.Style.fontLight.name - font.pixelSize: 24 - echoMode: TextInput.Password - bottomPadding: 10 - leftPadding: 10 - topPadding: 10 - color: MoneroComponents.Style.defaultFontColor - selectionColor: MoneroComponents.Style.textSelectionColor - selectedTextColor: MoneroComponents.Style.textSelectedColor - KeyNavigation.tab: passwordInput2 - - background: Rectangle { - radius: 2 - border.color: MoneroComponents.Style.inputBorderColorInActive - border.width: 1 - color: MoneroComponents.Style.blackTheme ? "black" : "#A9FFFFFF" - - MoneroComponents.Label { - fontSize: 20 - text: isHidden ? FontAwesome.eye : FontAwesome.eyeSlash - opacity: 0.7 - fontFamily: FontAwesome.fontFamily - anchors.right: parent.right - anchors.rightMargin: 15 - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: 1 - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - onClicked: { - toggleIsHidden() - } - onEntered: { - parent.opacity = 0.9 - parent.fontSize = 24 - } - onExited: { - parent.opacity = 0.7 - parent.fontSize = 20 - } - } - } - } - - 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 confirm new password") + translationManager.emptyString - Layout.fillWidth: true - - font.pixelSize: 16 - font.family: MoneroComponents.Style.fontLight.name - - color: MoneroComponents.Style.defaultFontColor - } - - TextField { - id : passwordInput2 - Layout.topMargin: 6 - Layout.fillWidth: true - horizontalAlignment: TextInput.AlignLeft - verticalAlignment: TextInput.AlignVCenter - font.family: MoneroComponents.Style.fontLight.name - font.pixelSize: 24 - echoMode: TextInput.Password - KeyNavigation.tab: okButton - bottomPadding: 10 - leftPadding: 10 - topPadding: 10 - color: MoneroComponents.Style.defaultFontColor - selectionColor: MoneroComponents.Style.textSelectionColor - selectedTextColor: MoneroComponents.Style.textSelectedColor - - background: Rectangle { - radius: 2 - border.color: MoneroComponents.Style.inputBorderColorInActive - border.width: 1 - color: MoneroComponents.Style.blackTheme ? "black" : "#A9FFFFFF" - - MoneroComponents.Label { - fontSize: 20 - text: isHidden ? FontAwesome.eye : FontAwesome.eyeSlash - opacity: 0.7 - fontFamily: FontAwesome.fontFamily - anchors.right: parent.right - anchors.rightMargin: 15 - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: 1 - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - onClicked: { - toggleIsHidden() - } - onEntered: { - parent.opacity = 0.9 - parent.fontSize = 24 - } - onExited: { - parent.opacity = 0.7 - parent.fontSize = 20 - } - } - } - } - - Keys.onReturnPressed: { - if (passwordInput1.text === passwordInput2.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 - Layout.topMargin: 16 - Layout.alignment: Qt.AlignRight - - MoneroComponents.StandardButton { - id: cancelButton - text: qsTr("Cancel") + translationManager.emptyString - KeyNavigation.tab: passwordInput1 - onClicked: { - root.close() - root.rejected() - } - } - MoneroComponents.StandardButton { - id: okButton - text: qsTr("Continue") + translationManager.emptyString - KeyNavigation.tab: cancelButton - enabled: passwordInput1.text === passwordInput2.text - onClicked: { - root.close() - root.accepted() - } - } - } - } - } -} diff --git a/components/PassphraseDialog.qml b/components/PassphraseDialog.qml deleted file mode 100644 index 79dd2f19..00000000 --- a/components/PassphraseDialog.qml +++ /dev/null @@ -1,321 +0,0 @@ -// 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 FontAwesome 1.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) { - isHidden = true - passphaseInput1.echoMode = TextInput.Password; - passphaseInput2.echoMode = TextInput.Password; - - inactiveOverlay.visible = true - - root.walletName = walletName ? walletName : "" - root.errorText = errorText ? errorText : ""; - - leftPanel.enabled = false - middlePanel.enabled = false - titleBar.state = "essentials" - - root.visible = true; - passphaseInput1.text = ""; - passphaseInput2.text = ""; - passphaseInput1.focus = true - } - - function close() { - inactiveOverlay.visible = false - leftPanel.enabled = true - middlePanel.enabled = true - titleBar.state = "default" - 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 - - ColumnLayout { - z: inactiveOverlay.z + 1 - id: mainLayout - spacing: 10 - anchors { fill: parent; margins: 35 } - - ColumnLayout { - id: column - - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: 400 - - Label { - text: (root.walletName.length > 0 ? qsTr("Please enter wallet device passphrase for: ") + root.walletName : qsTr("Please enter wallet device passphrase")) + translationManager.emptyString - Layout.fillWidth: true - - font.pixelSize: 16 - 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.") + translationManager.emptyString - Layout.fillWidth: true - wrapMode: Text.Wrap - - font.pixelSize: 14 - font.family: MoneroComponents.Style.fontLight.name - - color: MoneroComponents.Style.warningColor - } - - Label { - text: root.errorText - visible: root.errorText - - color: MoneroComponents.Style.errorColor - font.pixelSize: 16 - 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 - echoMode: TextInput.Password - bottomPadding: 10 - leftPadding: 10 - topPadding: 10 - color: MoneroComponents.Style.defaultFontColor - selectionColor: MoneroComponents.Style.textSelectionColor - selectedTextColor: MoneroComponents.Style.textSelectedColor - KeyNavigation.tab: passphaseInput2 - - background: Rectangle { - radius: 2 - border.color: MoneroComponents.Style.inputBorderColorInActive - border.width: 1 - color: MoneroComponents.Style.blackTheme ? "black" : "#A9FFFFFF" - - MoneroComponents.Label { - fontSize: 20 - text: isHidden ? FontAwesome.eye : FontAwesome.eyeSlash - opacity: 0.7 - fontFamily: FontAwesome.fontFamily - anchors.right: parent.right - anchors.rightMargin: 15 - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: 1 - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - onClicked: { - toggleIsHidden() - } - onEntered: { - parent.opacity = 0.9 - parent.fontSize = 24 - } - onExited: { - parent.opacity = 0.7 - parent.fontSize = 20 - } - } - } - } - - Keys.onEscapePressed: { - root.close() - root.rejected() - } - } - - // padding - Rectangle { - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - height: 10 - opacity: 0 - color: "transparent" - } - - Label { - text: qsTr("Please re-enter") + translationManager.emptyString - Layout.fillWidth: true - - font.pixelSize: 16 - 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 - 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: MoneroComponents.Style.inputBorderColorInActive - border.width: 1 - color: MoneroComponents.Style.blackTheme ? "black" : "#A9FFFFFF" - - MoneroComponents.Label { - fontSize: 20 - text: isHidden ? FontAwesome.eye : FontAwesome.eyeSlash - opacity: 0.7 - fontFamily: FontAwesome.fontFamily - anchors.right: parent.right - anchors.rightMargin: 15 - anchors.verticalCenter: parent.verticalCenter - anchors.verticalCenterOffset: 1 - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - onClicked: { - toggleIsHidden() - } - onEntered: { - parent.opacity = 0.9 - parent.fontSize = 24 - } - onExited: { - parent.opacity = 0.7 - parent.fontSize = 20 - } - } - } - } - - 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 - 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") + translationManager.emptyString - KeyNavigation.tab: cancelButton - enabled: passphaseInput1.text === passphaseInput2.text - onClicked: { - root.close() - root.accepted() - } - } - } - } - } -} diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml index 4d1c8467..67ffce96 100644 --- a/components/PasswordDialog.qml +++ b/components/PasswordDialog.qml @@ -44,34 +44,63 @@ Item { z: parent.z + 2 property bool isHidden: true - property alias password: passwordInput.text + property alias password: passwordInput1.text property string walletName property string errorText - property bool shiftIsPressed: false - property bool isCapsLocksActive: false - property bool backspaceIsPressed: false + property bool passwordDialogMode + property bool passphraseDialogMode + property bool newPasswordDialogMode // same signals as Dialog has signal accepted() + signal acceptedNewPassword() + signal acceptedPassphrase() signal rejected() + signal rejectedNewPassword() + signal rejectedPassphrase() signal closeCallback() - function open(walletName, errorText) { + function _openInit(walletName, errorText) { isHidden = true - passwordInput.echoMode = TextInput.Password - passwordInput.text = "" - passwordInput.forceActiveFocus(); + capsLockTextLabel.visible = oshelper.isCapsLock(); + passwordInput1.echoMode = TextInput.Password + passwordInput2.echoMode = TextInput.Password + passwordInput1.text = "" + passwordInput2.text = "" + passwordInput1.forceActiveFocus(); inactiveOverlay.visible = true // draw appwindow inactive root.walletName = walletName ? walletName : "" errorTextLabel.text = errorText ? errorText : ""; leftPanel.enabled = false middlePanel.enabled = false + wizard.enabled = false titleBar.state = "essentials" root.visible = true; appWindow.hideBalanceForced = true; appWindow.updateBalance(); } + function open(walletName, errorText) { + passwordDialogMode = true; + passphraseDialogMode = false; + newPasswordDialogMode = false; + _openInit(walletName, errorText); + } + + function openPassphraseDialog() { + passwordDialogMode = false; + passphraseDialogMode = true; + newPasswordDialogMode = false; + _openInit("", ""); + } + + function openNewPasswordDialog() { + passwordDialogMode = false; + passphraseDialogMode = false; + newPasswordDialogMode = true; + _openInit("", ""); + } + function showError(errorText) { open(root.walletName, errorText); } @@ -80,6 +109,7 @@ Item { inactiveOverlay.visible = false leftPanel.enabled = true middlePanel.enabled = true + wizard.enabled = true titleBar.state = "default" root.visible = false; @@ -88,6 +118,12 @@ Item { closeCallback(); } + function toggleIsHidden() { + passwordInput1.echoMode = isHidden ? TextInput.Normal : TextInput.Password; + passwordInput2.echoMode = isHidden ? TextInput.Normal : TextInput.Password; + isHidden = !isHidden; + } + ColumnLayout { z: inactiveOverlay.z + 1 id: mainLayout @@ -102,7 +138,11 @@ Item { Layout.maximumWidth: 400 Label { - text: (root.walletName.length > 0 ? qsTr("Please enter wallet password for: ") + root.walletName : qsTr("Please enter wallet password")) + translationManager.emptyString + text: { + var device = passwordDialogMode ? qsTr("wallet password") : qsTr("wallet device passphrase"); + (root.walletName.length > 0 ? qsTr("Please enter %1 for: ").arg(device) + root.walletName : qsTr("Please enter %1").arg(device)) + translationManager.emptyString; + } + visible: !newPasswordDialogMode Layout.fillWidth: true font.pixelSize: 16 @@ -111,19 +151,41 @@ Item { 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.") + translationManager.emptyString + visible: passphraseDialogMode + Layout.fillWidth: true + wrapMode: Text.Wrap + + font.pixelSize: 14 + font.family: MoneroComponents.Style.fontLight.name + + color: MoneroComponents.Style.warningColor + } + Label { id: errorTextLabel visible: root.errorText || text !== "" - color: MoneroComponents.Style.errorColor font.pixelSize: 16 - font.family: MoneroComponents.Style.fontLight.name + font.family: MoneroComponents.Style.fontLight.name Layout.fillWidth: true wrapMode: Text.Wrap } + Label { + id: capsLockTextLabel + visible: false + color: MoneroComponents.Style.errorColor + font.pixelSize: 16 + font.family: MoneroComponents.Style.fontLight.name + Layout.fillWidth: true + wrapMode: Text.Wrap + text: qsTr("CAPSLOCKS IS ON.") + translationManager.emptyString; + } + TextField { - id : passwordInput + id: passwordInput1 Layout.topMargin: 6 Layout.fillWidth: true horizontalAlignment: TextInput.AlignLeft @@ -131,24 +193,20 @@ Item { font.family: MoneroComponents.Style.fontLight.name font.pixelSize: 24 echoMode: TextInput.Password - KeyNavigation.tab: okButton + KeyNavigation.tab: { + if (passwordDialogMode) { + return okButton + } else { + return passwordInput2 + } + } bottomPadding: 10 leftPadding: 10 topPadding: 10 color: MoneroComponents.Style.defaultFontColor selectionColor: MoneroComponents.Style.textSelectionColor selectedTextColor: MoneroComponents.Style.textSelectedColor - - onTextChanged: { - var letter = text[passwordInput.text.length - 1]; - isCapsLocksActive = Utils.isUpperLock(shiftIsPressed, letter); - if(isCapsLocksActive && !backspaceIsPressed){ - errorTextLabel.text = qsTr("CAPSLOCKS IS ON.") + translationManager.emptyString; - } - else{ - errorTextLabel.text = ""; - } - } + onTextChanged: capsLockTextLabel.visible = oshelper.isCapsLock(); background: Rectangle { radius: 2 @@ -177,8 +235,7 @@ Item { cursorShape: Qt.PointingHandCursor hoverEnabled: true onClicked: { - passwordInput.echoMode = isHidden ? TextInput.Normal : TextInput.Password; - isHidden = !isHidden; + toggleIsHidden(); } onEntered: { parent.opacity = 0.9 @@ -195,28 +252,129 @@ Item { Keys.enabled: root.visible Keys.onReturnPressed: { root.close() - root.accepted() + if (passwordDialogMode) { + root.accepted() + } else if (newPasswordDialogMode) { + root.acceptedNewPassword() + } else if (passphraseDialogMode) { + root.acceptedPassphrase() + } } Keys.onEscapePressed: { root.close() - root.rejected() - } - Keys.onPressed: { - if(event.key === Qt.Key_Shift){ - shiftIsPressed = true; - } - if(event.key === Qt.Key_Backspace){ - backspaceIsPressed = true; + if (passwordDialogMode) { + root.rejected() + } else if (newPasswordDialogMode) { + root.rejectedNewPassword() + } else if (passphraseDialogMode) { + root.rejectedPassphrase() } } - Keys.onReleased: { - if(event.key === Qt.Key_Shift){ - shiftIsPressed = false; - } - if(event.key === Qt.Key_Backspace){ - backspaceIsPressed =false; + } + + // padding + Rectangle { + visible: !passwordDialogMode + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + height: 10 + opacity: 0 + color: "black" + } + + Label { + visible: !passwordDialogMode + text: qsTr("Please confirm new password") + translationManager.emptyString + Layout.fillWidth: true + + font.pixelSize: 16 + font.family: MoneroComponents.Style.fontLight.name + + color: MoneroComponents.Style.defaultFontColor + } + + TextField { + id: passwordInput2 + visible: !passwordDialogMode + Layout.topMargin: 6 + Layout.fillWidth: true + horizontalAlignment: TextInput.AlignLeft + verticalAlignment: TextInput.AlignVCenter + font.family: MoneroComponents.Style.fontLight.name + font.pixelSize: 24 + echoMode: TextInput.Password + KeyNavigation.tab: okButton + bottomPadding: 10 + leftPadding: 10 + topPadding: 10 + color: MoneroComponents.Style.defaultFontColor + selectionColor: MoneroComponents.Style.textSelectionColor + selectedTextColor: MoneroComponents.Style.textSelectedColor + onTextChanged: capsLockTextLabel.visible = oshelper.isCapsLock(); + + background: Rectangle { + radius: 2 + border.color: MoneroComponents.Style.inputBorderColorInActive + border.width: 1 + color: MoneroComponents.Style.blackTheme ? "black" : "#A9FFFFFF" + + MoneroComponents.Label { + fontSize: 20 + text: isHidden ? FontAwesome.eye : FontAwesome.eyeSlash + opacity: 0.7 + fontFamily: FontAwesome.fontFamily + anchors.right: parent.right + anchors.rightMargin: 15 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: 1 + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onClicked: { + toggleIsHidden() + } + onEntered: { + parent.opacity = 0.9 + parent.fontSize = 24 + } + onExited: { + parent.opacity = 0.7 + parent.fontSize = 20 + } + } } } + + Keys.onReturnPressed: { + if (passwordInput1.text === passwordInput2.text) { + root.close() + if (newPasswordDialogMode) { + root.acceptedNewPassword() + } else if (passphraseDialogMode) { + root.acceptedPassphrase() + } + } + } + Keys.onEscapePressed: { + root.close() + if (newPasswordDialogMode) { + root.rejectedNewPassword() + } else if (passphraseDialogMode) { + root.rejectedPassphrase() + } + } + } + + // padding + Rectangle { + visible: !passwordDialogMode + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + height: 10 + opacity: 0 + color: "black" } // Ok/Cancel buttons @@ -230,10 +388,16 @@ Item { id: cancelButton small: true text: root.walletName.length > 0 ? qsTr("Change wallet") + translationManager.emptyString : qsTr("Cancel") + translationManager.emptyString - KeyNavigation.tab: passwordInput + KeyNavigation.tab: passwordInput1 onClicked: { root.close() - root.rejected() + if (passwordDialogMode) { + root.rejected() + } else if (newPasswordDialogMode) { + root.rejectedNewPassword() + } else if (passphraseDialogMode) { + root.rejectedPassphrase() + } } } @@ -242,13 +406,19 @@ Item { small: true text: qsTr("Continue") + translationManager.emptyString KeyNavigation.tab: cancelButton + enabled: (passwordDialogMode == true) ? true : passwordInput1.text === passwordInput2.text onClicked: { root.close() - root.accepted() + if (passwordDialogMode) { + root.accepted() + } else if (newPasswordDialogMode) { + root.acceptedNewPassword() + } else if (passphraseDialogMode) { + root.acceptedPassphrase() + } } } } - } } } diff --git a/js/Utils.js b/js/Utils.js index b709b2ce..4a9a5fed 100644 --- a/js/Utils.js +++ b/js/Utils.js @@ -112,25 +112,6 @@ function roundDownToNearestThousand(_num){ return Math.floor(_num/1000.0)*1000 } -function isAlpha(letter){ return letter.match(/^[A-Za-z0-9]+$/) !== null; } - -function isLowerCaseChar(letter){ return letter === letter.toLowerCase(); } - -function isUpperLock(shift, letter){ - if(!isAlpha((letter))) return false; - if(shift) { - if(isLowerCaseChar(letter)) - return true; - else - return false; - } else { - if(isLowerCaseChar(letter)) - return false; - else - return true; - } -} - function qmlEach(item, properties, ignoredObjectNames, arr){ // Traverse QML object tree and return components that match // via property names. Similar to jQuery("myclass").each(... diff --git a/main.qml b/main.qml index 743af497..af80ad4e 100644 --- a/main.qml +++ b/main.qml @@ -569,15 +569,15 @@ ApplicationWindow { hideProcessingSplash(); console.log(">>> wallet passphrase needed: ") - passphraseDialog.onAcceptedCallback = function() { - walletManager.onPassphraseEntered(passphraseDialog.passphrase); + passwordDialog.onAcceptedPassphraseCallback = function() { + walletManager.onPassphraseEntered(passwordDialog.password); this.onWalletOpening(); } - passphraseDialog.onRejectedCallback = function() { + passwordDialog.onRejectedPassphraseCallback = function() { walletManager.onPassphraseEntered("", true); this.onWalletOpening(); } - passphraseDialog.open() + passwordDialog.openPassphraseDialog() } function onWalletUpdate() { @@ -1494,23 +1494,6 @@ 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 { id: passwordDialog visible: false @@ -1518,6 +1501,8 @@ ApplicationWindow { anchors.fill: parent property var onAcceptedCallback property var onRejectedCallback + property var onAcceptedPassphraseCallback + property var onRejectedPassphraseCallback onAccepted: { if (onAcceptedCallback) onAcceptedCallback(); @@ -1526,16 +1511,9 @@ ApplicationWindow { if (onRejectedCallback) onRejectedCallback(); } - } - - NewPasswordDialog { - id: newPasswordDialog - z: parent.z + 1 - visible:false - anchors.fill: parent - onAccepted: { - if (currentWallet.setPassword(newPasswordDialog.password)) { - appWindow.walletPassword = newPasswordDialog.password; + onAcceptedNewPassword: { + if (currentWallet.setPassword(passwordDialog.password)) { + appWindow.walletPassword = passwordDialog.password; informationPopup.title = qsTr("Information") + translationManager.emptyString; informationPopup.text = qsTr("Password changed successfully") + translationManager.emptyString; informationPopup.icon = StandardIcon.Information; @@ -1547,7 +1525,14 @@ ApplicationWindow { informationPopup.onCloseCallback = null; informationPopup.open(); } - onRejected: { + onRejectedNewPassword: {} + onAcceptedPassphrase: { + if (onAcceptedPassphraseCallback) + onAcceptedPassphraseCallback(); + } + onRejectedPassphrase: { + if (onRejectedPassphraseCallback) + onRejectedPassphraseCallback(); } } diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro index 8c4707c1..51c81a7d 100644 --- a/monero-wallet-gui.pro +++ b/monero-wallet-gui.pro @@ -67,7 +67,8 @@ HEADERS += \ src/qt/mime.h \ src/qt/KeysFiles.h \ src/qt/utils.h \ - src/qt/prices.h + src/qt/prices.h \ + src/qt/macoshelper.h SOURCES += main.cpp \ filter.cpp \ @@ -332,7 +333,8 @@ linux { -llmdb \ -lsodium \ -lhidapi-libusb \ - -lcrypto $$TREZOR_LINKER + -lcrypto $$TREZOR_LINKER \ + -lX11 if(!android) { LIBS+= \ @@ -357,6 +359,8 @@ macx { # message("using static libraries") # LIBS+= -Wl,-Bstatic # } + QT += macextras + OBJECTIVE_SOURCES += src/qt/macoshelper.mm LIBS+= \ -L/usr/local/lib \ -L/usr/local/opt/openssl/lib \ @@ -370,6 +374,7 @@ macx { -lboost_chrono \ -lboost_program_options \ -framework CoreFoundation \ + -framework AppKit \ -lhidapi \ -lssl \ -lsodium \ diff --git a/oshelper.cpp b/oshelper.cpp index 36a3c866..d3e4d063 100644 --- a/oshelper.cpp +++ b/oshelper.cpp @@ -31,6 +31,20 @@ #include #include #include +#ifdef Q_OS_MAC +#include "qt/macoshelper.h" +#endif +#ifdef Q_OS_WIN32 +#include +#endif +#ifdef Q_OS_LINUX +#include +#undef KeyPress +#undef KeyRelease +#undef FocusIn +#undef FocusOut +// #undef those Xlib #defines that conflict with QEvent::Type enum +#endif OSHelper::OSHelper(QObject *parent) : QObject(parent) { @@ -58,6 +72,27 @@ bool OSHelper::removeTemporaryWallet(const QString &fileName) const return cache_deleted && address_deleted && keys_deleted; } +// https://stackoverflow.com/a/3006934 +bool OSHelper::isCapsLock() const +{ + // platform dependent method of determining if CAPS LOCK is on +#if defined(Q_OS_WIN32) // MS Windows version + return GetKeyState(VK_CAPITAL) == 1; +#elif defined(Q_OS_LINUX) // X11 version + Display * d = XOpenDisplay((char*)0); + bool caps_state = false; + if (d) { + unsigned n; + XkbGetIndicatorState(d, XkbUseCoreKbd, &n); + caps_state = (n & 0x01) == 1; + } + return caps_state; +#elif defined(Q_OS_MAC) + return MacOSHelper::isCapsLock(); +#endif + return false; +} + QString OSHelper::temporaryPath() const { return QDir::tempPath(); diff --git a/oshelper.h b/oshelper.h index 1c11bdde..4d378a9d 100644 --- a/oshelper.h +++ b/oshelper.h @@ -42,6 +42,7 @@ public: Q_INVOKABLE QString temporaryFilename() const; Q_INVOKABLE QString temporaryPath() const; Q_INVOKABLE bool removeTemporaryWallet(const QString &walletName) const; + Q_INVOKABLE bool isCapsLock() const; signals: diff --git a/pages/settings/SettingsWallet.qml b/pages/settings/SettingsWallet.qml index 1b2c8463..ab80fec2 100644 --- a/pages/settings/SettingsWallet.qml +++ b/pages/settings/SettingsWallet.qml @@ -339,7 +339,7 @@ Rectangle { onClicked: { passwordDialog.onAcceptedCallback = function() { if(appWindow.walletPassword === passwordDialog.password){ - newPasswordDialog.open() + passwordDialog.openNewPasswordDialog() } else { informationPopup.title = qsTr("Error") + translationManager.emptyString; informationPopup.text = qsTr("Wrong password") + translationManager.emptyString; diff --git a/qml.qrc b/qml.qrc index 1200b285..3bced000 100644 --- a/qml.qrc +++ b/qml.qrc @@ -96,9 +96,7 @@ pages/SharedRingDB.qml components/effects/ImageMask.qml components/IconButton.qml - components/PassphraseDialog.qml components/PasswordDialog.qml - components/NewPasswordDialog.qml components/InputDialog.qml components/ProcessingSplash.qml components/ProgressBar.qml diff --git a/src/qt/macoshelper.h b/src/qt/macoshelper.h new file mode 100644 index 00000000..bddf70c6 --- /dev/null +++ b/src/qt/macoshelper.h @@ -0,0 +1,40 @@ +// 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. + +#ifndef MACOSHELPER_H +#define MACOSHELPER_H + +class MacOSHelper +{ + MacOSHelper() {} + +public: + static bool isCapsLock(); +}; + +#endif //MACOSHELPER_H diff --git a/src/qt/macoshelper.mm b/src/qt/macoshelper.mm new file mode 100644 index 00000000..0b29f25f --- /dev/null +++ b/src/qt/macoshelper.mm @@ -0,0 +1,49 @@ +// 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. + +#include +#include +#include +#include "macoshelper.h" + +#import +#import +#include +#include +#include + +bool MacOSHelper::isCapsLock() +{ +#ifdef __MAC_10_12 + NSUInteger flags = [NSEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask; + return (flags == NSEventModifierFlagCapsLock); +#else + NSUInteger flags = [NSEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask; + return (flags & NSAlphaShiftKeyMask); +#endif +} diff --git a/wizard/WizardController.qml b/wizard/WizardController.qml index f4728ac2..c209faf0 100644 --- a/wizard/WizardController.qml +++ b/wizard/WizardController.qml @@ -498,15 +498,15 @@ Rectangle { splash.close() console.log(">>> wallet passphrase needed: "); - passphraseDialog.onAcceptedCallback = function() { - walletManager.onPassphraseEntered(passphraseDialog.passphrase); + passwordDialog.onAcceptedPassphraseCallback = function() { + walletManager.onPassphraseEntered(passwordDialog.password); creatingWalletDeviceSplash(); } - passphraseDialog.onRejectedCallback = function() { + passwordDialog.onRejectedPassphraseCallback = function() { walletManager.onPassphraseEntered("", true); creatingWalletDeviceSplash(); } - passphraseDialog.open() + passwordDialog.openPassphraseDialog() } function onDeviceButtonRequest(code){