Skip to content

Commit bc5095e

Browse files
Mickaël SchoentgenBoboTiG
authored andcommitted
Mac: Better AiO monitor handling
1 parent 4bc23de commit bc5095e

4 files changed

Lines changed: 98 additions & 23 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ before_install:
1717
- xpra --xvfb="Xorg +extension RANDR -config `pwd`/tests/res/dummy.xorg.conf -logfile ${HOME}/.xpra/xorg.log" start :42
1818

1919
install:
20-
- pip install flake8 numpy pylint
20+
- pip install flake8 numpy pillow pylint
2121
- python setup.py install
2222

2323
script:

CHANGES.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
3.1.0 (2017-xx-xx)
2+
==================
3+
4+
darwin.py
5+
---------
6+
- Removed `get_infinity()` function
7+
8+
19
3.0.0 (2017-07-06)
210
==================
311

mss/darwin.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,35 @@ def cgfloat():
2626
return ctypes.c_double if sys.maxsize > 2 ** 32 else ctypes.c_float
2727

2828

29-
def get_infinity(maxi=False):
30-
# type: (bool) -> float
31-
""" Get infinity "numbers". """
32-
33-
return 1.7976931348623157e+308 if maxi else -8.988465674311579e+307
34-
35-
3629
class CGPoint(ctypes.Structure):
3730
""" Structure that contains coordinates of a rectangle. """
3831

3932
_fields_ = [('x', cgfloat()), ('y', cgfloat())]
4033

34+
def __repr__(self):
35+
return '{0}(left={cls.x} top={cls.y})'.format(
36+
type(self).__name__, cls=self)
37+
4138

4239
class CGSize(ctypes.Structure):
4340
""" Structure that contains dimensions of an rectangle. """
4441

4542
_fields_ = [('width', cgfloat()), ('height', cgfloat())]
4643

44+
def __repr__(self):
45+
return '{0}(width={cls.width} height={cls.height})'.format(
46+
type(self).__name__, cls=self)
47+
4748

4849
class CGRect(ctypes.Structure):
4950
""" Structure that contains informations about a rectangle. """
5051

5152
_fields_ = [('origin', CGPoint), ('size', CGSize)]
5253

54+
def __repr__(self):
55+
return '{0}<{cls.origin} {cls.size}>'.format(
56+
type(self).__name__, cls=self)
57+
5358

