From d49d400d604da686a0216d687c6bd3ac45d40e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Schoentgen?= Date: Mon, 1 May 2023 00:36:33 +0200 Subject: [PATCH] Mac: disable auto-scaling --- .github/workflows/tests.yml | 90 ++++++++++++++++++------------------- pyproject.toml | 1 + src/mss/darwin.py | 70 ++++++++++++++++++----------- src/tests/test_macos.py | 23 +++++++--- 4 files changed, 106 insertions(+), 78 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f46daa3..fafaad4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,38 +8,38 @@ on: workflow_dispatch: jobs: - quality: - name: Quality - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.x" - cache: pip - - name: Install dependencies - run: | - python -m pip install -U pip - python -m pip install -e '.[dev]' - - name: Tests - run: ./check.sh + # quality: + # name: Quality + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - uses: actions/setup-python@v4 + # with: + # python-version: "3.x" + # cache: pip + # - name: Install dependencies + # run: | + # python -m pip install -U pip + # python -m pip install -e '.[dev]' + # - name: Tests + # run: ./check.sh - documentation: - name: Documentation - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.x" - cache: pip - - name: Install test dependencies - run: | - python -m pip install -U pip - python -m pip install -e '.[test]' - - name: Tests - run: | - sphinx-build -d docs docs/source docs_out --color -W -bhtml + # documentation: + # name: Documentation + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - uses: actions/setup-python@v4 + # with: + # python-version: "3.x" + # cache: pip + # - name: Install test dependencies + # run: | + # python -m pip install -U pip + # python -m pip install -e '.[test]' + # - name: Tests + # run: | + # sphinx-build -d docs docs/source docs_out --color -W -bhtml tests: name: "${{ matrix.os.emoji }} ${{ matrix.python.name }}" @@ -48,25 +48,25 @@ jobs: fail-fast: false matrix: os: - - emoji: 🐧 - runs-on: [ubuntu-latest] + # - emoji: 🐧 + # runs-on: [ubuntu-latest] - emoji: 🍎 runs-on: [macos-latest] - - emoji: 🪟 - runs-on: [windows-latest] + # - emoji: 🪟 + # runs-on: [windows-latest] python: - name: CPython 3.8 runs-on: "3.8" - - name: CPython 3.9 - runs-on: "3.9" - - name: CPython 3.10 - runs-on: "3.10" - - name: CPython 3.11 - runs-on: "3.11" - - name: CPython 3.12 - runs-on: "3.12-dev" - - name: PyPy 3.9 - runs-on: "pypy-3.9" + # - name: CPython 3.9 + # runs-on: "3.9" + # - name: CPython 3.10 + # runs-on: "3.10" + # - name: CPython 3.11 + # runs-on: "3.11" + # - name: CPython 3.12 + # runs-on: "3.12-dev" + # - name: PyPy 3.9 + # runs-on: "pypy-3.9" steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/pyproject.toml b/pyproject.toml index c352b79..a89ce81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,7 @@ mss = "mss.__main__:main" [project.optional-dependencies] test = [ "numpy", + "pyautogui; sys_platform == 'darwin'", "pillow", "pytest", "pytest-cov", diff --git a/src/mss/darwin.py b/src/mss/darwin.py index 1dd37ea..47fc718 100644 --- a/src/mss/darwin.py +++ b/src/mss/darwin.py @@ -5,7 +5,7 @@ import ctypes import ctypes.util import sys -from ctypes import POINTER, Structure, c_double, c_float, c_int32, c_ubyte, c_uint32, c_uint64, c_void_p +from ctypes import POINTER, Structure, byref, c_bool, c_double, c_float, c_int32, c_ubyte, c_uint32, c_uint64, c_void_p from platform import mac_ver from typing import Any, Optional, Type, Union @@ -23,6 +23,19 @@ def cgfloat() -> Union[Type[c_double], Type[c_float]]: return c_double if sys.maxsize > 2**32 else c_float +class CGImage(Structure): + """Structure that contains information about a composite image.""" + + _fields_ = [ + ("isMask", c_bool), + ("width", c_uint32), + ("height", c_uint32), + ("bitsPerComponent", c_uint32), + ("bitsPerPixel", c_uint32), + ("bytesPerRow", c_uint32), + ] + + class CGPoint(Structure): """Structure that contains coordinates of a rectangle.""" @@ -64,19 +77,25 @@ def __repr__(self) -> str: "CGDisplayRotation": ("core", [c_uint32], c_float), "CFDataGetBytePtr": ("core", [c_void_p], c_void_p), "CFDataGetLength": ("core", [c_void_p], c_uint64), - "CFRelease": ("core", [c_void_p], c_void_p), - "CGDataProviderRelease": ("core", [c_void_p], c_void_p), + "CFRelease": ("core", [POINTER(CGImage)], c_void_p), + "CGDataProviderRelease": ("core", [POINTER(CGImage)], c_void_p), "CGGetActiveDisplayList": ("core", [c_uint32, POINTER(c_uint32), POINTER(c_uint32)], c_int32), - "CGImageGetBitsPerPixel": ("core", [c_void_p], int), - "CGImageGetBytesPerRow": ("core", [c_void_p], int), - "CGImageGetDataProvider": ("core", [c_void_p], c_void_p), - "CGImageGetHeight": ("core", [c_void_p], int), - "CGImageGetWidth": ("core", [c_void_p], int), + # "CGImageGetBitsPerPixel": ("core", [c_void_p], int), + # "CGImageGetBytesPerRow": ("core", [c_void_p], int), + "CGImageGetDataProvider": ("core", [POINTER(CGImage)], c_void_p), + # "CGImageGetHeight": ("core", [c_void_p], int), + # "CGImageGetWidth": ("core", [c_void_p], int), "CGRectStandardize": ("core", [CGRect], CGRect), "CGRectUnion": ("core", [CGRect, CGRect], CGRect), - "CGWindowListCreateImage": ("core", [CGRect, c_uint32, c_uint32, c_uint32], c_void_p), + "CGWindowListCreateImage": ("core", [CGRect, c_uint32, c_uint32, c_uint32], CGImage), } +_CORE = ( + ctypes.util.find_library("CoreGraphics") + if float(".".join(mac_ver()[0].split(".")[:2])) < 10.16 + else "/System/Library/Frameworks/CoreGraphics.framework/Versions/Current/CoreGraphics" +) + class MSS(MSSBase): """ @@ -98,16 +117,9 @@ def __init__(self, /, **kwargs: Any) -> None: def _init_library(self) -> None: """Load the CoreGraphics library.""" - version = float(".".join(mac_ver()[0].split(".")[:2])) - if version < 10.16: - coregraphics = ctypes.util.find_library("CoreGraphics") - else: - # macOS Big Sur and newer - coregraphics = "/System/Library/Frameworks/CoreGraphics.framework/Versions/Current/CoreGraphics" - - if not coregraphics: + if not _CORE: raise ScreenShotError("No CoreGraphics library found.") - self.core = ctypes.cdll.LoadLibrary(coregraphics) + self.core = ctypes.cdll.LoadLibrary(_CORE) def _set_cfunctions(self) -> None: """Set all ctypes functions and attach them to attributes.""" @@ -138,9 +150,11 @@ def _monitors_impl(self) -> None: rect = core.CGDisplayBounds(display) rect = core.CGRectStandardize(rect) width, height = rect.size.width, rect.size.height + + # {0.0: "normal", 90.0: "right", -90.0: "left"} if core.CGDisplayRotation(display) in {90.0, -90.0}: - # {0.0: "normal", 90.0: "right", -90.0: "left"} width, height = height, width + self._monitors.append( { "left": int_(rect.origin.x), @@ -169,15 +183,17 @@ def _grab_impl(self, monitor: Monitor, /) -> ScreenShot: core = self.core rect = CGRect((monitor["left"], monitor["top"]), (monitor["width"], monitor["height"])) - image_ref = core.CGWindowListCreateImage(rect, 1, 0, 0) - if not image_ref: + cg_image = core.CGWindowListCreateImage(rect, 1, 0, 0) + if not cg_image: raise ScreenShotError("CoreGraphics.CGWindowListCreateImage() failed.") - width = core.CGImageGetWidth(image_ref) - height = core.CGImageGetHeight(image_ref) + # width = core.CGImageGetWidth(cg_image) + # height = core.CGImageGetHeight(cg_image) + width = cg_image.width + height = cg_image.height prov = copy_data = None try: - prov = core.CGImageGetDataProvider(image_ref) + prov = core.CGImageGetDataProvider(POINTER(cg_image)) copy_data = core.CGDataProviderCopyData(prov) data_ref = core.CFDataGetBytePtr(copy_data) buf_len = core.CFDataGetLength(copy_data) @@ -185,8 +201,10 @@ def _grab_impl(self, monitor: Monitor, /) -> ScreenShot: data = bytearray(raw.contents) # Remove padding per row - bytes_per_row = core.CGImageGetBytesPerRow(image_ref) - bytes_per_pixel = core.CGImageGetBitsPerPixel(image_ref) + # bytes_per_row = core.CGImageGetBytesPerRow(cg_image) + # bytes_per_pixel = core.CGImageGetBitsPerPixel(cg_image) + bytes_per_row = cg_image.bytesPerRow + bytes_per_pixel = cg_image.bitsPerPixel bytes_per_pixel = (bytes_per_pixel + 7) // 8 if bytes_per_pixel * width != bytes_per_row: diff --git a/src/tests/test_macos.py b/src/tests/test_macos.py index 113b33e..2c5e86e 100644 --- a/src/tests/test_macos.py +++ b/src/tests/test_macos.py @@ -7,16 +7,15 @@ import pytest -import mss -from mss.exception import ScreenShotError - if platform.system().lower() != "darwin": pytestmark = pytest.mark.skip +from mss import mss +from mss.darwin import CGPoint, CGRect, CGSize +from mss.exception import ScreenShotError -def test_repr(): - from mss.darwin import CGPoint, CGRect, CGSize +def test_repr(): # CGPoint point = CGPoint(2.0, 1.0) ref = CGPoint() @@ -48,10 +47,10 @@ def test_implementation(monkeypatch): if version < 10.16: monkeypatch.setattr(ctypes.util, "find_library", lambda x: None) with pytest.raises(ScreenShotError): - mss.mss() + mss() monkeypatch.undo() - with mss.mss() as sct: + with mss() as sct: # Test monitor's rotation original = sct.monitors[1] monkeypatch.setattr(sct.core, "CGDisplayRotation", lambda x: -90.0) @@ -65,3 +64,13 @@ def test_implementation(monkeypatch): monkeypatch.setattr(sct.core, "CGWindowListCreateImage", lambda *args: None) with pytest.raises(ScreenShotError): sct.grab(sct.monitors[1]) + + +def test_no_auto_scaling(): + pyautogui = pytest.importorskip("pyautogui") + + w, h = pyautogui.size() + with mss() as sct: + monitor = {"top": 0, "left": 0, "width": w, "height": h} + img = sct.grab(monitor) + assert (w, h) == (img.width, img.height)