Coverage for core / dpi_awareness.py: 47.4%

19 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-12 04:49 +0000

1""" 

2core.dpi_awareness — single source of truth for Windows per-monitor DPI awareness. 

3 

4Why this module exists: 

5 Without explicit DPI awareness on Windows, the OS returns LOGICAL coords 

6 from the high-level APIs (pyautogui.size, EnumDisplayMonitors, 

7 GetWindowRect) but PHYSICAL pixels from low-level capture 

8 (pyautogui.screenshot uses BitBlt of the desktop DC at physical res). 

9 

10 On a 150%-scaled 2560x1440 display: 

11 - pyautogui.size() → (1707, 960) (logical) 

12 - pyautogui.screenshot() → 2560x1440 (physical) 

13 - EnumDisplayMonitors → (0,0,1707,960) (logical) 

14 - VLM grounds coordinates in IMAGE space (physical) → caller scales 

15 to (logical) for pyautogui.click → click misses by ~1.5x. 

16 

17 Setting DPI awareness once per process makes every API return PHYSICAL 

18 coords consistently. Idempotent: calling twice with the same value is 

19 a no-op; calling with a different value silently fails (so existing 

20 DPI-aware processes aren't disturbed). 

21 

22Why a dedicated module: 

23 Two call sites needed it (integrations/vlm/local_computer_tool.py for 

24 screenshot/click DPI, integrations/remote_desktop/window_capture.py for 

25 EnumDisplayMonitors / GetWindowRect). A second copy was added in the 

26 Phase 1 VLM commit (693ccad7) and immediately flagged as a DRY 

27 violation. Promoting here so there is exactly ONE place that knows 

28 about SetProcessDpiAwareness and the (Win 8.1+ shcore vs Win 7 user32) 

29 branch, and exactly ONE place to update if Microsoft adds a new tier 

30 (e.g. PER_MONITOR_DPI_AWARE_V2 already exists at value 4). 

31 

32Use: 

33 from core.dpi_awareness import ensure_dpi_aware 

34 ensure_dpi_aware() # safe at module-load OR lazy first-call 

35 

36 # Optional: query whether we successfully set it 

37 from core.dpi_awareness import is_dpi_aware 

38 if is_dpi_aware(): 

39 ... 

40""" 

41import logging 

42import sys 

43 

44logger = logging.getLogger('hevolve.dpi_awareness') 

45 

46# PROCESS_PER_MONITOR_DPI_AWARE — value passed to SetProcessDpiAwareness. 

47# Win 8.1+ accepts 0 (UNAWARE), 1 (SYSTEM_DPI_AWARE), 2 (PER_MONITOR_DPI_AWARE). 

48# Win 10 1607+ also has SetProcessDpiAwarenessContext for V2 (DPI scale changes 

49# without process restart) but value 2 is the most portable + sufficient for 

50# VLM screenshot/click coordinate consistency. 

51_PER_MONITOR_DPI_AWARE = 2 

52 

53_dpi_aware_set: bool = False 

54 

55 

56def ensure_dpi_aware() -> None: 

57 """Make this process DPI-aware on Windows. No-op on macOS / Linux. 

58 

59 Idempotent and crash-proof: any failure is logged at debug level and 

60 swallowed. Safe to call from module-load AND from lazy-first-call 

61 paths (the second call is a Win32 no-op when awareness is already set). 

62 """ 

63 global _dpi_aware_set 

64 if sys.platform != 'win32': 

65 return 

66 if _dpi_aware_set: 

67 return 

68 try: 

69 import ctypes 

70 try: 

71 ctypes.windll.shcore.SetProcessDpiAwareness(_PER_MONITOR_DPI_AWARE) 

72 except (AttributeError, OSError): 

73 # Pre-Win-8.1: shcore.dll missing or function absent. Fall back 

74 # to the older system-wide DPI awareness API. Better than 

75 # nothing; coords stay consistent within a single monitor at 

76 # the cost of multi-monitor coord weirdness on mixed-DPI setups. 

77 ctypes.windll.user32.SetProcessDPIAware() 

78 except Exception as e: 

79 # Don't blow up the importer of any module that calls this — log 

80 # and continue. Worst case: VLM clicks land slightly off, which 

81 # the loop's verify-and-retry path will detect. 

82 logger.debug(f"DPI awareness setup skipped: {e}") 

83 return 

84 _dpi_aware_set = True 

85 

86 

87def is_dpi_aware() -> bool: 

88 """True if :func:`ensure_dpi_aware` has successfully run. 

89 

90 Note: returns False on non-Windows (where the concept doesn't apply) 

91 AND when the call ran but failed (logged at debug). Callers that 

92 need to know "are coordinates physical?" should use this OR explicit 

93 platform checks. 

94 """ 

95 return _dpi_aware_set