diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml new file mode 100644 index 00000000..497630f1 --- /dev/null +++ b/resources/qml/PrivacyScreen.qml @@ -0,0 +1,74 @@ +import QtQuick 2.12 +import QtGraphicalEffects 1.0 + +Item { + property var timelineRoot + property var imageSource + property int screenTimeout + anchors.fill: parent + + Timer { + id: screenSaverTimer + interval: screenTimeout * 1000 + running: true + onTriggered: { + timelineRoot.grabToImage(function(result) { + imageSource = result.url; + screenSaver.visible = true + particles.resume() + }, Qt.size(width, height)) + } + } + + // Reset screensaver timer when clicks are received + MouseArea { + anchors.fill: parent + // Pass mouse events through + propagateComposedEvents: true + hoverEnabled: true + onClicked: { + screenSaverTimer.restart(); + mouse.accepted = false; + } + } + + Rectangle { + id: screenSaver + anchors.fill: parent + visible: false + color: "transparent" + + Image { + id: image + visible : screenSaver.visible + anchors.fill: parent + source: imageSource + } + + ShaderEffectSource { + id: effectSource + + sourceItem: image + anchors.fill: image + sourceRect: Qt.rect(0,0, width, height) + } + + FastBlur{ + id: blur + anchors.fill: effectSource + source: effectSource + radius: 50 + } + + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + hoverEnabled: true + onClicked: { + screenSaver.visible = false; + screenSaverTimer.restart(); + mouse.accepted = false + } + } + } +} \ No newline at end of file diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index c4c18e0e..95a025cf 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -28,7 +28,7 @@ Item { if (mouse.button === Qt.RightButton) messageContextMenu.show(model.id, model.type, model.isEncrypted, row); else - event.accepted = false; + mouse.accepted = false; } onPressAndHold: { messageContextMenu.show(model.id, model.type, model.isEncrypted, row, mapToItem(timelineRoot, mouse.x, mouse.y)); diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 38e3a928..5f43de55 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -1,3 +1,4 @@ +import "." import "./delegates" import "./device-verification" import "./emoji" @@ -187,6 +188,7 @@ Page { } ColumnLayout { + id: timelineLayout visible: TimelineManager.timeline != null anchors.fill: parent spacing: 0 @@ -271,6 +273,12 @@ Page { } + PrivacyScreen { + visible: Settings.privacyScreen + screenTimeout: Settings.privacyScreenTimeout + timelineRoot: timelineRoot + } + } systemInactive: SystemPalette { diff --git a/resources/res.qrc b/resources/res.qrc index e3998bd1..308d81a6 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -131,6 +131,7 @@ qml/MessageInput.qml qml/MessageView.qml qml/NhekoBusyIndicator.qml + qml/PrivacyScreen.qml qml/Reactions.qml qml/ReplyPopup.qml qml/ScrollHelper.qml diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index f90938c9..1875e4f9 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -95,6 +95,8 @@ UserSettings::load(std::optional profile) font_ = settings.value("user/font_family", "default").toString(); avatarCircles_ = settings.value("user/avatar_circles", true).toBool(); decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); + privacyScreen_ = settings.value("user/privacy_screen", false).toBool(); + privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt(); shareKeysWithTrustedUsers_ = settings.value("user/share_keys_with_trusted_users", true).toBool(); mobileMode_ = settings.value("user/mobile_mode", false).toBool(); @@ -284,6 +286,28 @@ UserSettings::setDecryptSidebar(bool state) save(); } +void +UserSettings::setPrivacyScreen(bool state) +{ + if (state == privacyScreen_) { + return; + } + privacyScreen_ = state; + emit privacyScreenChanged(state); + save(); +} + +void +UserSettings::setPrivacyScreenTimeout(int state) +{ + if (state == privacyScreenTimeout_) { + return; + } + privacyScreenTimeout_ = state; + emit privacyScreenTimeoutChanged(state); + save(); +} + void UserSettings::setFontSize(double size) { @@ -531,6 +555,8 @@ UserSettings::save() settings.setValue("avatar_circles", avatarCircles_); settings.setValue("decrypt_sidebar", decryptSidebar_); + settings.setValue("privacy_screen", privacyScreen_); + settings.setValue("privacy_screen_timeout", privacyScreenTimeout_); settings.setValue("share_keys_with_trusted_users", shareKeysWithTrustedUsers_); settings.setValue("mobile_mode", mobileMode_); settings.setValue("font_size", baseFontSize_); @@ -619,6 +645,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge startInTrayToggle_ = new Toggle{this}; avatarCircles_ = new Toggle{this}; decryptSidebar_ = new Toggle(this); + privacyScreen_ = new Toggle{this}; shareKeysWithTrustedUsers_ = new Toggle(this); groupViewToggle_ = new Toggle{this}; timelineButtonsToggle_ = new Toggle{this}; @@ -642,11 +669,13 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge cameraResolutionCombo_ = new QComboBox{this}; cameraFrameRateCombo_ = new QComboBox{this}; timelineMaxWidthSpin_ = new QSpinBox{this}; + privacyScreenTimeout_ = new QSpinBox{this}; trayToggle_->setChecked(settings_->tray()); startInTrayToggle_->setChecked(settings_->startInTray()); avatarCircles_->setChecked(settings_->avatarCircles()); decryptSidebar_->setChecked(settings_->decryptSidebar()); + privacyScreen_->setChecked(settings_->privacyScreen()); shareKeysWithTrustedUsers_->setChecked(settings_->shareKeysWithTrustedUsers()); groupViewToggle_->setChecked(settings_->groupView()); timelineButtonsToggle_->setChecked(settings_->buttonsInTimeline()); @@ -666,6 +695,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge startInTrayToggle_->setDisabled(true); } + if (!settings_->privacyScreen()) { + privacyScreenTimeout_->setDisabled(true); + } + avatarCircles_->setFixedSize(64, 48); auto uiLabel_ = new QLabel{tr("INTERFACE"), this}; @@ -706,6 +739,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge timelineMaxWidthSpin_->setMaximum(100'000'000); timelineMaxWidthSpin_->setSingleStep(10); + privacyScreenTimeout_->setMinimum(0); + privacyScreenTimeout_->setMaximum(3600); + privacyScreenTimeout_->setSingleStep(10); + auto callsLabel = new QLabel{tr("CALLS"), this}; callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin); callsLabel->setAlignment(Qt::AlignBottom); @@ -799,6 +836,13 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge decryptSidebar_, tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in " "encrypted chats.")); + boxWrap(tr("Encrypted chat privacy screen"), + privacyScreen_, + tr("When the window loses focus, the timeline will\nbe blurred.")); + boxWrap(tr("Privacy screen timeout"), + privacyScreenTimeout_, + tr("Set timeout for how long after window loses\nfocus before the screen" + " will be blurred.\nSet to 0 to blur immediately after focus loss.")); boxWrap(tr("Show buttons in timeline"), timelineButtonsToggle_, tr("Show buttons to quickly reply, react or access additional options next to each " @@ -1057,7 +1101,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge connect(decryptSidebar_, &Toggle::toggled, this, [this](bool enabled) { settings_->setDecryptSidebar(enabled); - emit decryptSidebarChanged(); + }); + + connect(privacyScreen_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setPrivacyScreen(enabled); + if (enabled) { + privacyScreenTimeout_->setEnabled(true); + } else { + privacyScreenTimeout_->setDisabled(true); + } }); connect(shareKeysWithTrustedUsers_, &Toggle::toggled, this, [this](bool enabled) { @@ -1113,6 +1165,13 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge this, [this](int newValue) { settings_->setTimelineMaxWidth(newValue); }); + connect(privacyScreenTimeout_, + qOverload(&QSpinBox::valueChanged), + this, + [this](int newValue) { + settings_->setPrivacyScreenTimeout(newValue); + }); + connect( sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys); @@ -1146,6 +1205,7 @@ UserSettingsPage::showEvent(QShowEvent *) startInTrayToggle_->setState(settings_->startInTray()); groupViewToggle_->setState(settings_->groupView()); decryptSidebar_->setState(settings_->decryptSidebar()); + privacyScreen_->setState(settings_->privacyScreen()); shareKeysWithTrustedUsers_->setState(settings_->shareKeysWithTrustedUsers()); avatarCircles_->setState(settings_->avatarCircles()); typingNotifications_->setState(settings_->typingNotifications()); @@ -1160,6 +1220,7 @@ UserSettingsPage::showEvent(QShowEvent *) enlargeEmojiOnlyMessages_->setState(settings_->enlargeEmojiOnlyMessages()); deviceIdValue_->setText(QString::fromStdString(http::client()->device_id())); timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth()); + privacyScreenTimeout_->setValue(settings_->privacyScreenTimeout()); WebRTCSession::instance().refreshDevices(); auto mics = diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index 6744d101..7e475c3b 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -67,6 +67,9 @@ class UserSettings : public QObject bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged) Q_PROPERTY(bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY decryptSidebarChanged) + Q_PROPERTY(bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY + privacyScreenChanged) + Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout NOTIFY privacyScreenTimeoutChanged) Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY timelineMaxWidthChanged) Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged) @@ -131,6 +134,8 @@ public: void setAlertOnNotification(bool state); void setAvatarCircles(bool state); void setDecryptSidebar(bool state); + void setPrivacyScreen(bool state); + void setPrivacyScreenTimeout(int state); void setPresence(Presence state); void setRingtone(QString ringtone); void setMicrophone(QString microphone); @@ -153,6 +158,8 @@ public: bool groupView() const { return groupView_; } bool avatarCircles() const { return avatarCircles_; } bool decryptSidebar() const { return decryptSidebar_; } + bool privacyScreen() const { return privacyScreen_; } + int privacyScreenTimeout() const { return privacyScreenTimeout_; } bool markdown() const { return markdown_; } bool typingNotifications() const { return typingNotifications_; } bool sortByImportance() const { return sortByImportance_; } @@ -199,6 +206,8 @@ signals: void alertOnNotificationChanged(bool state); void avatarCirclesChanged(bool state); void decryptSidebarChanged(bool state); + void privacyScreenChanged(bool state); + void privacyScreenTimeoutChanged(int state); void timelineMaxWidthChanged(int state); void mobileModeChanged(bool mode); void fontSizeChanged(double state); @@ -239,6 +248,8 @@ private: bool hasAlertOnNotification_; bool avatarCircles_; bool decryptSidebar_; + bool privacyScreen_; + int privacyScreenTimeout_; bool shareKeysWithTrustedUsers_; bool mobileMode_; int timelineMaxWidth_; @@ -317,6 +328,8 @@ private: Toggle *avatarCircles_; Toggle *useStunServer_; Toggle *decryptSidebar_; + Toggle *privacyScreen_; + QSpinBox *privacyScreenTimeout_; Toggle *shareKeysWithTrustedUsers_; Toggle *mobileMode_; QLabel *deviceFingerprintValue_;