6
6
import ctypes
7
7
import ctypes .util
8
8
import os
9
+ import threading
9
10
from types import SimpleNamespace
10
11
from typing import TYPE_CHECKING
11
12
@@ -187,6 +188,12 @@ class MSS(MSSBase):
187
188
# Instancied one time to prevent resource leak.
188
189
display = None
189
190
191
+ # A dict to maintain *display* values created by multiple threads.
192
+ _display_dict = {} # type: Dict[threading.Thread, int]
193
+
194
+ # A threading lock to lock resources.
195
+ _lock = threading .Lock ()
196
+
190
197
def __init__ (self , display = None ):
191
198
# type: (Optional[Union[bytes, str]]) -> None
192
199
""" GNU/Linux initialisations. """
@@ -221,14 +228,29 @@ def __init__(self, display=None):
221
228
222
229
self ._set_cfunctions ()
223
230
224
- if not MSS .display :
225
- MSS .display = self .xlib .XOpenDisplay (display )
226
- self .root = self .xlib .XDefaultRootWindow (MSS .display )
231
+ self .root = self .xlib .XDefaultRootWindow (self ._get_display (display ))
227
232
228
233
# Fix for XRRGetScreenResources and XGetImage:
229
234
# expected LP_Display instance instead of LP_XWindowAttributes
230
235
self .drawable = ctypes .cast (self .root , ctypes .POINTER (Display ))
231
236
237
+ def _get_display (self , disp = None ):
238
+ """
239
+ Retrieve a thread-safe display from XOpenDisplay().
240
+ In multithreading, if the thread who creates *display* is dead, *display* will
241
+ no longer be valid to grab the screen. The *display* attribute is replaced
242
+ with *_display_dict* to maintain the *display* values in multithreading.
243
+ Since the current thread and main thread are always alive, reuse their
244
+ *display* value first.
245
+ """
246
+ cur_thread , main_thread = threading .current_thread (), threading .main_thread ()
247
+ display = MSS ._display_dict .get (cur_thread ) or MSS ._display_dict .get (
248
+ main_thread
249
+ )
250
+ if not display :
251
+ display = MSS ._display_dict [cur_thread ] = self .xlib .XOpenDisplay (disp )
252
+ return display
253
+
232
254
def _set_cfunctions (self ):
233
255
"""
234
256
Set all ctypes functions and attach them to attributes.
@@ -324,7 +346,7 @@ def get_error_details(self):
324
346
ERROR .details = None
325
347
xserver_error = ctypes .create_string_buffer (1024 )
326
348
self .xlib .XGetErrorText (
327
- MSS . display ,
349
+ self . _get_display () ,
328
350
details .get ("xerror_details" , {}).get ("error_code" , 0 ),
329
351
xserver_error ,
330
352
len (xserver_error ),
@@ -335,53 +357,66 @@ def get_error_details(self):
335
357
336
358
return details
337
359
338
- @property
339
- def monitors (self ):
340
- # type: () -> Monitors
341
- """ Get positions of monitors (see parent class property). """
360
+ def _monitors_impl (self ):
361
+ # type: () -> None
362
+ """
363
+ Get positions of monitors (has to be run using a threading lock).
364
+ It will populate self._monitors.
365
+ """
342
366
343
- if not self ._monitors :
344
- display = MSS .display
345
- int_ = int
346
- xrandr = self .xrandr
367
+ display = self ._get_display ()
368
+ int_ = int
369
+ xrandr = self .xrandr
370
+
371
+ # All monitors
372
+ gwa = XWindowAttributes ()
373
+ self .xlib .XGetWindowAttributes (display , self .root , ctypes .byref (gwa ))
374
+ self ._monitors .append (
375
+ {
376
+ "left" : int_ (gwa .x ),
377
+ "top" : int_ (gwa .y ),
378
+ "width" : int_ (gwa .width ),
379
+ "height" : int_ (gwa .height ),
380
+ }
381
+ )
382
+
383
+ # Each monitors
384
+ mon = xrandr .XRRGetScreenResourcesCurrent (display , self .drawable ).contents
385
+ crtcs = mon .crtcs
386
+ for idx in range (mon .ncrtc ):
387
+ crtc = xrandr .XRRGetCrtcInfo (display , mon , crtcs [idx ]).contents
388
+ if crtc .noutput == 0 :
389
+ xrandr .XRRFreeCrtcInfo (crtc )
390
+ continue
347
391
348
- # All monitors
349
- gwa = XWindowAttributes ()
350
- self .xlib .XGetWindowAttributes (display , self .root , ctypes .byref (gwa ))
351
392
self ._monitors .append (
352
393
{
353
- "left" : int_ (gwa .x ),
354
- "top" : int_ (gwa .y ),
355
- "width" : int_ (gwa .width ),
356
- "height" : int_ (gwa .height ),
394
+ "left" : int_ (crtc .x ),
395
+ "top" : int_ (crtc .y ),
396
+ "width" : int_ (crtc .width ),
397
+ "height" : int_ (crtc .height ),
357
398
}
358
399
)
400
+ xrandr .XRRFreeCrtcInfo (crtc )
401
+ xrandr .XRRFreeScreenResources (mon )
359
402
360
- # Each monitors
361
- mon = xrandr .XRRGetScreenResourcesCurrent (display , self .drawable ).contents
362
- crtcs = mon .crtcs
363
- for idx in range (mon .ncrtc ):
364
- crtc = xrandr .XRRGetCrtcInfo (display , mon , crtcs [idx ]).contents
365
- if crtc .noutput == 0 :
366
- xrandr .XRRFreeCrtcInfo (crtc )
367
- continue
368
-
369
- self ._monitors .append (
370
- {
371
- "left" : int_ (crtc .x ),
372
- "top" : int_ (crtc .y ),
373
- "width" : int_ (crtc .width ),
374
- "height" : int_ (crtc .height ),
375
- }
376
- )
377
- xrandr .XRRFreeCrtcInfo (crtc )
378
- xrandr .XRRFreeScreenResources (mon )
403
+ @property
404
+ def monitors (self ):
405
+ # type: () -> Monitors
406
+ """ Get positions of monitors (see parent class property). """
407
+
408
+ if not self ._monitors :
409
+ with MSS ._lock :
410
+ self ._monitors_impl ()
379
411
380
412
return self ._monitors
381
413
382
- def grab (self , monitor ):
414
+ def _grab_impl (self , monitor ):
383
415
# type: (Monitor) -> ScreenShot
384
- """ Retrieve all pixels from a monitor. Pixels have to be RGB. """
416
+ """
417
+ Retrieve all pixels from a monitor. Pixels have to be RGB.
418
+ That method has to be run using a threading lock.
419
+ """
385
420
386
421
# Convert PIL bbox style
387
422
if isinstance (monitor , tuple ):
@@ -393,7 +428,7 @@ def grab(self, monitor):
393
428
}
394
429
395
430
ximage = self .xlib .XGetImage (
396
- MSS . display ,
431
+ self . _get_display () ,
397
432
self .drawable ,
398
433
monitor ["left" ],
399
434
monitor ["top" ],
@@ -424,3 +459,10 @@ def grab(self, monitor):
424
459
self .xlib .XDestroyImage (ximage )
425
460
426
461
return self .cls_image (data , monitor )
462
+
463
+ def grab (self , monitor ):
464
+ # type: (Monitor) -> ScreenShot
465
+ """ Retrieve all pixels from a monitor. Pixels have to be RGB. """
466
+
467
+ with MSS ._lock :
468
+ return self ._grab_impl (monitor )
0 commit comments