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_;