1
0

windows: protect against screenshots and screen recordings

This commit is contained in:
f321x
2025-06-03 15:29:13 +02:00
parent e3ccee6d63
commit ca9c4777d8
4 changed files with 66 additions and 5 deletions

View File

@@ -78,7 +78,8 @@ from electrum import constants
from electrum.gui.common_qt.i18n import ElectrumTranslator
from electrum.gui.messages import TERMS_OF_USE_LATEST_VERSION
from .util import read_QIcon, ColorScheme, custom_message_box, MessageBoxMixin, WWLabel
from .util import (read_QIcon, ColorScheme, custom_message_box, MessageBoxMixin, WWLabel,
set_windows_os_screenshot_protection_drm_flag)
from .main_window import ElectrumWindow
from .network_dialog import NetworkDialog
from .stylesheet_patcher import patch_qt_stylesheet
@@ -105,6 +106,20 @@ class OpenFileEventFilter(QObject):
return False
class ScreenshotProtectionEventFilter(QObject):
def __init__(self):
super().__init__()
def eventFilter(self, obj, event):
if (
event.type() == QtCore.QEvent.Type.Show
and isinstance(obj, QWidget)
and obj.isWindow()
):
set_windows_os_screenshot_protection_drm_flag(obj)
return False
class QElectrumApplication(QApplication):
new_window_signal = pyqtSignal(str, object)
quit_signal = pyqtSignal()
@@ -136,9 +151,12 @@ class ElectrumGui(BaseElectrumGui, Logger):
QGuiApplication.setApplicationName("Electrum")
self.gui_thread = threading.current_thread()
self.windows = [] # type: List[ElectrumWindow]
self.efilter = OpenFileEventFilter(self.windows)
self.open_file_efilter = OpenFileEventFilter(self.windows)
self.app = QElectrumApplication(sys.argv)
self.app.installEventFilter(self.efilter)
self.app.installEventFilter(self.open_file_efilter)
self.screenshot_protection_efilter = ScreenshotProtectionEventFilter()
if sys.platform in ['win32', 'windows'] and self.config.GUI_QT_SCREENSHOT_PROTECTION:
self.app.installEventFilter(self.screenshot_protection_efilter)
self.app.setWindowIcon(read_QIcon("electrum.png"))
self.translator = ElectrumTranslator()
self.app.installTranslator(self.translator)
@@ -247,8 +265,11 @@ class ElectrumGui(BaseElectrumGui, Logger):
return
self._cleaned_up = True
self.app.new_window_signal.disconnect()
self.app.removeEventFilter(self.efilter)
self.efilter = None
self.app.removeEventFilter(self.open_file_efilter)
self.open_file_efilter = None
# it is save to remove the filter, even if it has not been installed
self.app.removeEventFilter(self.screenshot_protection_efilter)
self.screenshot_protection_efilter = None
# If there are still some open windows, try to clean them up.
for window in list(self.windows):
window.close()

View File

@@ -24,6 +24,7 @@
# SOFTWARE.
import ast
import sys
from typing import TYPE_CHECKING, Dict
from PyQt6.QtCore import Qt
@@ -262,6 +263,20 @@ class SettingsDialog(QDialog, QtEventListener):
self.need_restart = True
filelogging_cb.stateChanged.connect(on_set_filelogging)
screenshot_protection_cb = checkbox_from_configvar(
self.config.cv.GUI_QT_SCREENSHOT_PROTECTION
)
screenshot_protection_cb.setChecked(self.config.GUI_QT_SCREENSHOT_PROTECTION)
if sys.platform not in ['windows', 'win32']:
screenshot_protection_cb.setChecked(False)
screenshot_protection_cb.setDisabled(True)
screenshot_protection_cb.setToolTip(_("This option is only available on Windows"))
def on_set_screenshot_protection(_x):
self.config.GUI_QT_SCREENSHOT_PROTECTION = screenshot_protection_cb.isChecked()
self.need_restart = True
screenshot_protection_cb.stateChanged.connect(on_set_screenshot_protection)
block_explorers = sorted(util.block_explorer_info().keys())
BLOCK_EX_CUSTOM_ITEM = _("Custom URL")
if BLOCK_EX_CUSTOM_ITEM in block_explorers: # malicious translation?
@@ -386,6 +401,7 @@ class SettingsDialog(QDialog, QtEventListener):
misc_widgets = []
misc_widgets.append((updatecheck_cb, None))
misc_widgets.append((filelogging_cb, None))
misc_widgets.append((screenshot_protection_cb, None))
misc_widgets.append((alias_label, self.alias_e))
misc_widgets.append((qr_label, qr_combo))

View File

@@ -6,6 +6,7 @@ import platform
import queue
import os
import webbrowser
import ctypes
from functools import partial, lru_cache, wraps
from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Sequence, Tuple, Union)
@@ -1531,6 +1532,21 @@ def insert_spaces(text: str, every_chars: int) -> str:
return ' '.join(text[i:i+every_chars] for i in range(0, len(text), every_chars))
def set_windows_os_screenshot_protection_drm_flag(window: QWidget) -> None:
"""
sets the windows WDA_MONITOR flag on the window so windows prevents capturing
screenshots and microsoft recall will not be able to record the window
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity
"""
if sys.platform not in ('win32', 'windows'):
return
try:
window_id = int(window.winId())
WDA_MONITOR = 0x01
ctypes.windll.user32.SetWindowDisplayAffinity(window_id, WDA_MONITOR)
except Exception:
_logger.exception(f"failed to set windows screenshot protection flag")
class _ABCQObjectMeta(type(QObject), ABCMeta): pass
class _ABCQWidgetMeta(type(QWidget), ABCMeta): pass
class AbstractQObject(QObject, ABC, metaclass=_ABCQObjectMeta): pass

View File

@@ -800,6 +800,14 @@ Warning: setting this to too low will result in lots of payment failures."""),
GUI_QT_SHOW_TAB_CONTACTS = ConfigVar('show_contacts_tab', default=False, type_=bool)
GUI_QT_SHOW_TAB_CONSOLE = ConfigVar('show_console_tab', default=False, type_=bool)
GUI_QT_SHOW_TAB_NOTES = ConfigVar('show_notes_tab', default=False, type_=bool)
GUI_QT_SCREENSHOT_PROTECTION = ConfigVar(
'screenshot_protection', default=True, type_=bool,
short_desc=lambda: _("Prevent screenshots"),
# currently this option is Windows only, so the description can be specific to Windows
long_desc=lambda: _(
'Signals Windows to disallow recordings and screenshots of the application window. '
'There is no guarantee Windows will respect this signal.'),
)
GUI_QML_PREFERRED_REQUEST_TYPE = ConfigVar('preferred_request_type', default='bolt11', type_=str)
GUI_QML_USER_KNOWS_PRESS_AND_HOLD = ConfigVar('user_knows_press_and_hold', default=False, type_=bool)