Prevent screen capture on Windows and macOS

* Closes #5859
This commit is contained in:
smlu 2021-02-04 16:39:13 -05:00 committed by Jonathan White
parent 9a8a5a0006
commit a5094dd3ea
11 changed files with 100 additions and 5 deletions

View File

@ -25,3 +25,9 @@ OSUtilsBase::OSUtilsBase(QObject* parent)
OSUtilsBase::~OSUtilsBase()
{
}
bool OSUtilsBase::setPreventScreenCapture(QWindow*, bool) const
{
// Do nothing by default
return false;
}

View File

@ -21,6 +21,8 @@
#include <QObject>
#include <QPointer>
class QWindow;
/**
* Abstract base class for generic OS-specific functionality
* which can be reasonably expected to be available on all platforms.
@ -63,6 +65,9 @@ public:
QString* error = nullptr) = 0;
virtual bool unregisterGlobalShortcut(const QString& name) = 0;
virtual bool canPreventScreenCapture() const = 0;
virtual bool setPreventScreenCapture(QWindow* window, bool allow) const;
signals:
void globalShortcutTriggered(const QString& name);

View File

@ -23,6 +23,8 @@
#include <QColor>
#include <unistd.h>
class QWindow;
class AppKit : public QObject
{
Q_OBJECT
@ -42,6 +44,7 @@ public:
bool enableAccessibility();
bool enableScreenRecording();
void toggleForegroundApp(bool foreground);
void setWindowSecurity(QWindow* window, bool state);
signals:
void lockDatabases();

View File

@ -20,6 +20,7 @@
#import <Foundation/Foundation.h>
#import <AppKit/NSRunningApplication.h>
#import <AppKit/NSWindow.h>
@interface AppKitImpl : NSObject
{
@ -40,5 +41,6 @@
- (bool) enableAccessibility;
- (bool) enableScreenRecording;
- (void) toggleForegroundApp:(bool) foreground;
- (void) setWindowSecurity:(NSWindow*) window state:(bool) state;
@end

View File

@ -18,11 +18,14 @@
#import "AppKitImpl.h"
#include "AppKit.h"
#include <QWindow>
#import <AppKit/NSStatusBar.h>
#import <AppKit/NSStatusItem.h>
#import <AppKit/NSStatusBarButton.h>
#import <AppKit/NSWorkspace.h>
#import <AppKit/NSWindow.h>
#import <AppKit/NSView.h>
#import <CoreVideo/CVPixelBuffer.h>
@implementation AppKitImpl
@ -211,6 +214,11 @@
}
}
- (void) setWindowSecurity:(NSWindow*) window state:(bool) state
{
[window setSharingType: state ? NSWindowSharingNone : NSWindowSharingReadOnly];
}
@end
//
@ -285,3 +293,9 @@ void AppKit::toggleForegroundApp(bool foreground)
{
[static_cast<id>(self) toggleForegroundApp:foreground];
}
void AppKit::setWindowSecurity(QWindow* window, bool state)
{
auto view = reinterpret_cast<NSView*>(window->winId());
[static_cast<id>(self) setWindowSecurity:view.window state:state];
}

View File

@ -23,6 +23,7 @@
#include <QSettings>
#include <QStandardPaths>
#include <QTimer>
#include <QWindow>
#include <ApplicationServices/ApplicationServices.h>
#include <CoreGraphics/CGEventSource.h>
@ -40,9 +41,7 @@ MacUtils::MacUtils(QObject* parent)
connect(m_appkit.data(), &AppKit::interfaceThemeChanged, this, [this]() {
// Emit with delay, since isStatusBarDark() still returns the old value
// if we call it too fast after a theme change.
QTimer::singleShot(100, [this]() {
emit statusbarThemeChanged();
});
QTimer::singleShot(100, [this]() { emit statusbarThemeChanged(); });
});
}
@ -152,6 +151,21 @@ void MacUtils::toggleForegroundApp(bool foreground)
m_appkit->toggleForegroundApp(foreground);
}
bool MacUtils::canPreventScreenCapture() const
{
return true;
}
bool MacUtils::setPreventScreenCapture(QWindow* window, bool prevent) const
{
if (!window) {
return false;
}
m_appkit->setWindowSecurity(window, prevent);
return true;
}
void MacUtils::registerNativeEventFilter()
{
EventTypeSpec eventSpec;

View File

@ -62,6 +62,9 @@ public:
uint16 qtToNativeKeyCode(Qt::Key key);
CGEventFlags qtToNativeModifiers(Qt::KeyboardModifiers modifiers, bool native);
bool canPreventScreenCapture() const override;
bool setPreventScreenCapture(QWindow* window, bool prevent) const override;
signals:
void lockDatabases();

View File

@ -43,6 +43,11 @@ public:
QString* error = nullptr) override;
bool unregisterGlobalShortcut(const QString& name) override;
bool canPreventScreenCapture() const override
{
return false;
}
signals:
void keymapChanged();

View File

@ -16,12 +16,14 @@
*/
#include "WinUtils.h"
#include <QAbstractNativeEventFilter>
#include <QApplication>
#include <QDir>
#include <QSettings>
#include <QWindow>
#include <windows.h>
#include <Windows.h>
#undef MessageBox
QPointer<WinUtils> WinUtils::m_instance = nullptr;
@ -49,6 +51,20 @@ WinUtils::WinUtils(QObject* parent)
{
}
bool WinUtils::canPreventScreenCapture() const
{
return true;
}
bool WinUtils::setPreventScreenCapture(QWindow* window, bool prevent) const
{
if (window) {
HWND handle = reinterpret_cast<HWND>(window->winId());
return SetWindowDisplayAffinity(handle, prevent ? WDA_EXCLUDEFROMCAPTURE : WDA_NONE);
}
return false;
}
/**
* Register event filters to handle native platform events such as global hotkeys
*/

