python-mss: Unjustified Screenshot error after closing a Tkinter Toplevel (Linux)

General information:

  • OS name: Ubuntu Linux
  • OS version: 22.04
  • OS architecture: 64 bits
  • Resolutions:
    • Monitor 1: 3440x1440 (issue occurs at all resolutions)
  • Python version: 3.10.6
  • MSS version: 6.1.0 & 7.0.1

For GNU/Linux users:

  • Display server protocol and version, if known: X11 server
  • Desktop Environment: ubuntu:GNOME
  • Composite Window Manager name and version: X.Org v: 1.21.1.3, compositor: gnome-shell v: 42.5

Description of the warning/error

The issue (and solution) is described in detail here

In summary, the following error occurs when trying to instantiate an mss object after opening/closing a Tkinter Toplevel window. This error does not occur on Windows.

Full message

  File "/home/my_project/src/utilities/geometry.py", line 64, in screenshot
    with mss.mss() as sct:
  File "/home/my_project/env/lib/python3.10/site-packages/mss/factory.py", line 34, in mss
    return linux.MSS(**kwargs)
  File "/home/my_project/env/lib/python3.10/site-packages/mss/linux.py", line 297, in __init__
    self.root = self.xlib.XDefaultRootWindow(self._get_display(display))
  File "/home/my_project/env/lib/python3.10/site-packages/mss/linux.py", line 184, in validate
    raise ScreenShotError(f"{func.__name__}() failed", details=details)
mss.exception.ScreenShotError: XDefaultRootWindow() failed

Screenshot error: XDefaultRootWindow() failed, {'retval': <mss.linux.LP_XWindowAttributes object at 0x7f31d8d38ac0>, 
    'args': (<mss.linux.LP_Display object at 0x7f31d8d38740>,)}

Other details

The following code can be used to reproduce the error. Simply run this program and use the GUI button to open a new window that allows you to take a screenshot - then close the pop-up window and attempt to repeat the process.

import tkinter as tk

import mss
import mss.tools


def take_screenshot():
   with mss.mss() as sct:
       screen_part = {"top": 370, "left": 1090, "width": 80, "height": 390}
       sct_img = sct.grab(screen_part)
       mss.tools.to_png(sct_img.rgb, sct_img.size, output="./output.png")

def create_top_level_win():
   top_level_win = tk.Toplevel(root)

   take_screenshot_btn = tk.Button(top_level_win, text="Take screenshot", command=take_screenshot)
   take_screenshot_btn.pack()

root = tk.Tk()

btn = tk.Button(root, text="Open TopLevel", command=create_top_level_win)
btn.pack()

root.mainloop()

A temporary workaround is to reuse a single mss object throughout the application, like so:

sct = mss.mss()

def take_screenshot():
    global sct
    screen_part = {"top": 370, "left": 1090, "width": 80, "height": 390}
    sct_img = sct.grab(screen_part)
    mss.tools.to_png(sct_img.rgb, sct_img.size, output="./output.png")

On StackOverflow, one answer suggests that the bug is due to “the current implementation of MSS changing the current X11 error handler and leaving it afterwards, which causes a conflict with Tcl/Tk (the backend of Python Tkinter), see here for details”

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 28 (21 by maintainers)

Commits related to this issue

Most upvoted comments

IMHO, the current fix has a potential of another side effect, because the previous X11 error handler is not restored. The previous error handler needs to be backed up in the MSS.__init__(). (The XSetErrorHandler() returns the previous error handler.) And the backed up handler needs to be restored in the MSS.close().

Looks like it’s resolved! Thanks for checking this out!

If #224 may not fix the issue, it will likely fix another issue regarding that any X11 error would not be cleared, and so subsequent legitimate calls to any X11 function would fail. I refactored that part, and I think it will help in your case, and your tests will be critical 😃

You should just use the version of MSS from the master branch because I merged the PR.