diff --git a/.gitignore b/.gitignore index 958bc75a..ecb0fb8c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,22 @@ monero-wallet-gui_plugin_import.cpp monero-wallet-gui_qml_plugin_import.cpp *.qmlc *.jsc + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ diff --git a/Logger.cpp b/Logger.cpp index f2e34552..ad356bcb 100644 --- a/Logger.cpp +++ b/Logger.cpp @@ -34,6 +34,7 @@ #include #include "Logger.h" +#include "src/qt/TailsOS.h" #include "wallet/api/wallet2_api.h" // default log path by OS (should be writable) @@ -66,6 +67,9 @@ const QString getLogPath(const QString logPath) { const QFileInfo fi(logPath); + if(TailsOS::detect() && TailsOS::usePersistence) + return QDir::homePath() + "/Persistent/Monero/logs/" + defaultLogName; + if(!logPath.isEmpty() && !fi.isDir()) return fi.absoluteFilePath(); else { diff --git a/components/NetworkStatusItem.qml b/components/NetworkStatusItem.qml index 77ef4afa..30a7a867 100644 --- a/components/NetworkStatusItem.qml +++ b/components/NetworkStatusItem.qml @@ -88,6 +88,7 @@ Rectangle { } MouseArea { anchors.fill: parent + visible: appWindow.walletMode >= 2 cursorShape: Qt.PointingHandCursor onClicked: { if(!appWindow.isMining) { @@ -133,6 +134,7 @@ Rectangle { MouseArea { anchors.fill: parent + visible: appWindow.walletMode >= 2 cursorShape: Qt.PointingHandCursor onClicked: { if(!appWindow.isMining) { 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..33942df9 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,14 @@ 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: { + if (newPasswordDialogMode) { + return qsTr("Please enter new wallet password") + translationManager.emptyString; + } else { + var device = passwordDialogMode ? qsTr("wallet password") : qsTr("wallet device passphrase"); + return (root.walletName.length > 0 ? qsTr("Please enter %1 for: ").arg(device) + root.walletName : qsTr("Please enter %1").arg(device)) + translationManager.emptyString; + } + } Layout.fillWidth: true font.pixelSize: 16 @@ -111,19 +154,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 +196,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 +238,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 +255,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: newPasswordDialogMode ? qsTr("Please confirm new password") : qsTr("Please confirm wallet device passphrase") + 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 +391,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 +409,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/images/tails-grey.png b/images/tails-grey.png new file mode 100644 index 00000000..b2e18e42 Binary files /dev/null and b/images/tails-grey.png differ diff --git a/installers/windows/Deterministic.md b/installers/windows/Deterministic.md new file mode 100644 index 00000000..bef6fc34 --- /dev/null +++ b/installers/windows/Deterministic.md @@ -0,0 +1,30 @@ +# Building the Installer Deterministically + +This file contains info about building the Windows installer deterministically, i.e. how different people on different Windows machines or VMs can build it and arrive at a result that is bit-for-bit identical. This approach is also known as *reproducible builds*, see e.g. [this Wikipedia article](https://en.wikipedia.org/wiki/Reproducible_builds). + +The steps to build the Windows installer deterministically by a group of people are the following (for some details about the build process in general see `README.md`): + +* Agree on a particular version of Inno Setup, and everybody install that +* Get the zip file for the Windows GUI wallet and unpack it, plus make sure / check that the file timestamps are preserved, i.e. upacked timestamp = timestamp in zip file +* Build using Inno Setup and the `Monero.iss` script file +* Success: All people arrive at a bit-for-bit identical installer .exe file, which they can verify by calculating and exchanging SHA256 hashes + +Some background info why this process is relatively simple: + +The tool used to build the Windows installer, Inno Setup, avoids many issues that make reproducible builds very challenging with many other compilers and similar tools: It does not store current date and time in the installer .exe file, and it does not seem to depend on the Windows version it runs on (tried with Windows 7 and two different editions of Windows 10), nor on the locale and display language. + +So fortunately no complicated things as faked current system time or use of VMs with exactly prescribed versions of Windows are necessary. + +The version of Inno Setup **is** important however: People wanting to reproducibly build the installer must agree on a particular version to use. This should not be hard to do however. + +Also important are the **timestamps** of the source files because they go into the installer file, to be restored at install time. + +You would think timestamp preservation is no problem when unpacking the zip archive with the files for the Windows GUI wallet from getmonero.org, but if you use the zip folder unpack functionality of the Windows 7 GUI, the files get the current date, **not** the file recorded in the zip file. (The Windows 10 GUI seems better here, and also the 7zip app.) + +In any case, after unpacking, check the file dates in the `bin` directory where the installer script looks for them with the dates of the files in the zip file: They must be identical. + +Note that the the following line in `Monero.iss` is also important regarding file timestamps: + + TimeStampsInUTC=yes + +Without this line the **timezone** of the machine used to build the installer would matter, with different timezones leading to different installer files. diff --git a/installers/windows/Monero.iss b/installers/windows/Monero.iss index 033c0440..bad675c2 100644 --- a/installers/windows/Monero.iss +++ b/installers/windows/Monero.iss @@ -21,6 +21,8 @@ DisableWelcomePage=no LicenseFile=LICENSE AppPublisher=The Monero Developer Community AppPublisherURL=https://getmonero.org +TimeStampsInUTC=yes +CompressionThreads=1 UsedUserAreasWarning=no ; The above directive silences the following compiler warning: @@ -68,7 +70,7 @@ Source: "FinishImage.bmp"; Flags: dontcopy ; Monero GUI wallet exe and guide Source: "bin\monero-wallet-gui.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "bin\monero-GUI-guide.pdf"; DestDir: "{app}"; Flags: ignoreversion +Source: "bin\monero-gui-wallet-guide.pdf"; DestDir: "{app}"; Flags: ignoreversion ; Monero CLI wallet Source: "bin\monero-wallet-cli.exe"; DestDir: "{app}"; Flags: ignoreversion diff --git a/js/TxUtils.js b/js/TxUtils.js index de4197d9..31f8553c 100644 --- a/js/TxUtils.js +++ b/js/TxUtils.js @@ -12,13 +12,13 @@ function destinationsToAddress(destinations){ } function addressTruncate(address, range){ - if(typeof(address) === "undefined") return; + if(typeof(address) === "undefined") return ""; if(typeof(range) === "undefined") range = 8; return address.substring(0, range) + "..." + address.substring(address.length-range); } function addressTruncatePretty(address, blocks){ - if(typeof(address) === "undefined") return; + if(typeof(address) === "undefined") return ""; if(typeof(blocks) === "undefined") blocks = 2; blocks = blocks <= 1 ? 1 : blocks >= 23 ? 23 : blocks; var ret = ""; 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/linuxdeploy_helper.sh b/linuxdeploy_helper.sh index db6ae8d1..59c1873c 100755 --- a/linuxdeploy_helper.sh +++ b/linuxdeploy_helper.sh @@ -51,4 +51,12 @@ SCRIPT_DIR="\$(dirname "\$(test -L "\${BASH_SOURCE[0]}" && readlink "\${BASH_SOU "\$SCRIPT_DIR"/$GUI_EXEC "\$@" EOL +# Create start script +cat > $TARGET/start-tails.AppImage <("moneroComponents.Clipboard", 1, 0, "Clipboard"); + // Temporary Qt.labs.settings replacement + qmlRegisterType("moneroComponents.Settings", 1, 0, "MoneroSettings"); + qmlRegisterUncreatableType("moneroComponents.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); @@ -313,6 +333,8 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("walletLogPath", logPath); + engine.rootContext()->setContextProperty("tailsUsePersistence", TailsOS::usePersistence); + // Exclude daemon manager from IOS #ifndef Q_OS_IOS const QStringList arguments = (QStringList) QCoreApplication::arguments().at(0); @@ -326,7 +348,7 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("isIOS", isIOS); engine.rootContext()->setContextProperty("isAndroid", isAndroid); engine.rootContext()->setContextProperty("isOpenGL", isOpenGL); - engine.rootContext()->setContextProperty("isLinux", isLinux); + engine.rootContext()->setContextProperty("isTails", isTails); engine.rootContext()->setContextProperty("screenWidth", geo.width()); engine.rootContext()->setContextProperty("screenHeight", geo.height()); @@ -350,6 +372,7 @@ int main(int argc, char *argv[]) accountName = "My monero Account"; engine.rootContext()->setContextProperty("defaultAccountName", accountName); + engine.rootContext()->setContextProperty("homePath", QDir::homePath()); engine.rootContext()->setContextProperty("applicationDirectory", QApplication::applicationDirPath()); engine.rootContext()->setContextProperty("idealThreadCount", QThread::idealThreadCount()); diff --git a/main.qml b/main.qml index 743af497..48fde1a9 100644 --- a/main.qml +++ b/main.qml @@ -31,11 +31,11 @@ import QtQuick.Window 2.0 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 import QtQuick.Dialogs 1.2 -import Qt.labs.settings 1.0 import moneroComponents.Wallet 1.0 import moneroComponents.PendingTransaction 1.0 import moneroComponents.NetworkType 1.0 +import moneroComponents.Settings 1.0 import "components" import "components" as MoneroComponents @@ -283,7 +283,7 @@ ApplicationWindow { titleBar.visible = persistentSettings.customDecorations; } - function closeWallet() { + function closeWallet(callback) { // Disconnect all listeners if (typeof currentWallet !== "undefined" && currentWallet !== null) { @@ -306,8 +306,17 @@ ApplicationWindow { } currentWallet = undefined; - walletManager.closeWallet(); + appWindow.showProcessingSplash(qsTr("Closing wallet...")); + if (callback) { + walletManager.closeWalletAsync(function() { + hideProcessingSplash(); + callback(); + }); + } else { + walletManager.closeWallet(); + hideProcessingSplash(); + } } function connectWallet(wallet) { @@ -558,26 +567,21 @@ ApplicationWindow { } } - function onWalletClosed(walletAddress) { - hideProcessingSplash(); - 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); + 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() { @@ -1105,27 +1109,30 @@ ApplicationWindow { console.log("Hiding processing splash") splash.close(); - leftPanel.enabled = true - middlePanel.enabled = true - titleBar.enabled = true - inactiveOverlay.visible = false; + if (!passwordDialog.visible) { + leftPanel.enabled = true + middlePanel.enabled = true + titleBar.enabled = true + inactiveOverlay.visible = false; + } } // close wallet and show wizard function showWizard(){ clearMoneroCardLabelText(); walletInitialized = false; - closeWallet(); - currentWallet = undefined; - wizard.restart(); - wizard.wizardState = "wizardHome"; - rootItem.state = "wizard" - // reset balance - leftPanel.balanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(0); - fiatApiUpdateBalance(0, 0); - // disable timers - userInActivityTimer.running = false; - simpleModeConnectionTimer.running = false; + closeWallet(function() { + currentWallet = undefined; + wizard.restart(); + wizard.wizardState = "wizardHome"; + rootItem.state = "wizard" + // reset balance + leftPanel.balanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(0); + fiatApiUpdateBalance(0, 0); + // disable timers + userInActivityTimer.running = false; + simpleModeConnectionTimer.running = false; + }); } function hideMenu() { @@ -1289,7 +1296,6 @@ ApplicationWindow { y = (Screen.height - maxWindowHeight) / 2 // walletManager.walletOpened.connect(onWalletOpened); - walletManager.walletClosed.connect(onWalletClosed); walletManager.deviceButtonRequest.connect(onDeviceButtonRequest); walletManager.deviceButtonPressed.connect(onDeviceButtonPressed); walletManager.checkUpdatesComplete.connect(onWalletCheckUpdatesComplete); @@ -1341,8 +1347,14 @@ ApplicationWindow { } } - Settings { + MoneroSettings { id: persistentSettings + fileName: { + if(isTails && tailsUsePersistence) + return homePath + "/Persistent/Monero/monero-core.conf"; + return ""; + } + property string language property string locale property string account_name @@ -1494,23 +1506,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 +1513,8 @@ ApplicationWindow { anchors.fill: parent property var onAcceptedCallback property var onRejectedCallback + property var onAcceptedPassphraseCallback + property var onRejectedPassphraseCallback onAccepted: { if (onAcceptedCallback) onAcceptedCallback(); @@ -1526,16 +1523,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 +1537,14 @@ ApplicationWindow { informationPopup.onCloseCallback = null; informationPopup.open(); } - onRejected: { + onRejectedNewPassword: {} + onAcceptedPassphrase: { + if (onAcceptedPassphraseCallback) + onAcceptedPassphraseCallback(); + } + onRejectedPassphrase: { + if (onRejectedPassphraseCallback) + onRejectedPassphraseCallback(); } } @@ -2131,8 +2128,8 @@ ApplicationWindow { console.log("close accepted"); // Close wallet non async on exit daemonManager.exit(); - walletManager.closeWallet(); - Qt.quit(); + + closeWallet(Qt.quit); } function onWalletCheckUpdatesComplete(update) { @@ -2201,6 +2198,7 @@ ApplicationWindow { function checkInUserActivity() { if(rootItem.state !== "normal") return; if(!persistentSettings.lockOnUserInActivity) return; + if(passwordDialog.visible) return; // prompt password after X seconds of inactivity var epoch = Math.floor((new Date).getTime() / 1000); diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro index 19eb5cde..737a6875 100644 --- a/monero-wallet-gui.pro +++ b/monero-wallet-gui.pro @@ -1,6 +1,6 @@ -# qml components require at least QT 5.7.0 -lessThan (QT_MAJOR_VERSION, 5) | lessThan (QT_MINOR_VERSION, 7) { - error("Can't build with Qt $${QT_VERSION}. Use at least Qt 5.7.0") +# qml components require at least QT 5.9.0 +lessThan (QT_MAJOR_VERSION, 5) | lessThan (QT_MINOR_VERSION, 9) { + error("Can't build with Qt $${QT_VERSION}. Use at least Qt 5.9.0") } TEMPLATE = app @@ -65,10 +65,12 @@ HEADERS += \ MainApp.h \ src/qt/FutureScheduler.h \ src/qt/ipc.h \ - src/qt/mime.h \ src/qt/KeysFiles.h \ src/qt/utils.h \ - src/qt/prices.h + src/qt/prices.h \ + src/qt/macoshelper.h \ + src/qt/MoneroSettings.h \ + src/qt/TailsOS.h SOURCES += main.cpp \ filter.cpp \ @@ -99,10 +101,11 @@ SOURCES += main.cpp \ MainApp.cpp \ src/qt/FutureScheduler.cpp \ src/qt/ipc.cpp \ - src/qt/mime.cpp \ src/qt/KeysFiles.cpp \ src/qt/utils.cpp \ - src/qt/prices.cpp + src/qt/prices.cpp \ + src/qt/MoneroSettings.cpp \ + src/qt/TailsOS.cpp CONFIG(DISABLE_PASS_STRENGTH_METER) { HEADERS -= src/zxcvbn-c/zxcvbn.h @@ -334,7 +337,8 @@ linux { -llmdb \ -lsodium \ -lhidapi-libusb \ - -lcrypto $$TREZOR_LINKER + -lcrypto $$TREZOR_LINKER \ + -lX11 if(!android) { LIBS+= \ @@ -359,6 +363,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 \ @@ -372,6 +378,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/History.qml b/pages/History.qml index 4ce053a7..d35657a3 100644 --- a/pages/History.qml +++ b/pages/History.qml @@ -49,9 +49,9 @@ Rectangle { property var model property int sideMargin: 50 property var initialized: false - property int txMax: 5 + property int txMax: Math.max(5, ((appWindow.height - 250) / 60)) property int txOffset: 0 - property int txPage: (txOffset / 5) + 1 + property int txPage: (txOffset / txMax) + 1 property int txCount: 0 property var sortSearchString: null property bool sortDirection: true // true = desc, false = asc @@ -67,6 +67,8 @@ Rectangle { color: "transparent" + onTxMaxChanged: root.updateDisplay(root.txOffset, root.txMax); + ColumnLayout { id: pageRoot anchors.topMargin: 40 @@ -1343,13 +1345,14 @@ Rectangle { root.updateDisplay(root.txOffset, root.txMax); } - function reset() { + function reset(keepDate) { root.txOffset = 0; - root.txMax = 5; if (typeof root.model !== 'undefined' && root.model != null) { - root.model.dateFromFilter = "2014-04-18" // genesis block - root.model.dateToFilter = "9999-09-09" // fix before september 9999 + if (!keepDate) { + root.model.dateFromFilter = "2014-04-18" // genesis block + root.model.dateToFilter = "9999-09-09" // fix before september 9999 + } // negative values disable filters here; root.model.amountFromFilter = -1; root.model.amountToFilter = -1; @@ -1387,6 +1390,8 @@ Rectangle { txs.push(item); } else if(item.blockheight.toString().startsWith(root.sortSearchString)) { txs.push(item); + } else if(item.tx_note.toLowerCase().indexOf(root.sortSearchString.toLowerCase()) !== -1) { + txs.push(item); } else if (item.hash.startsWith(root.sortSearchString)){ txs.push(item); } @@ -1704,6 +1709,6 @@ Rectangle { function onPageClosed(){ root.initialized = false; - root.reset(); + root.reset(true); } } diff --git a/pages/Keys.qml b/pages/Keys.qml index b913314b..a8f62a19 100644 --- a/pages/Keys.qml +++ b/pages/Keys.qml @@ -116,11 +116,11 @@ Rectangle { MoneroComponents.LineEdit { Layout.fillWidth: true + id: walletCreationHeight readOnly: true copyButton: true labelText: qsTr("Block #") + translationManager.emptyString fontSize: 16 - text: currentWallet.walletCreationHeight } } @@ -261,6 +261,7 @@ Rectangle { function onPageCompleted() { console.log("keys page loaded"); + walletCreationHeight.text = currentWallet.walletCreationHeight secretViewKey.text = currentWallet.secretViewKey publicViewKey.text = currentWallet.publicViewKey secretSpendKey.text = (!currentWallet.viewOnly) ? currentWallet.secretSpendKey : "" diff --git a/pages/settings/SettingsInfo.qml b/pages/settings/SettingsInfo.qml index b329c068..80e6a129 100644 --- a/pages/settings/SettingsInfo.qml +++ b/pages/settings/SettingsInfo.qml @@ -208,10 +208,11 @@ Rectangle { confirmationDialog.icon = StandardIcon.Question confirmationDialog.cancelText = qsTr("Cancel") confirmationDialog.onAcceptedCallback = function() { - walletManager.closeWallet(); - walletManager.clearWalletCache(persistentSettings.wallet_path); - walletManager.openWalletAsync(persistentSettings.wallet_path, appWindow.walletPassword, - persistentSettings.nettype, persistentSettings.kdfRounds); + appWindow.closeWallet(function() { + walletManager.clearWalletCache(persistentSettings.wallet_path); + walletManager.openWalletAsync(persistentSettings.wallet_path, appWindow.walletPassword, + persistentSettings.nettype, persistentSettings.kdfRounds); + }); } confirmationDialog.onRejectedCallback = null; @@ -325,6 +326,41 @@ Rectangle { font.pixelSize: 14 text: isOpenGL ? "OpenGL" : "Low graphics mode" } + + Rectangle { + visible: isTails + height: 1 + Layout.topMargin: 2 + Layout.bottomMargin: 2 + Layout.fillWidth: true + color: MoneroComponents.Style.dividerColor + opacity: MoneroComponents.Style.dividerOpacity + } + + Rectangle { + visible: isTails + height: 1 + Layout.topMargin: 2 + Layout.bottomMargin: 2 + Layout.fillWidth: true + color: MoneroComponents.Style.dividerColor + opacity: MoneroComponents.Style.dividerOpacity + } + + MoneroComponents.TextBlock { + visible: isTails + Layout.fillWidth: true + font.pixelSize: 14 + text: qsTr("Tails: ") + translationManager.emptyString + } + + MoneroComponents.TextBlock { + visible: isTails + Layout.fillWidth: true + color: MoneroComponents.Style.dimmedFontColor + font.pixelSize: 14 + text: tailsUsePersistence ? qsTr("persistent") + translationManager.emptyString : qsTr("persistence disabled") + translationManager.emptyString; + } } // Copy info to clipboard 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..29f68f1a 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 @@ -253,5 +251,6 @@ images/copy.svg images/edit.svg images/arrow-right-in-circle-outline-medium-white.svg + images/tails-grey.png diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 46f843de..90172d15 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -524,7 +524,8 @@ bool Wallet::submitTxFile(const QString &fileName) const void Wallet::commitTransactionAsync(PendingTransaction *t) { m_scheduler.run([this, t] { - emit transactionCommitted(t->commit(), t, t->txid()); + auto txIdList = t->txid(); // retrieve before commit + emit transactionCommitted(t->commit(), t, txIdList); }); } diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 95d4a954..15da7a80 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -351,7 +351,7 @@ signals: void walletCreationHeightChanged(); void deviceButtonRequest(quint64 buttonCode); void deviceButtonPressed(); - void transactionCommitted(bool status, PendingTransaction *t, QStringList txid); + void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const; // emitted when transaction is created async diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 22a810b9..d4ec5d41 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -66,7 +66,7 @@ public: } auto tmpPass = m_mgr->m_passphrase.toStdString(); - m_mgr->m_passphrase = QString::null; + m_mgr->m_passphrase = QString(); return Monero::optional(tmpPass); } @@ -228,11 +228,11 @@ QString WalletManager::closeWallet() return result; } -void WalletManager::closeWalletAsync() +void WalletManager::closeWalletAsync(const QJSValue& callback) { m_scheduler.run([this] { - emit walletClosed(closeWallet()); - }); + return QJSValueList({closeWallet()}); + }, callback); } bool WalletManager::walletExists(const QString &path) const diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 18256b1c..54d2a29f 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -117,7 +117,7 @@ public: /*! * \brief closeWalletAsync - asynchronous version of "closeWallet" */ - Q_INVOKABLE void closeWalletAsync(); + Q_INVOKABLE void closeWalletAsync(const QJSValue& callback); //! checks is given filename is a wallet; Q_INVOKABLE bool walletExists(const QString &path) const; @@ -192,7 +192,6 @@ signals: void walletPassphraseNeeded(); void deviceButtonRequest(quint64 buttonCode); void deviceButtonPressed(); - void walletClosed(const QString &walletAddress); void checkUpdatesComplete(const QString &result) const; void miningStatus(bool isMining) const; diff --git a/src/qt/MoneroSettings.cpp b/src/qt/MoneroSettings.cpp new file mode 100644 index 00000000..ba3e6876 --- /dev/null +++ b/src/qt/MoneroSettings.cpp @@ -0,0 +1,207 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +****************************************************************************/ +// 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 +#include +#include +#include + +#include "src/qt/MoneroSettings.h" + +/*! + \qmlmodule moneroSettings 1.0 + \title Monero Settings QML Component + \ingroup qmlmodules + \brief Provides persistent platform-independent application settings. + + This component was introduced in order to have control over where the + configuration file is written. This is needed for Tails OS and + portable installations. + + For more information, see: https://doc.qt.io/qt-5/qml-qt-labs-settings-settings.html and + https://github.com/qt/qtdeclarative/blob/v5.12.0/src/imports/settings/qqmlsettings.cpp + + To use this module, import the module with the following line: + \code + import moneroComponents.Settings 1.0 + \endcode + + Usage: + \code + MoneroSettings { id: persistentSettings, property bool foo: true } + \endcode + + @TODO: Remove this QML component after migrating to Qt >= 5.12.0, as + `Qt.labs.settings` provides the fileName via a Q_PROPERTY +*/ + + +void MoneroSettings::load() +{ + const QMetaObject *mo = this->metaObject(); + const int offset = mo->propertyOffset(); + const int count = mo->propertyCount(); + + for (int i = offset; i < count; ++i) { + QMetaProperty property = mo->property(i); + const QVariant previousValue = readProperty(property); + const QVariant currentValue = this->m_settings->value(property.name(), previousValue); + + if (!currentValue.isNull() && (!previousValue.isValid() + || (currentValue.canConvert(previousValue.type()) && previousValue != currentValue))) { + property.write(this, currentValue); +#ifdef QT_DEBUG + qDebug() << "QQmlSettings: load" << property.name() << "setting:" << currentValue << "default:" << previousValue; +#endif + } + + // ensure that a non-existent setting gets written + // even if the property wouldn't change later + if (!this->m_settings->contains(property.name())) + this->_q_propertyChanged(); + + // setup change notifications on first load + if (!this->m_initialized && property.hasNotifySignal()) { + static const int propertyChangedIndex = mo->indexOfSlot("_q_propertyChanged()"); + int signalIndex = property.notifySignalIndex(); + QMetaObject::connect(this, signalIndex, this, propertyChangedIndex); + } + } +} + +void MoneroSettings::_q_propertyChanged() +{ + // Called on QML property change + const QMetaObject *mo = this->metaObject(); + const int offset = mo->propertyOffset(); + const int count = mo->propertyCount(); + for (int i = offset; i < count; ++i) { + const QMetaProperty &property = mo->property(i); + const QVariant value = readProperty(property); + this->m_changedProperties.insert(property.name(), value); +#ifdef QT_DEBUG + //qDebug() << "QQmlSettings: cache" << property.name() << ":" << value; +#endif + } + + if (this->m_timerId != 0) + this->killTimer(this->m_timerId); + this->m_timerId = this->startTimer(settingsWriteDelay); +} + +QVariant MoneroSettings::readProperty(const QMetaProperty &property) const +{ + QVariant var = property.read(this); + if (var.userType() == qMetaTypeId()) + var = var.value().toVariant(); + return var; +} + +void MoneroSettings::init() +{ + if (!this->m_initialized) { + this->m_settings = this->m_fileName.isEmpty() ? new QSettings() : new QSettings(this->m_fileName, QSettings::IniFormat); +#ifdef QT_DEBUG + qDebug() << "QQmlSettings: stored at" << this->m_settings->fileName(); +#endif + this->load(); + this->m_initialized = true; + } +} + +void MoneroSettings::reset() +{ + if (this->m_initialized && this->m_settings && !this->m_changedProperties.isEmpty()) + this->store(); + delete this->m_settings; +} + +void MoneroSettings::store() +{ + QHash::const_iterator it = this->m_changedProperties.constBegin(); + + while (it != this->m_changedProperties.constEnd()) { + this->m_settings->setValue(it.key(), it.value()); + +#ifdef QT_DEBUG + //qDebug() << "QQmlSettings: store" << it.key() << ":" << it.value(); +#endif + + ++it; + } + + this->m_changedProperties.clear(); +} + +void MoneroSettings::setFileName(const QString &fileName) +{ + if (fileName != this->m_fileName) { + this->reset(); + this->m_fileName = fileName; + if (this->m_initialized) + this->load(); + } +} + +QString MoneroSettings::fileName() const +{ + return this->m_fileName; +} + +void MoneroSettings::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == this->m_timerId) { + killTimer(this->m_timerId); + this->m_timerId = 0; + this->store(); + } + QObject::timerEvent(event); +} + +void MoneroSettings::componentComplete() +{ + this->init(); +} + +void MoneroSettings::classBegin() +{ +} + +MoneroSettings::MoneroSettings(QObject *parent) : + QObject(parent) +{ +} diff --git a/src/qt/MoneroSettings.h b/src/qt/MoneroSettings.h new file mode 100644 index 00000000..6de7f641 --- /dev/null +++ b/src/qt/MoneroSettings.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +****************************************************************************/ +// 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 MONEROSETTINGS_H +#define MONEROSETTINGS_H + +#include +#include +#include +#include +#include +#include + +static const int settingsWriteDelay = 500; // ms + +class MoneroSettings : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + Q_PROPERTY(QString fileName READ fileName WRITE setFileName FINAL) +public: + explicit MoneroSettings(QObject *parent = nullptr); + + QString fileName() const; + void setFileName(const QString &fileName); + +public slots: + void _q_propertyChanged(); + +protected: + void timerEvent(QTimerEvent *event) override; + void classBegin() override; + void componentComplete() override; + +private: + QVariant readProperty(const QMetaProperty &property) const; + void init(); + void reset(); + void load(); + void store(); + + QHash m_changedProperties; + QSettings *m_settings; + QString m_fileName = QString(""); + bool m_initialized = false; + int m_timerId = 0; +}; + +#endif // MONEROSETTINGS_H diff --git a/src/qt/TailsOS.cpp b/src/qt/TailsOS.cpp new file mode 100644 index 00000000..df2fa92b --- /dev/null +++ b/src/qt/TailsOS.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include + +#include "TailsOS.h" +#include "utils.h" + +bool TailsOS::usePersistence = false; +QString TailsOS::tailsPathData = QString("/live/persistence/TailsData_unlocked/"); + +bool TailsOS::detect() +{ + if (!fileExists("/etc/os-release")) + return false; + + QByteArray data = fileOpen("/etc/os-release"); + QRegularExpression re("TAILS_PRODUCT_NAME=\"Tails\""); + QRegularExpressionMatch os_match = re.match(data); + bool matched = os_match.hasMatch(); + +#ifdef QT_DEBUG + if (matched) + qDebug() << "Tails OS detected"; +#endif + + return matched; +} + +bool TailsOS::detectDataPersistence() +{ + return QDir(QDir::homePath() + "/Persistent").exists(); +} + +bool TailsOS::detectDotPersistence() +{ + return QDir(tailsPathData + "dotfiles").exists(); +} + +void TailsOS::showDataPersistenceDisabledWarning() +{ + QMessageBox msgBox; + msgBox.setText(QObject::tr("Warning: persistence disabled")); + msgBox.setWindowTitle(QObject::tr("Warning: persistence disabled")); + msgBox.setInformativeText( + QObject::tr("Monero GUI has detected that Tails persistence is " + "currently disabled. Any configurations you make inside " + "the Monero GUI will not be saved." + "\n\n" + "In addition, make sure to not save your wallet on the " + "filesystem, as it will be lost at shutdown." + "\n\n" + "To enable Tails persistence, setup an encrypted volume " + "and restart Tails. To gain a startup menu item, " + "enable the Tails \"dotfiles\" feature.")); + + msgBox.setStandardButtons(QMessageBox::Ok); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.setIconPixmap(QPixmap(":/images/tails-grey.png")); + msgBox.exec(); +} + +void TailsOS::askPersistence() +{ + QMessageBox msgBox; + msgBox.setWindowTitle(QObject::tr("Monero GUI")); + msgBox.setText(QObject::tr("Use Tails persistence?")); + msgBox.setInformativeText( + QObject::tr("Persist wallet files and configuration on the encrypted volume?" + "\n\n" + "In addition, you can enable Tails dotfiles persistence " + "to gain a start menu entry.\n")); + + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + msgBox.setIconPixmap(QPixmap(":/images/tails-grey.png")); + TailsOS::usePersistence = (msgBox.exec() == QMessageBox::Yes); +} + +void TailsOS::persistXdgMime(QString filePath, QString data) +{ + QFileInfo file(filePath); + QString tailsPath = tailsPathData + "dotfiles/.local/share/applications/"; + + // write to persistent volume +#ifdef QT_DEBUG + qDebug() << "Writing xdg mime: " << tailsPath + file.fileName(); +#endif + + QDir().mkpath(tailsPath); // ensure directory exists + fileWrite(tailsPath + file.fileName(), data); + + // write to current session +#ifdef QT_DEBUG + qDebug() << "Writing xdg mime: " << file.filePath(); +#endif + + QDir().mkpath(file.path()); // ensure directory exists + fileWrite(file.filePath(), data); +} diff --git a/src/qt/TailsOS.h b/src/qt/TailsOS.h new file mode 100644 index 00000000..05d5ebd1 --- /dev/null +++ b/src/qt/TailsOS.h @@ -0,0 +1,23 @@ +#ifndef TAILSOS_H +#define TAILSOS_H + +#include + + +class TailsOS +{ +public: + TailsOS(); + static bool detect(); + static bool detectDataPersistence(); + static bool detectDotPersistence(); + + static void showDataPersistenceDisabledWarning(); + static void askPersistence(); + static void persistXdgMime(QString filePath, QString data); + + static bool usePersistence; + static QString tailsPathData; +}; + +#endif // TAILSOS_H diff --git a/src/qt/ipc.cpp b/src/qt/ipc.cpp index 0753470e..ae6b061d 100644 --- a/src/qt/ipc.cpp +++ b/src/qt/ipc.cpp @@ -99,8 +99,8 @@ bool IPC::saveCommand(QString cmdString){ return true; } -bool IPC::saveCommand(const QUrl &url){; - this->saveCommand(url.toString()); +bool IPC::saveCommand(const QUrl &url){ + return this->saveCommand(url.toString()); } void IPC::handleConnection(){ diff --git a/src/qt/mime.h b/src/qt/macoshelper.h similarity index 91% rename from src/qt/mime.h rename to src/qt/macoshelper.h index b79d43a3..bddf70c6 100644 --- a/src/qt/mime.h +++ b/src/qt/macoshelper.h @@ -26,11 +26,15 @@ // 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 MIME_H -#define MIME_H +#ifndef MACOSHELPER_H +#define MACOSHELPER_H -#include +class MacOSHelper +{ + MacOSHelper() {} -void registerXdgMime(QApplication &app); +public: + static bool isCapsLock(); +}; -#endif // MIME_H +#endif //MACOSHELPER_H diff --git a/src/qt/mime.cpp b/src/qt/macoshelper.mm similarity index 55% rename from src/qt/mime.cpp rename to src/qt/macoshelper.mm index 6daf4de6..0b29f25f 100644 --- a/src/qt/mime.cpp +++ b/src/qt/macoshelper.mm @@ -27,44 +27,23 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include -#include -#include -#include +#include +#include +#include "macoshelper.h" -#include "mime.h" -#include "utils.h" +#import +#import +#include +#include +#include -void registerXdgMime(QApplication &app){ - // MacOS handled via Info.plist - // Windows handled in the installer by rbrunner7 - - QString xdg = QString( - "[Desktop Entry]\n" - "Name=Monero GUI\n" - "GenericName=Monero-GUI\n" - "X-GNOME-FullName=Monero-GUI\n" - "Comment=Monero GUI\n" - "Keywords=Monero;\n" - "Exec=%1 %u\n" - "Terminal=false\n" - "Type=Application\n" - "Icon=monero\n" - "Categories=Network;GNOME;Qt;\n" - "MimeType=x-scheme-handler/monero;x-scheme-handler/moneroseed\n" - "StartupNotify=true\n" - "X-GNOME-Bugzilla-Bugzilla=GNOME\n" - "X-GNOME-UsesNotifications=true\n" - ).arg(app.applicationFilePath()); - - QString appPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); - QString filePath = QString("%1/monero-gui.desktop").arg(appPath); - - qDebug() << QString("Writing %1").arg(filePath); - QFile file(filePath); - if(file.open(QIODevice::WriteOnly)){ - QTextStream out(&file); out << xdg << endl; - file.close(); - } - else - file.close(); +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/src/qt/utils.cpp b/src/qt/utils.cpp index 2407a50e..4418a050 100644 --- a/src/qt/utils.cpp +++ b/src/qt/utils.cpp @@ -27,15 +27,35 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include +#include "src/qt/TailsOS.h" #include "utils.h" bool fileExists(QString path) { QFileInfo check_file(path); - if (check_file.exists() && check_file.isFile()) + return check_file.exists() && check_file.isFile(); +} + +QByteArray fileOpen(QString path) { + QFile file(path); + if(!file.open(QFile::ReadOnly | QFile::Text)) + return QByteArray(); + + QByteArray data = file.readAll(); + file.close(); + return data; +} + +bool fileWrite(QString path, QString data) { + QFile file(path); + if(file.open(QIODevice::WriteOnly)){ + QTextStream out(&file); out << data << endl; + file.close(); return true; - else - return false; + } + + return false; } QString getAccountName(){ @@ -47,6 +67,53 @@ QString getAccountName(){ return accountName; } +QString xdgMime(QApplication &app){ + return QString( + "[Desktop Entry]\n" + "Name=Monero GUI\n" + "GenericName=Monero-GUI\n" + "X-GNOME-FullName=Monero-GUI\n" + "Comment=Monero GUI\n" + "Keywords=Monero;\n" + "Exec=%1 %u\n" + "Terminal=false\n" + "Type=Application\n" + "Icon=monero\n" + "Categories=Network;GNOME;Qt;\n" + "MimeType=x-scheme-handler/monero;x-scheme-handler/moneroseed\n" + "StartupNotify=true\n" + "X-GNOME-Bugzilla-Bugzilla=GNOME\n" + "X-GNOME-UsesNotifications=true\n" + ).arg(app.applicationFilePath()); +} + +void registerXdgMime(QApplication &app){ +#ifdef Q_OS_LINUX + // Register desktop entry + // - MacOS handled via Info.plist + // - Windows handled in the installer by rbrunner7 + // - Linux written to `QStandardPaths::ApplicationsLocation` + // - Tails written to persistent dotfiles + QString mime = xdgMime(app); + QString appPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); + QString filePath = QString("%1/monero-gui.desktop").arg(appPath); + + if (TailsOS::detect() && TailsOS::detectDotPersistence() && TailsOS::usePersistence) { + TailsOS::persistXdgMime(filePath, mime); + return; + } + + QFileInfo file(filePath); + QDir().mkpath(file.path()); // ensure directory exists + +#ifdef QT_DEBUG + qDebug() << "Writing xdg mime: " << filePath; +#endif + + fileWrite(filePath, mime); +#endif +} + QString randomUserAgent(){ QStringList urand; urand << "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1" diff --git a/src/qt/utils.h b/src/qt/utils.h index c18ffdc1..f48dff97 100644 --- a/src/qt/utils.h +++ b/src/qt/utils.h @@ -31,9 +31,14 @@ #include #include +#include bool fileExists(QString path); +QByteArray fileOpen(QString path); +bool fileWrite(QString path, QString data); QString getAccountName(); +QString xdgMime(QApplication &app); +void registerXdgMime(QApplication &app); const static QRegExp reURI = QRegExp("^\\w+:\\/\\/([\\w+\\-?\\-_\\-=\\-&]+)"); QString randomUserAgent(); 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){ diff --git a/wizard/WizardCreateWallet1.qml b/wizard/WizardCreateWallet1.qml index 577243c3..f5090114 100644 --- a/wizard/WizardCreateWallet1.qml +++ b/wizard/WizardCreateWallet1.qml @@ -151,7 +151,7 @@ Rectangle { labelFontSize: 14 copyButton: false readOnly: true - text: Utils.roundDownToNearestThousand(wizardController.m_wallet.walletCreationHeight) + text: Utils.roundDownToNearestThousand(wizardController.m_wallet ? wizardController.m_wallet.walletCreationHeight : 0) } MoneroComponents.WarningBox { diff --git a/wizard/WizardSummary.qml b/wizard/WizardSummary.qml index 1698d28e..a94d921d 100644 --- a/wizard/WizardSummary.qml +++ b/wizard/WizardSummary.qml @@ -57,12 +57,6 @@ ColumnLayout { value: wizardController.language_language } - WizardSummaryItem { - Layout.fillWidth: true - header: qsTr("Wallet name") + translationManager.emptyString - value: walletOptionsName - } - WizardSummaryItem { Layout.fillWidth: true header: qsTr("Restore height") + translationManager.emptyString