Skip to content

Commit f2cf83d

Browse files
committed
MSS: refactor ctypes argtype, restype and errcheck setup (BoboTiG#84)
1 parent f7dbc35 commit f2cf83d

File tree

9 files changed

+318
-292
lines changed

9 files changed

+318
-292
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ History:
55
4.0.0 201x/xx/xx
66
- MSS: remove use of setup.py for setup.cfg
77
- MSS: renamed MSSBase to MSSMixin in base.py
8+
- MSS: refactor ctypes argtype, restype and errcheck setup (fixes #84)
89
- Linux: ensure resources are freed in grab()
910
- Windows: avoid unecessary class attributes
1011
- MSS: ensure calls without context manager will not leak resources or document them (fixes #72 and #85)

CHANGES.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ base.py
77

88
linux.py
99
--------
10-
- Renamed ``__del__()`` method to ``close()``
10+
- Renamed ``MSS.__del__()`` method to ``MSS.close()``
11+
- Deleted ``MSS.last_error`` attribute. Use ``LAST_ERROR`` constant instead.
12+
- Added ``validate()`` function
13+
- Added ``MSS.get_error_details()`` method
1114

1215
windows.py
1316
----------
14-
- Renamed ``__exit__()`` method to ``close()``
17+
- Renamed ``MSS.__exit__()`` method to ``MSS.close()``
1518

1619

1720
3.3.0 (2018-09-04)

docs/source/api.rst

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ GNU/Linux
1010

1111
.. module:: mss.linux
1212

13+
.. attribute:: LAST_ERROR
14+
15+
:type: dict[str, Any]
16+
17+
Contains the latest Xlib or XRANDR function.
18+
1319
.. class:: MSS
1420

1521
.. method:: __init__([display=None])
@@ -25,6 +31,33 @@ GNU/Linux
2531

2632
.. versionadded:: 4.0.0
2733

34+
.. method:: get_error_details()
35+
36+
:rtype: Optional[dict[str, Any]]
37+
38+
Get more information about the latest X server error. To use in such scenario::
39+
40+
with mss.mss() as sct:
41+
# Take a screenshot of a region out of monitor bounds
42+
rect = {"left": -30, "top": 0, "width": 100, "height": 100}
43+
44+
try:
45+
sct.grab(rect)
46+
except ScreenShotError:
47+
details = sct.get_error_details()
48+
"""
49+
>>> import pprint
50+
>>> pprint.pprint(details)
51+
{'xerror': 'BadFont (invalid Font parameter)',
52+
'xerror_details': {'error_code': 7,
53+
'minor_code': 0,
54+
'request_code': 0,
55+
'serial': 422,
56+
'type': 0}}
57+
"""
58+
59+
.. versionadded:: 4.0.0
60+
2861
.. method:: grab(monitor)
2962

3063
:rtype: :class:`mss.base.ScreenShot`
@@ -43,7 +76,7 @@ GNU/Linux
4376
Error handler passed to `X11.XSetErrorHandler()` to catch any error that can happen when calling a X11 function.
4477
This will prevent Python interpreter crashes.
4578

46-
When such an error happen, a :class:`mss.exception.ScreenShotError` exception is raised and all XError information are added to the :attr:`mss.exception.ScreenShotError.details` attribute.
79+
When such an error happen, a :class:`mss.exception.ScreenShotError` exception is raised and all `XError` information are added to the :attr:`mss.exception.ScreenShotError.details` attribute.
4780

4881
.. versionadded:: 3.3.0
4982

@@ -173,9 +206,7 @@ Methods
173206
Properties
174207
==========
175208

176-
.. module:: mss.base
177-
178-
.. class:: MSSMixin
209+
.. class:: mss.base.MSSMixin
179210

180211
.. attribute:: monitors
181212

@@ -198,7 +229,7 @@ Properties
198229

199230
:rtype: list[dict[str, int]]
200231

201-
.. class:: ScreenShot
232+
.. class:: mss.base.ScreenShot
202233

203234
.. attribute:: __array_interface__()
204235

mss/base.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ def close(self):
3333
# type: () -> None
3434
""" Clean-up. """
3535

36-
pass
37-
3836
def grab(self, monitor):
3937
# type: (Dict[str, int]) -> ScreenShot
4038
"""
@@ -140,3 +138,15 @@ def shot(self, **kwargs):
140138

141139
kwargs["mon"] = kwargs.get("mon", 1)
142140
return next(self.save(**kwargs))
141+
142+
@staticmethod
143+
def _cfactory(attr, func, argtypes, restype, errcheck=None):
144+
# type: (Any, str, List[Any], Any, Optional[Callable]) -> None
145+
# pylint: disable=too-many-locals
146+
""" Factory to create a ctypes function and automatically manage errors. """
147+
148+
meth = getattr(attr, func)
149+
meth.argtypes = argtypes
150+
meth.restype = restype
151+
if errcheck:
152+
meth.errcheck = errcheck

mss/darwin.py

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -74,59 +74,46 @@ def __init__(self):
7474
raise ScreenShotError("No CoreGraphics library found.")
7575
self.core = ctypes.cdll.LoadLibrary(coregraphics)
7676

77-
self._set_argtypes()
78-
self._set_restypes()
79-
80-
def _set_argtypes(self):
81-
# type: () -> None
82-
""" Functions arguments. """
83-
84-
self.core.CGGetActiveDisplayList.argtypes = [
85-
ctypes.c_uint32,
86-
ctypes.POINTER(ctypes.c_uint32),
87-
ctypes.POINTER(ctypes.c_uint32),
88-
]
89-
self.core.CGDisplayBounds.argtypes = [ctypes.c_uint32]
90-
self.core.CGRectStandardize.argtypes = [CGRect]
91-
self.core.CGRectUnion.argtypes = [CGRect, CGRect]
92-
self.core.CGDisplayRotation.argtypes = [ctypes.c_uint32]
93-
self.core.CGWindowListCreateImage.argtypes = [
94-
CGRect,
95-
ctypes.c_uint32,
96-
ctypes.c_uint32,
97-
ctypes.c_uint32,
98-
]
99-
self.core.CGImageGetWidth.argtypes = [ctypes.c_void_p]
100-
self.core.CGImageGetHeight.argtypes = [ctypes.c_void_p]
101-
self.core.CGImageGetDataProvider.argtypes = [ctypes.c_void_p]
102-
self.core.CGDataProviderCopyData.argtypes = [ctypes.c_void_p]
103-
self.core.CFDataGetBytePtr.argtypes = [ctypes.c_void_p]
104-
self.core.CFDataGetLength.argtypes = [ctypes.c_void_p]
105-
self.core.CGImageGetBytesPerRow.argtypes = [ctypes.c_void_p]
106-
self.core.CGImageGetBitsPerPixel.argtypes = [ctypes.c_void_p]
107-
self.core.CGDataProviderRelease.argtypes = [ctypes.c_void_p]
108-
self.core.CFRelease.argtypes = [ctypes.c_void_p]
109-
110-
def _set_restypes(self):
111-
# type: () -> None
112-
""" Functions return type. """
113-
114-
self.core.CGGetActiveDisplayList.restype = ctypes.c_int32
115-
self.core.CGDisplayBounds.restype = CGRect
116-
self.core.CGRectStandardize.restype = CGRect
117-
self.core.CGRectUnion.restype = CGRect
118-
self.core.CGDisplayRotation.restype = ctypes.c_float
119-
self.core.CGWindowListCreateImage.restype = ctypes.c_void_p
120-
self.core.CGImageGetWidth.restype = ctypes.c_size_t
121-
self.core.CGImageGetHeight.restype = ctypes.c_size_t
122-
self.core.CGImageGetDataProvider.restype = ctypes.c_void_p
123-
self.core.CGDataProviderCopyData.restype = ctypes.c_void_p
124-
self.core.CFDataGetBytePtr.restype = ctypes.c_void_p
125-
self.core.CFDataGetLength.restype = ctypes.c_uint64
126-
self.core.CGImageGetBytesPerRow.restype = ctypes.c_size_t
127-
self.core.CGImageGetBitsPerPixel.restype = ctypes.c_size_t
128-
self.core.CGDataProviderRelease.restype = ctypes.c_void_p
129-
self.core.CFRelease.restype = ctypes.c_void_p
77+
self._set_cfunctions()
78+
79+
def _set_cfunctions(self):
80+
""" Set all ctypes functions and attach them to attributes. """
81+
82+
def cfactory(attr=self.core, func=None, argtypes=None, restype=None):
83+
# type: (Any, str, List[Any], Any) -> None
84+
# pylint: disable=too-many-locals
85+
""" Factorize ctypes creations. """
86+
self._cfactory(attr=attr, func=func, argtypes=argtypes, restype=restype)
87+
88+
uint32 = ctypes.c_uint32
89+
void = ctypes.c_void_p
90+
size_t = ctypes.c_size_t
91+
pointer = ctypes.POINTER
92+
93+
cfactory(
94+
func="CGGetActiveDisplayList",
95+
argtypes=[uint32, pointer(uint32), pointer(uint32)],
96+
restype=ctypes.c_int32,
97+
)
98+
cfactory(func="CGDisplayBounds", argtypes=[uint32], restype=CGRect)
99+
cfactory(func="CGRectStandardize", argtypes=[CGRect], restype=CGRect)
100+
cfactory(func="CGRectUnion", argtypes=[CGRect, CGRect], restype=CGRect)
101+
cfactory(func="CGDisplayRotation", argtypes=[uint32], restype=ctypes.c_float)
102+
cfactory(
103+
func="CGWindowListCreateImage",
104+
argtypes=[CGRect, uint32, uint32, uint32],
105+
restype=void,
106+
)
107+
cfactory(func="CGImageGetWidth", argtypes=[void], restype=size_t)
108+
cfactory(func="CGImageGetHeight", argtypes=[void], restype=size_t)
109+
cfactory(func="CGImageGetDataProvider", argtypes=[void], restype=void)
110+
cfactory(func="CGDataProviderCopyData", argtypes=[void], restype=void)
111+
cfactory(func="CFDataGetBytePtr", argtypes=[void], restype=void)
112+
cfactory(func="CFDataGetLength", argtypes=[void], restype=ctypes.c_uint64)
113+
cfactory(func="CGImageGetBytesPerRow", argtypes=[void], restype=size_t)
114+
cfactory(func="CGImageGetBitsPerPixel", argtypes=[void], restype=size_t)
115+
cfactory(func="CGDataProviderRelease", argtypes=[void], restype=void)
116+
cfactory(func="CFRelease", argtypes=[void], restype=void)
130117

131118
@property
132119
def monitors(self):

mss/factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def mss(**kwargs):
2121
instantiation.
2222
"""
2323

24+
# pylint: disable=import-error
25+
2426
operating_system = platform.system().lower()
2527
if operating_system == "darwin":
2628
from .darwin import MSS

0 commit comments

Comments
 (0)