Open GeorgeWashingtonABCDEFG opened 2 years ago
Here's something related:
"""
QtWarningMsg:
setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) failed:
COM error 0x5 (Access is denied.)
"""
import pyautogui # noqa # pylint:disable=unused-import
from PySide6.QtWidgets import QApplication
QApplication()
It outputs the warning
qt.qpa.windows: setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) failed: COM error 0x5 (Access is denied.)
According to MS docs,
Possible errors are [..] ERROR_ACCESS_DENIED if the default API awareness mode for the process has already been set (via a previous API call or within the application manifest).
https://learn.microsoft.com/th-th/windows/win32/api/winuser/nf-winuser-setprocessdpiawarenesscontext
So it seems pyautogui
sets DPI awareness somewhere before. I find it in https://github.com/asweigart/pyautogui/blob/ad7609c6c277831e5fb5809272139f7db8ccdbeb/pyautogui/_pyautogui_win.py#L14-L18
I am considering monkey-patching ctypes
before importing pyautogui
to remove that call.
Here's a workaround:
"""Workaround."""
import ctypes
import pytest
from PySide6.QtWidgets import QApplication
with pytest.MonkeyPatch.context() as mp:
mp.setattr(ctypes.windll.user32, "SetProcessDPIAware", lambda: None)
import pyautogui # noqa # pylint:disable=unused-import
QApplication()
Much nicer workaround with the standard library:
"""Workaround."""
from unittest.mock import patch
from PySide6.QtWidgets import QApplication
with patch("ctypes.windll.user32.SetProcessDPIAware", autospec=True):
import pyautogui # noqa # pylint:disable=unused-import
QApplication()
Importantly, that Qt warning message will go away: https://bugreports.qt.io/browse/PYSIDE-2105
This will solve my issue (and make my patch pointless), but it will not solve @GeorgeWashingtonABCDEFG's issue (for which my patch may still be useful).
In the end, pyautogui
should probably stop calling SetProcessDPIAware
unless necessary (and if so, call it as late as possible).
Much nicer workaround with the standard library:
"""Workaround.""" from unittest.mock import patch from PySide6.QtWidgets import QApplication with patch("ctypes.windll.user32.SetProcessDPIAware", autospec=True): import pyautogui # noqa # pylint:disable=unused-import QApplication()
The unittest workaround significantly increases build time, boot time and build size with PyInstaller. So I went with some sort of postinstall powershell script that patches all offending libraries, and uninstalls those I don't actually need.
# Prevent pyautogui and pywinctl from setting Process DPI Awareness, which Qt tries to do then throws warnings about it.
# The unittest workaround significantly increases build time, boot time and build size with PyInstaller.
# https://github.com/asweigart/pyautogui/issues/663#issuecomment-1296719464
$libPath = python -c 'import pyautogui as _; print(_.__path__[0])'
(Get-Content "$libPath/_pyautogui_win.py").replace('ctypes.windll.user32.SetProcessDPIAware()', 'pass') |
Set-Content "$libPath/_pyautogui_win.py"
$libPath = python -c 'import pymonctl as _; print(_.__path__[0])'
(Get-Content "$libPath/_pymonctl_win.py").replace('ctypes.windll.shcore.SetProcessDpiAwareness', 'pass # ') |
Set-Content "$libPath/_pymonctl_win.py"
$libPath = python -c 'import pywinbox as _; print(_.__path__[0])'
(Get-Content "$libPath/_pywinbox_win.py").replace('ctypes.windll.shcore.SetProcessDpiAwareness', 'pass # ') |
Set-Content "$libPath/_pywinbox_win.py"
# Uninstall optional dependencies that PyInstaller picks up
python -m pip uninstall pyscreeze mouseinfo pyperclip -y
To find where it's called in the first place, I simply added the following at the top of my main/entrypoint module:
# flake8: noqa
def find_culprit(*_):
raise Exception
import ctypes
ctypes.windll.shcore.SetProcessDpiAwareness = find_culprit
ctypes.windll.user32.SetProcessDPIAware = find_culprit
I think I found an even better workaround. This is what I'm using now. If you did need SetProcessDPIAware
or SetProcessDpiAwareness
yourself, just assign it to a variable first.
import sys
# Prevent PyAutoGUI and pywinctl from setting Process DPI Awareness, which Qt tries to do then throws warnings about it.
# The unittest workaround significantly increases build time, boot time and build size with PyInstaller.
# https://github.com/asweigart/pyautogui/issues/663#issuecomment-1296719464
# QT doesn't call those from Python/ctypes, meaning we can stop other programs from setting it.
if sys.platform == "win32":
import ctypes
# pyautogui._pyautogui_win.py
ctypes.windll.user32.SetProcessDPIAware = lambda: None # pyright: ignore[reportAttributeAccessIssue]
# pymonctl._pymonctl_win.py
# pywinbox._pywinbox_win.py
ctypes.windll.shcore.SetProcessDpiAwareness = ( # pyright: ignore[reportAttributeAccessIssue]
lambda _: None # pyright: ignore[reportUnknownLambdaType]
)