diff --git a/src/gui/osutils/OSUtilsBase.cpp b/src/gui/osutils/OSUtilsBase.cpp index 143cb72c1..a8395bb74 100644 --- a/src/gui/osutils/OSUtilsBase.cpp +++ b/src/gui/osutils/OSUtilsBase.cpp @@ -25,3 +25,9 @@ OSUtilsBase::OSUtilsBase(QObject* parent) OSUtilsBase::~OSUtilsBase() { } + +bool OSUtilsBase::setPreventScreenCapture(QWindow*, bool) const +{ + // Do nothing by default + return false; +} diff --git a/src/gui/osutils/OSUtilsBase.h b/src/gui/osutils/OSUtilsBase.h index 5b97cd505..00ad6f0cf 100644 --- a/src/gui/osutils/OSUtilsBase.h +++ b/src/gui/osutils/OSUtilsBase.h @@ -21,6 +21,8 @@ #include #include +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); diff --git a/src/gui/osutils/macutils/AppKit.h b/src/gui/osutils/macutils/AppKit.h index 9ce4c01db..309c05b90 100644 --- a/src/gui/osutils/macutils/AppKit.h +++ b/src/gui/osutils/macutils/AppKit.h @@ -23,6 +23,8 @@ #include #include +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(); diff --git a/src/gui/osutils/macutils/AppKitImpl.h b/src/gui/osutils/macutils/AppKitImpl.h index 5e7a2fbae..c03fc2948 100644 --- a/src/gui/osutils/macutils/AppKitImpl.h +++ b/src/gui/osutils/macutils/AppKitImpl.h @@ -20,6 +20,7 @@ #import #import +#import @interface AppKitImpl : NSObject { @@ -40,5 +41,6 @@ - (bool) enableAccessibility; - (bool) enableScreenRecording; - (void) toggleForegroundApp:(bool) foreground; +- (void) setWindowSecurity:(NSWindow*) window state:(bool) state; @end diff --git a/src/gui/osutils/macutils/AppKitImpl.mm b/src/gui/osutils/macutils/AppKitImpl.mm index faf061106..35465e7cb 100644 --- a/src/gui/osutils/macutils/AppKitImpl.mm +++ b/src/gui/osutils/macutils/AppKitImpl.mm @@ -18,11 +18,14 @@ #import "AppKitImpl.h" #include "AppKit.h" +#include #import #import #import #import +#import +#import #import @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(self) toggleForegroundApp:foreground]; } + +void AppKit::setWindowSecurity(QWindow* window, bool state) +{ + auto view = reinterpret_cast(window->winId()); + [static_cast(self) setWindowSecurity:view.window state:state]; +} diff --git a/src/gui/osutils/macutils/MacUtils.cpp b/src/gui/osutils/macutils/MacUtils.cpp index 9e85024d0..a3128ea5c 100644 --- a/src/gui/osutils/macutils/MacUtils.cpp +++ b/src/gui/osutils/macutils/MacUtils.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -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; diff --git a/src/gui/osutils/macutils/MacUtils.h b/src/gui/osutils/macutils/MacUtils.h index 717be27db..1281aa072 100644 --- a/src/gui/osutils/macutils/MacUtils.h +++ b/src/gui/osutils/macutils/MacUtils.h @@ -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(); diff --git a/src/gui/osutils/nixutils/NixUtils.h b/src/gui/osutils/nixutils/NixUtils.h index bd659e210..92b83fcf8 100644 --- a/src/gui/osutils/nixutils/NixUtils.h +++ b/src/gui/osutils/nixutils/NixUtils.h @@ -43,6 +43,11 @@ public: QString* error = nullptr) override; bool unregisterGlobalShortcut(const QString& name) override; + bool canPreventScreenCapture() const override + { + return false; + } + signals: void keymapChanged(); diff --git a/src/gui/osutils/winutils/WinUtils.cpp b/src/gui/osutils/winutils/WinUtils.cpp index 1ccade5f5..ce44e71d3 100644 --- a/src/gui/osutils/winutils/WinUtils.cpp +++ b/src/gui/osutils/winutils/WinUtils.cpp @@ -16,12 +16,14 @@ */ #include "WinUtils.h" -#include + #include #include #include +#include -#include +#include +#undef MessageBox QPointer 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(window->winId()); + return SetWindowDisplayAffinity(handle, prevent ? WDA_EXCLUDEFROMCAPTURE : WDA_NONE); + } + return false; +} + /** * Register event filters to handle native platform events such as global hotkeys */ diff --git a/src/gui/osutils/winutils/WinUtils.h b/src/gui/osutils/winutils/WinUtils.h index b7ef47573..a4522e08b 100644 --- a/src/gui/osutils/winutils/WinUtils.h +++ b/src/gui/osutils/winutils/WinUtils.h @@ -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; diff --git a/src/main.cpp b/src/main.cpp index ebbd7dc22..80884562e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #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 @@ -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;