Skip to content

Commit 2115e08

Browse files
authored
Linux: removed get_error_details(), use the ScreenShotError details attribute instead (#224)
It should fix threading issues, and prevent old errors messing with new X11 calls.
1 parent a116dd1 commit 2115e08

File tree

8 files changed

+51
-86
lines changed

8 files changed

+51
-86
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
if: matrix.os.emoji == '🐧'
8484
run: |
8585
export DISPLAY=:99
86-
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
86+
sudo Xvfb -ac ${DISPLAY} -screen 0 1280x1024x24 > /dev/null 2>&1 &
8787
python -m pytest
8888
- name: Tests on other platforms
8989
if: matrix.os.emoji != '🐧'

CHANGELOG

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ History:
22

33
<see Git checking messages for history>
44

5-
7.0.2 2023/0x/xx
5+
8.0.0 2023/0x/xx
6+
- Linux: removed get_error_details(), use the ScreenShotError details attribute instead
67
- dev: removed pre-commit
78
- tests: removed tox
89
- tests: added PyPy 3.9

docs/source/api.rst

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,6 @@ GNU/Linux
1919

2020
.. attribute:: CFUNCTIONS
2121

22-
.. attribute:: ERROR
23-
24-
:type: types.SimpleNamspacedict
25-
26-
The `details` attribute contains the latest Xlib or XRANDR function. It is a dict.
27-
28-
.. versionadded:: 5.0.0
29-
3022
.. attribute:: PLAINMASK
3123

3224
.. attribute:: ZPIXMAP
@@ -40,33 +32,6 @@ GNU/Linux
4032

4133
GNU/Linux initializations.
4234

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

7237
:rtype: :class:`~mss.base.ScreenShot`

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
# built documents.
2828
#
2929
# The short X.Y version.
30-
version = "7.0.1"
30+
version = "8.0.0"
3131

3232
# The full version, including alpha/beta/rc tags.
3333
release = "latest"

mss/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .exception import ScreenShotError
1212
from .factory import mss
1313

14-
__version__ = "7.0.1"
14+
__version__ = "8.0.0"
1515
__author__ = "Mickaël 'Tiger-222' Schoentgen"
1616
__copyright__ = """
1717
Copyright (c) 2013-2023, Mickaël 'Tiger-222' Schoentgen

mss/linux.py

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
c_ushort,
2323
c_void_p,
2424
)
25-
from types import SimpleNamespace
2625
from typing import Any, Dict, Optional, Tuple, Union
2726

2827
from .base import MSSBase, lock
@@ -33,7 +32,6 @@
3332
__all__ = ("MSS",)
3433

3534

36-
ERROR = SimpleNamespace(details=None)
3735
PLAINMASK = 0x00FFFFFF
3836
ZPIXMAP = 2
3937

@@ -158,29 +156,46 @@ class XRRCrtcInfo(Structure):
158156
]
159157

160158

159+
_ERROR = {}
160+
161+
161162
@CFUNCTYPE(c_int, POINTER(Display), POINTER(Event))
162-
def error_handler(_: Any, event: Any) -> int:
163+
def error_handler(display: Display, event: Event) -> int:
163164
"""Specifies the program's supplied error handler."""
165+
x11 = ctypes.util.find_library("X11")
166+
if not x11:
167+
return 0
168+
169+
# Get the specific error message
170+
xlib = ctypes.cdll.LoadLibrary(x11)
171+
get_error = getattr(xlib, "XGetErrorText")
172+
get_error.argtypes = [POINTER(Display), c_int, c_char_p, c_int]
173+
get_error.restype = c_void_p
174+
164175
evt = event.contents
165-
ERROR.details = {
166-
"type": evt.type,
167-
"serial": evt.serial,
176+
error = ctypes.create_string_buffer(1024)
177+
get_error(display, evt.error_code, error, len(error))
178+
179+
_ERROR[threading.current_thread()] = {
180+
"error": error.value.decode("utf-8"),
168181
"error_code": evt.error_code,
169-
"request_code": evt.request_code,
170182
"minor_code": evt.minor_code,
183+
"request_code": evt.request_code,
184+
"serial": evt.serial,
185+
"type": evt.type,
171186
}
187+
172188
return 0
173189

174190

175-
def validate(
176-
retval: int, func: Any, args: Tuple[Any, Any]
177-
) -> Optional[Tuple[Any, Any]]:
191+
def validate(retval: int, func: Any, args: Tuple[Any, Any]) -> Tuple[Any, Any]:
178192
"""Validate the returned value of a Xlib or XRANDR function."""
179193

180-
if retval != 0 and not ERROR.details:
194+
current_thread = threading.current_thread()
195+
if retval != 0 and current_thread not in _ERROR:
181196
return args
182197

183-
details = {"retval": retval, "args": args}
198+
details = _ERROR.pop(current_thread, {})
184199
raise ScreenShotError(f"{func.__name__}() failed", details=details)
185200

186201

@@ -196,7 +211,6 @@ def validate(
196211
CFUNCTIONS: CFunctions = {
197212
"XDefaultRootWindow": ("xlib", [POINTER(Display)], POINTER(XWindowAttributes)),
198213
"XDestroyImage": ("xlib", [POINTER(XImage)], c_void_p),
199-
"XGetErrorText": ("xlib", [POINTER(Display), c_int, c_char_p, c_int], c_void_p),
200214
"XGetImage": (
201215
"xlib",
202216
[
@@ -331,15 +345,13 @@ def _get_display(self, disp: Optional[bytes] = None) -> int:
331345
Since the current thread and main thread are always alive, reuse their
332346
*display* value first.
333347
"""
334-
cur_thread, main_thread = threading.current_thread(), threading.main_thread()
335-
current_display = MSS._display_dict.get(cur_thread) or MSS._display_dict.get(
336-
main_thread
337-
)
348+
current_thread = threading.current_thread()
349+
current_display = MSS._display_dict.get(current_thread)
338350
if current_display:
339351
display = current_display
340352
else:
341353
display = self.xlib.XOpenDisplay(disp)
342-
MSS._display_dict[cur_thread] = display
354+
MSS._display_dict[current_thread] = display
343355
return display
344356

345357
def _set_cfunctions(self) -> None:
@@ -360,27 +372,6 @@ def _set_cfunctions(self) -> None:
360372
restype=restype,
361373
)
362374

363-
def get_error_details(self) -> Optional[Dict[str, Any]]:
364-
"""Get more information about the latest X server error."""
365-
366-
details: Dict[str, Any] = {}
367-
368-
if ERROR.details:
369-
details = {"xerror_details": ERROR.details}
370-
ERROR.details = None
371-
xserver_error = ctypes.create_string_buffer(1024)
372-
self.xlib.XGetErrorText(
373-
self._get_display(),
374-
details.get("xerror_details", {}).get("error_code", 0),
375-
xserver_error,
376-
len(xserver_error),
377-
)
378-
xerror = xserver_error.value.decode("utf-8")
379-
if xerror != "0":
380-
details["xerror"] = xerror
381-
382-
return details
383-
384375
def _monitors_impl(self) -> None:
385376
"""Get positions of monitors. It will populate self._monitors."""
386377

mss/tests/test_gnu_linux.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010

1111
import mss
12+
import mss.linux
1213
from mss.base import MSSBase
1314
from mss.exception import ScreenShotError
1415

@@ -107,18 +108,23 @@ def find_lib_mocked(lib):
107108

108109
def test_region_out_of_monitor_bounds():
109110
display = os.getenv("DISPLAY")
111+
monitor = {"left": -30, "top": 0, "width": 100, "height": 100}
112+
113+
assert not mss.linux._ERROR
114+
110115
with mss.mss(display=display) as sct:
111116
with pytest.raises(ScreenShotError) as exc:
112-
monitor = {"left": -30, "top": 0, "width": 100, "height": 100}
113-
assert sct.grab(monitor)
117+
sct.grab(monitor)
114118

115119
assert str(exc.value)
116-
assert "retval" in exc.value.details
117-
assert "args" in exc.value.details
118120

119-
details = sct.get_error_details()
120-
assert details["xerror"]
121-
assert isinstance(details["xerror_details"], dict)
121+
details = exc.value.details
122+
assert details
123+
assert isinstance(details, dict)
124+
assert isinstance(details["error"], str)
125+
assert not mss.linux._ERROR
126+
127+
assert not mss.linux._ERROR
122128

123129

124130
def test_has_extension():

setup.cfg

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = mss
3-
version = 7.0.1
3+
version = 8.0.0
44
author = Mickaël 'Tiger-222' Schoentgen
55
author_email = [email protected]
66
description = An ultra fast cross-platform multiple screenshots module in pure python using ctypes.
@@ -20,6 +20,8 @@ platforms = Darwin, Linux, Windows
2020
classifiers =
2121
Development Status :: 5 - Production/Stable
2222
License :: OSI Approved :: MIT License
23+
Programming Language :: Python :: Implementation :: CPython
24+
Programming Language :: Python :: Implementation :: PyPy
2325
Programming Language :: Python
2426
Programming Language :: Python :: 3
2527
Programming Language :: Python :: 3 :: Only

0 commit comments

Comments
 (0)