5459
class MSS(MSSBase):
5560
"""
@@ -81,6 +86,7 @@ def _set_argtypes(self):
8186
ctypes.POINTER(ctypes.c_uint32)]
8287
self.core.CGDisplayBounds.argtypes = [ctypes.c_uint32]
8388
self.core.CGRectStandardize.argtypes = [CGRect]
89+
self.core.CGRectUnion.argtypes = [CGRect, CGRect]
8490
self.core.CGDisplayRotation.argtypes = [ctypes.c_uint32]
8591
self.core.CGWindowListCreateImage.argtypes = [
8692
CGRect,
@@ -100,6 +106,7 @@ def _set_restypes(self):
100106
self.core.CGGetActiveDisplayList.restype = ctypes.c_int32
101107
self.core.CGDisplayBounds.restype = CGRect
102108
self.core.CGRectStandardize.restype = CGRect
109+
self.core.CGRectUnion.restype = CGRect
103110
self.core.CGDisplayRotation.restype = ctypes.c_float
104111
self.core.CGWindowListCreateImage.restype = ctypes.c_void_p
105112
self.core.CGImageGetDataProvider.restype = ctypes.c_void_p
@@ -115,12 +122,10 @@ def monitors(self):
115122

116123
if not self._monitors:
117124
# All monitors
118-
self._monitors.append({
119-
'left': int(get_infinity()),
120-
'top': int(get_infinity()),
121-
'width': int(get_infinity(True)),
122-
'height': int(get_infinity(True)),
123-
})
125+
# We need to update the value with every single monitor found
126+
# using CGRectUnion. Else we will end with infinite values.
127+
all_monitors = CGRect()
128+
self._monitors.append({})
124129

125130
# Each monitors
126131
display_count = ctypes.c_uint32(0)
@@ -133,18 +138,28 @@ def monitors(self):
133138
display = active_displays[idx]
134139
rect = self.core.CGDisplayBounds(display)
135140
rect = self.core.CGRectStandardize(rect)
136-
left, top = rect.origin.x, rect.origin.y
137141
width, height = rect.size.width, rect.size.height
138142
rot = self.core.CGDisplayRotation(display)
139143
if rotations[rot] in ['left', 'right']:
140144
width, height = height, width
141145
self._monitors.append({
142-
'left': int(left),
143-
'top': int(top),
146+
'left': int(rect.origin.x),
147+
'top': int(rect.origin.y),
144148
'width': int(width),
145149
'height': int(height),
146150
})
147151

152+
# Update AiO monitor's values
153+
all_monitors = self.core.CGRectUnion(all_monitors, rect)
154+
155+
# Update AiO monitor's values
156+
self._monitors[0] = {
157+
'left': int(all_monitors.origin.x),
158+
'top': int(all_monitors.origin.y),
159+
'width': int(all_monitors.size.width),
160+
'height': int(all_monitors.size.height),
161+
}
162+
148163
return self._monitors
149164

150165
def grab(self, monitor):
@@ -178,10 +193,6 @@ def grab(self, monitor):
178193
if rounded_width != monitor['width']:
179194
data = self.resize(data, monitor)
180195

181-
if len(data) != monitor['width'] * monitor['height'] * 4:
182-
del data
183-
raise ScreenShotError('Data length mismatch.', locals())
184-
185196
return self.cls_image(data, monitor)
186197

187198
@staticmethod

tests/test_implementation.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,33 @@ def test_repr(sct):
7171
assert repr(img) == repr(ref)
7272

7373

74+
@pytest.mark.skipif(
75+
platform.system().lower() != 'darwin',
76+
reason='Specific to macOS.')
77+
def test_repr():
78+
from mss.darwin import CGSize, CGPoint, CGRect
79+
80+
point = CGPoint(2.0, 1.0)
81+
ref = CGPoint()
82+
ref.x = 2.0
83+
ref.y = 1.0
84+
assert repr(point) == repr(ref)
85+
86+
size = CGSize(2.0, 1.0)
87+
ref = CGSize()
88+
ref.width = 2.0
89+
ref.height = 1.0
90+
assert repr(size) == repr(ref)
91+
92+
rect = CGRect(point, size)
93+
ref = CGRect()
94+
ref.origin.x = 2.0
95+
ref.origin.y = 1.0
96+
ref.size.width = 2.0
97+
ref.size.height = 1.0
98+
assert repr(rect) == repr(ref)
99+
100+
74101
@pytest.mark.skipif(
75102
numpy is None,
76103
reason='Numpy module not available.')
@@ -113,7 +140,7 @@ def test_factory_basics(monkeypatch):
113140

114141
@pytest.mark.skipif(
115142
platform.system().lower() != 'linux',
116-
reason='To hard to maintain the test for all platforms.')
143+
reason='Too hard to maintain the test for all platforms.')
117144
def test_factory_systems(monkeypatch):
118145
""" Here, we are testing all systems. """
119146

@@ -142,7 +169,7 @@ def test_python_call():
142169

143170
@pytest.mark.skipif(
144171
platform.system().lower() != 'linux',
145-
reason='GNU/Linux test only.')
172+
reason='Specific to GNU/Linux.')
146173
def test_gnu_linux(monkeypatch):
147174
text = str if sys.version[0] > '2' else unicode
148175

@@ -188,3 +215,32 @@ def find_lib(lib):
188215
import mss.linux
189216
monkeypatch.setattr(mss.linux, 'Display', lambda: None)
190217
mss.mss()
218+
219+
220+
@pytest.mark.skipif(
221+
platform.system().lower() != 'darwin',
222+
reason='Specific to macOS.')
223+
def test_macos(monkeypatch):
224+
# No `CoreGraphics` library
225+
monkeypatch.setattr(ctypes.util, 'find_library', lambda x: None)
226+
with pytest.raises(ScreenShotError):
227+
mss.mss()
228+
monkeypatch.undo()
229+
230+
with mss.mss() as sct:
231+
# Test monitor's rotation
232+
original = sct.monitors[1]
233+
monkeypatch.setattr(sct.core, 'CGDisplayRotation',
234+
lambda x: -90.0)
235+
sct._monitors = []
236+
modified = sct.monitors[1]
237+
assert original['width'] == modified['height']
238+
assert original['height'] == modified['width']
239+
monkeypatch.undo()
240+
241+
# Test bad data retreival
242+
monkeypatch.setattr(sct.core, 'CGWindowListCreateImage',
243+
lambda *args: None)
244+
with pytest.raises(ScreenShotError):
245+
sct.grab(sct.monitors[1])
246+
monkeypatch.undo()

0 commit comments

Comments
 (0)