View File

@ -52,6 +52,9 @@ public:
DWORD qtToNativeKeyCode(Qt::Key key);
DWORD qtToNativeModifiers(Qt::KeyboardModifiers modifiers);
bool canPreventScreenCapture() const override;
bool setPreventScreenCapture(QWindow* window, bool prevent) const override;
protected:
explicit WinUtils(QObject* parent = nullptr);
~WinUtils() override = default;

View File

@ -19,6 +19,7 @@
#include <QCommandLineParser>
#include <QFile>
#include <QTextStream>
#include <QWindow>
#include "cli/Utils.h"
#include "config-keepassx.h"
@ -28,6 +29,7 @@
#include "gui/Application.h"
#include "gui/MainWindow.h"
#include "gui/MessageBox.h"
#include "gui/osutils/OSUtils.h"
#if defined(WITH_ASAN) && defined(WITH_LSAN)
#include <sanitizer/lsan_interface.h>
@ -65,6 +67,8 @@ int main(int argc, char** argv)
"localconfig", QObject::tr("path to a custom local config file"), "localconfig");
QCommandLineOption keyfileOption("keyfile", QObject::tr("key file of the database"), "keyfile");
QCommandLineOption pwstdinOption("pw-stdin", QObject::tr("read password of the database from stdin"));
QCommandLineOption allowScreenCaptureOption("allow-screencapture",
QObject::tr("allow app screen recordering and screenshots"));
QCommandLineOption helpOption = parser.addHelpOption();
QCommandLineOption versionOption = parser.addVersionOption();
@ -75,6 +79,10 @@ int main(int argc, char** argv)
parser.addOption(pwstdinOption);
parser.addOption(debugInfoOption);
if (osUtils->canPreventScreenCapture()) {
parser.addOption(allowScreenCaptureOption);
}
Application app(argc, argv);
// don't set organizationName as that changes the return value of
// QStandardPaths::writableLocation(QDesktopServices::DataLocation)
@ -132,6 +140,22 @@ int main(int argc, char** argv)
MainWindow mainWindow;
#ifndef QT_DEBUG
// Disable screen capture if capable and not explicitly allowed
if (osUtils->canPreventScreenCapture() && !parser.isSet(allowScreenCaptureOption)) {
// This ensures any top-level windows (Main Window, Modal Dialogs, etc.) are excluded from screenshots
QObject::connect(&app, &QGuiApplication::focusWindowChanged, &mainWindow, [&](QWindow* window) {
if (window) {
if (!osUtils->setPreventScreenCapture(window, true)) {
mainWindow.displayGlobalMessage(
QObject::tr("Warning: Failed to prevent screenshots on a top level window!"),
MessageWidget::Error);
}
}
});
}
#endif
const bool pwstdin = parser.isSet(pwstdinOption);
for (const QString& filename : fileNames) {
QString password;