Merge pull request #9898 from f321x/windows_disable_screenshot
qt: windows: protect against screenshots and screen recordings
This commit is contained in:
@@ -78,7 +78,8 @@ from electrum import constants
|
|||||||
from electrum.gui.common_qt.i18n import ElectrumTranslator
|
from electrum.gui.common_qt.i18n import ElectrumTranslator
|
||||||
from electrum.gui.messages import TERMS_OF_USE_LATEST_VERSION
|
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 .main_window import ElectrumWindow
|
||||||
from .network_dialog import NetworkDialog
|
from .network_dialog import NetworkDialog
|
||||||
from .stylesheet_patcher import patch_qt_stylesheet
|
from .stylesheet_patcher import patch_qt_stylesheet
|
||||||
@@ -105,6 +106,20 @@ class OpenFileEventFilter(QObject):
|
|||||||
return False
|
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):
|
class QElectrumApplication(QApplication):
|
||||||
new_window_signal = pyqtSignal(str, object)
|
new_window_signal = pyqtSignal(str, object)
|
||||||
quit_signal = pyqtSignal()
|
quit_signal = pyqtSignal()
|
||||||
@@ -136,9 +151,12 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
|||||||
QGuiApplication.setApplicationName("Electrum")
|
QGuiApplication.setApplicationName("Electrum")
|
||||||
self.gui_thread = threading.current_thread()
|
self.gui_thread = threading.current_thread()
|
||||||
self.windows = [] # type: List[ElectrumWindow]
|
self.windows = [] # type: List[ElectrumWindow]
|
||||||
self.efilter = OpenFileEventFilter(self.windows)
|
self.open_file_efilter = OpenFileEventFilter(self.windows)
|
||||||
self.app = QElectrumApplication(sys.argv)
|
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.app.setWindowIcon(read_QIcon("electrum.png"))
|
||||||
self.translator = ElectrumTranslator()
|
self.translator = ElectrumTranslator()
|
||||||
self.app.installTranslator(self.translator)
|
self.app.installTranslator(self.translator)
|
||||||
@@ -247,8 +265,11 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
|||||||
return
|
return
|
||||||
self._cleaned_up = True
|
self._cleaned_up = True
|
||||||
self.app.new_window_signal.disconnect()
|
self.app.new_window_signal.disconnect()
|
||||||
self.app.removeEventFilter(self.efilter)
|
self.app.removeEventFilter(self.open_file_efilter)
|
||||||
self.efilter = None
|
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.
|
# If there are still some open windows, try to clean them up.
|
||||||
for window in list(self.windows):
|
for window in list(self.windows):
|
||||||
window.close()
|
window.close()
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
import sys
|
||||||
from typing import TYPE_CHECKING, Dict
|
from typing import TYPE_CHECKING, Dict
|
||||||
|
|
||||||
from PyQt6.QtCore import Qt
|
from PyQt6.QtCore import Qt
|
||||||
@@ -262,6 +263,20 @@ class SettingsDialog(QDialog, QtEventListener):
|
|||||||
self.need_restart = True
|
self.need_restart = True
|
||||||
filelogging_cb.stateChanged.connect(on_set_filelogging)
|
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_explorers = sorted(util.block_explorer_info().keys())
|
||||||
BLOCK_EX_CUSTOM_ITEM = _("Custom URL")
|
BLOCK_EX_CUSTOM_ITEM = _("Custom URL")
|
||||||
if BLOCK_EX_CUSTOM_ITEM in block_explorers: # malicious translation?
|
if BLOCK_EX_CUSTOM_ITEM in block_explorers: # malicious translation?
|
||||||
@@ -386,6 +401,7 @@ class SettingsDialog(QDialog, QtEventListener):
|
|||||||
misc_widgets = []
|
misc_widgets = []
|
||||||
misc_widgets.append((updatecheck_cb, None))
|
misc_widgets.append((updatecheck_cb, None))
|
||||||
misc_widgets.append((filelogging_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((alias_label, self.alias_e))
|
||||||
misc_widgets.append((qr_label, qr_combo))
|
misc_widgets.append((qr_label, qr_combo))
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import platform
|
|||||||
import queue
|
import queue
|
||||||
import os
|
import os
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
import ctypes
|
||||||
from functools import partial, lru_cache, wraps
|
from functools import partial, lru_cache, wraps
|
||||||
from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Sequence, Tuple, Union)
|
from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, List, Any, Sequence, Tuple, Union)
|
||||||
|
|
||||||
@@ -1532,6 +1533,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))
|
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 _ABCQObjectMeta(type(QObject), ABCMeta): pass
|
||||||
class _ABCQWidgetMeta(type(QWidget), ABCMeta): pass
|
class _ABCQWidgetMeta(type(QWidget), ABCMeta): pass
|
||||||
class AbstractQObject(QObject, ABC, metaclass=_ABCQObjectMeta): pass
|
class AbstractQObject(QObject, ABC, metaclass=_ABCQObjectMeta): pass
|
||||||
|
|||||||
@@ -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_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_CONSOLE = ConfigVar('show_console_tab', default=False, type_=bool)
|
||||||
GUI_QT_SHOW_TAB_NOTES = ConfigVar('show_notes_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_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)
|
GUI_QML_USER_KNOWS_PRESS_AND_HOLD = ConfigVar('user_knows_press_and_hold', default=False, type_=bool)
|
||||||
|
|||||||
Reference in New Issue
Block a user