Skip to content

Commit f2c32c9

Browse files
authored
fix: object release in macOS (#453)
* Fix object release in macOS Previously, we would release the image data provider, but not the image. We actually need to release the image, and not the data provider. This is based on the CoreFoundation "Create/Copy/Get" ownership rules: we own things that we allocate with functions with Create or Copy in their name, but not things that have Get in their name. I suspect that the CGImage held a reference to its data provider, which stored the pixel data. When we freed the data provider, we released the pixel data, but not the CGImage structure. Now, we're releasing the CGImage, and it releases the data provider. Empirical testing while taking 10000 successive screenshots showed that, once steady state was reached, the previous version was leaking an average of about 467 bytes per screenshot on average. The new version still leaks about 148 bytes per screenshot, although that may not be related to macOS. Also fixed a few function types, but those were harmless. * Remove unneeded function declaration, and add CHANGELOG entry
1 parent 201f31b commit f2c32c9

File tree

2 files changed

+23
-13
lines changed

2 files changed

+23
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ See Git checking messages for full history.
88
- Linux: introduce an XCB-powered backend stack with a factory in ``mss.linux`` while keeping the Xlib code as a fallback (#425)
99
- Linux: add the XShmGetImage backend with automatic XGetImage fallback and explicit status reporting (#431)
1010
- Windows: improve error checking and messages for Win32 API calls (#448)
11+
- Mac: fix memory leak (#450, #453)
1112
- :heart: contributors: @jholveck
1213

1314
## 10.1.0 (2025-08-16)

src/mss/darwin.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,18 @@
99
import ctypes
1010
import ctypes.util
1111
import sys
12-
from ctypes import POINTER, Structure, c_double, c_float, c_int32, c_ubyte, c_uint32, c_uint64, c_void_p
12+
from ctypes import (
13+
POINTER,
14+
Structure,
15+
c_double,
16+
c_float,
17+
c_int32,
18+
c_long,
19+
c_size_t,
20+
c_ubyte,
21+
c_uint32,
22+
c_void_p,
23+
)
1324
from platform import mac_ver
1425
from typing import TYPE_CHECKING, Any
1526

@@ -75,17 +86,16 @@ def __repr__(self) -> str:
7586
# Syntax: cfunction: (attr, argtypes, restype)
7687
"CGDataProviderCopyData": ("core", [c_void_p], c_void_p),
7788
"CGDisplayBounds": ("core", [c_uint32], CGRect),
78-
"CGDisplayRotation": ("core", [c_uint32], c_float),
79-
"CFDataGetBytePtr": ("core", [c_void_p], c_void_p),
80-
"CFDataGetLength": ("core", [c_void_p], c_uint64),
81-
"CFRelease": ("core", [c_void_p], c_void_p),
82-
"CGDataProviderRelease": ("core", [c_void_p], c_void_p),
89+
"CGDisplayRotation": ("core", [c_uint32], c_double),
90+
"CFDataGetBytePtr": ("core", [c_void_p], POINTER(c_ubyte)),
91+
"CFDataGetLength": ("core", [c_void_p], c_long),
92+
"CFRelease": ("core", [c_void_p], None),
8393
"CGGetActiveDisplayList": ("core", [c_uint32, POINTER(c_uint32), POINTER(c_uint32)], c_int32),
84-
"CGImageGetBitsPerPixel": ("core", [c_void_p], int),
85-
"CGImageGetBytesPerRow": ("core", [c_void_p], int),
94+
"CGImageGetBitsPerPixel": ("core", [c_void_p], c_size_t),
95+
"CGImageGetBytesPerRow": ("core", [c_void_p], c_size_t),
8696
"CGImageGetDataProvider": ("core", [c_void_p], c_void_p),
87-
"CGImageGetHeight": ("core", [c_void_p], int),
88-
"CGImageGetWidth": ("core", [c_void_p], int),
97+
"CGImageGetHeight": ("core", [c_void_p], c_size_t),
98+
"CGImageGetWidth": ("core", [c_void_p], c_size_t),
8999
"CGRectStandardize": ("core", [CGRect], CGRect),
90100
"CGRectUnion": ("core", [CGRect, CGRect], CGRect),
91101
"CGWindowListCreateImage": ("core", [CGRect, c_uint32, c_uint32, c_uint32], c_void_p),
@@ -197,7 +207,7 @@ def _grab_impl(self, monitor: Monitor, /) -> ScreenShot:
197207

198208
width = core.CGImageGetWidth(image_ref)
199209
height = core.CGImageGetHeight(image_ref)
200-
prov = copy_data = None
210+
copy_data = None
201211
try:
202212
prov = core.CGImageGetDataProvider(image_ref)
203213
copy_data = core.CGDataProviderCopyData(prov)
@@ -219,10 +229,9 @@ def _grab_impl(self, monitor: Monitor, /) -> ScreenShot:
219229
cropped.extend(data[start:end])
220230
data = cropped
221231
finally:
222-
if prov:
223-
core.CGDataProviderRelease(prov)
224232
if copy_data:
225233
core.CFRelease(copy_data)
234+
core.CFRelease(image_ref)
226235

227236
return self.cls_image(data, monitor, size=Size(width, height))
228237

0 commit comments

Comments
 (0)