diff --git a/filter.cpp b/filter.cpp index 3ba011bf..9129d92b 100644 --- a/filter.cpp +++ b/filter.cpp @@ -38,6 +38,10 @@ filter::filter(QObject *parent) : } bool filter::eventFilter(QObject *obj, QEvent *ev) { + if(ev->type() == QEvent::KeyPress || ev->type() == QEvent::MouseButtonRelease){ + emit userActivity(); + } + switch(ev->type()) { case QEvent::KeyPress: { QKeyEvent *ke = static_cast(ev); diff --git a/filter.h b/filter.h index 4c998a29..bb3a78b7 100644 --- a/filter.h +++ b/filter.h @@ -48,6 +48,7 @@ signals: void sequenceReleased(const QVariant &o, const QVariant &seq); void mousePressed(const QVariant &o, const QVariant &x, const QVariant &y); void mouseReleased(const QVariant &o, const QVariant &x, const QVariant &y); + void userActivity(); }; #endif // FILTER_H diff --git a/main.cpp b/main.cpp index 5ebaff50..82229c23 100644 --- a/main.cpp +++ b/main.cpp @@ -324,6 +324,6 @@ int main(int argc, char *argv[]) QObject::connect(eventFilter, SIGNAL(sequenceReleased(QVariant,QVariant)), rootObject, SLOT(sequenceReleased(QVariant,QVariant))); QObject::connect(eventFilter, SIGNAL(mousePressed(QVariant,QVariant,QVariant)), rootObject, SLOT(mousePressed(QVariant,QVariant,QVariant))); QObject::connect(eventFilter, SIGNAL(mouseReleased(QVariant,QVariant,QVariant)), rootObject, SLOT(mouseReleased(QVariant,QVariant,QVariant))); - + QObject::connect(eventFilter, SIGNAL(userActivity()), rootObject, SLOT(userActivity())); return app.exec(); } diff --git a/main.qml b/main.qml index 84f7d38d..d8f9a699 100644 --- a/main.qml +++ b/main.qml @@ -75,6 +75,7 @@ ApplicationWindow { property var cameraUi property bool remoteNodeConnected: false property bool androidCloseTapped: false; + property int userLastActive; // epoch // Default daemon addresses readonly property string localDaemonAddress : persistentSettings.nettype == NetworkType.MAINNET ? "localhost:18081" : persistentSettings.nettype == NetworkType.TESTNET ? "localhost:28081" : "localhost:38081" property string currentDaemonAddress; @@ -219,6 +220,8 @@ ApplicationWindow { // Local daemon settings walletManager.setDaemonAddress(localDaemonAddress) + // enable user inactivity timer + userInActivityTimer.running = true; // wallet already opened with wizard, we just need to initialize it if (typeof wizard.m_wallet !== 'undefined') { @@ -932,6 +935,8 @@ ApplicationWindow { rootItem.state = "wizard" // reset balance leftPanel.balanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(0); + // disable inactivity timer + userInActivityTimer.running = false; } function hideMenu() { @@ -1041,6 +1046,8 @@ ApplicationWindow { property int segregationHeight: 0 property int kdfRounds: 1 property bool hideBalance: false + property bool lockOnUserInActivity: true + property int lockOnUserInActivityInterval: 10 // minutes } // Information dialog @@ -1696,6 +1703,12 @@ ApplicationWindow { triggeredOnStart: false } + Timer { + id: userInActivityTimer + interval: 2000; running: false; repeat: true + onTriggered: checkInUserActivity() + } + Rectangle { id: statusMessage z: 99 @@ -1823,6 +1836,32 @@ ApplicationWindow { leftPanel.balanceLabelText = qsTr("Balance") } + function userActivity() { + // register user activity + var epoch = Math.floor((new Date).getTime()/1000); + appWindow.userLastActive = epoch; + } + + function checkInUserActivity() { + if(!persistentSettings.lockOnUserInActivity) return; + + // prompt password after X seconds of inactivity + var epoch = Math.floor((new Date).getTime() / 1000); + var inactivity = epoch - appWindow.userLastActive; + if(inactivity < (persistentSettings.lockOnUserInActivityInterval * 60)) return; + + passwordDialog.onAcceptedCallback = function() { + if(walletPassword === passwordDialog.password){ + passwordDialog.close(); + } else { + passwordDialog.showError(qsTr("Wrong password")); + } + } + + passwordDialog.onRejectedCallback = function() { appWindow.showWizard(); } + passwordDialog.open(); + } + // Daemon console DaemonConsole { id: daemonConsolePopup diff --git a/pages/settings/Settings.qml b/pages/settings/Settings.qml index 5aabcccd..9aa44461 100644 --- a/pages/settings/Settings.qml +++ b/pages/settings/Settings.qml @@ -65,17 +65,16 @@ ColumnLayout { onCurrentViewChanged: { if (previousView) { -// if (typeof previousView.onPageClosed === "function") { -// previousView.onPageClosed(); -// } + if (typeof previousView.onPageClosed === "function") { + previousView.onPageClosed(); + } } previousView = currentView if (currentView) { stackView.replace(currentView) - // Component.onCompleted is called before wallet is initilized -// if (typeof currentView.onPageCompleted === "function") { -// currentView.onPageCompleted(); -// } + if (typeof currentView.onPageCompleted === "function") { + currentView.onPageCompleted(); + } } } diff --git a/pages/settings/SettingsLayout.qml b/pages/settings/SettingsLayout.qml index 974da5e3..6914a438 100644 --- a/pages/settings/SettingsLayout.qml +++ b/pages/settings/SettingsLayout.qml @@ -40,6 +40,14 @@ Rectangle { height: 1400 Layout.fillWidth: true + function onPageCompleted() { + userInactivitySliderTimer.running = true; + } + + function onPageClosed() { + userInactivitySliderTimer.running = false; + } + ColumnLayout { id: settingsUI property int itemHeight: 60 * scaleRatio @@ -70,6 +78,82 @@ Rectangle { text: qsTr("Hide balance") + translationManager.emptyString } + MoneroComponents.CheckBox { + visible: !isMobile + id: userInActivityCheckbox + checked: persistentSettings.lockOnUserInActivity + onClicked: persistentSettings.lockOnUserInActivity = !persistentSettings.lockOnUserInActivity + text: qsTr("Lock wallet on inactivity") + translationManager.emptyString + } + + ColumnLayout { + visible: userInActivityCheckbox.checked + Layout.fillWidth: true + Layout.topMargin: 6 * scaleRatio + Layout.leftMargin: 42 * scaleRatio + spacing: 0 + + MoneroComponents.TextBlock { + font.pixelSize: 14 * scaleRatio + Layout.fillWidth: true + text: { + var val = userInactivitySlider.value; + var minutes = val > 1 ? qsTr("minutes") : qsTr("minute"); + + qsTr("After ") + val + " " + minutes + translationManager.emptyString; + } + } + + Slider { + id: userInactivitySlider + from: 1 + value: persistentSettings.lockOnUserInActivityInterval + to: 60 + leftPadding: 0 + stepSize: 1 + snapMode: Slider.SnapAlways + + background: Rectangle { + x: parent.leftPadding + y: parent.topPadding + parent.availableHeight / 2 - height / 2 + implicitWidth: 200 * scaleRatio + implicitHeight: 4 * scaleRatio + width: parent.availableWidth + height: implicitHeight + radius: 2 + color: MoneroComponents.Style.grey + + Rectangle { + width: parent.visualPosition * parent.width + height: parent.height + color: MoneroComponents.Style.green + radius: 2 + } + } + + handle: Rectangle { + x: parent.leftPadding + parent.visualPosition * (parent.availableWidth - width) + y: parent.topPadding + parent.availableHeight / 2 - height / 2 + implicitWidth: 18 * scaleRatio + implicitHeight: 18 * scaleRatio + radius: 8 + color: parent.pressed ? "#f0f0f0" : "#f6f6f6" + border.color: MoneroComponents.Style.grey + } + } + + Timer { + // @TODO: Slider.onMoved{} is available in Qt > 5.9, use a hacky timer for now + id: userInactivitySliderTimer + interval: 1000; running: false; repeat: true + onTriggered: { + if(persistentSettings.lockOnUserInActivityInterval != userInactivitySlider.value) { + persistentSettings.lockOnUserInActivityInterval = userInactivitySlider.value; + } + } + } + } + MoneroComponents.TextBlock { visible: isMobile font.pixelSize: 14