From 0794f0a3fdc80f3316a206305a01a5356b1d270f Mon Sep 17 00:00:00 2001
From: Joseph Donofry <joedonofry@gmail.com>
Date: Mon, 25 Jan 2021 23:46:55 -0500
Subject: [PATCH] Initial commit for privacy screen

Missing window focus event and knowing when room is encryption
---
 resources/qml/PrivacyScreen.qml | 74 +++++++++++++++++++++++++++++++++
 resources/qml/TimelineRow.qml   |  2 +-
 resources/qml/TimelineView.qml  |  8 ++++
 resources/res.qrc               |  1 +
 src/UserSettingsPage.cpp        | 63 +++++++++++++++++++++++++++-
 src/UserSettingsPage.h          | 13 ++++++
 6 files changed, 159 insertions(+), 2 deletions(-)
 create mode 100644 resources/qml/PrivacyScreen.qml

diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml
new file mode 100644
index 000000000..497630f1e
--- /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 c4c18e0e0..95a025cf6 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 38e3a9285..5f43de55a 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 e3998bd1f..308d81a69 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -131,6 +131,7 @@
         <file>qml/MessageInput.qml</file>
         <file>qml/MessageView.qml</file>
         <file>qml/NhekoBusyIndicator.qml</file>
+        <file>qml/PrivacyScreen.qml</file>
         <file>qml/Reactions.qml</file>
         <file>qml/ReplyPopup.qml</file>
         <file>qml/ScrollHelper.qml</file>
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index f90938c92..1875e4f97 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -95,6 +95,8 @@ UserSettings::load(std::optional<QString> 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<UserSettings> 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<UserSettings> 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<UserSettings> 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<UserSettings> 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<UserSettings> 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<UserSettings> 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<UserSettings> settings, QWidge
                 this,
                 [this](int newValue) { settings_->setTimelineMaxWidth(newValue); });
 
+        connect(privacyScreenTimeout_,
+                qOverload<int>(&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 6744d101a..7e475c3bd 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_;
-- 
GitLab