I wrote a screenshot program in GTK+-3 using the GDK library, it mostly works but I find sometimes the program crashes randomly with X11 errors. I realized that the crashes are triggered if a window is opened and closed instantly, and I was able to write the following program to reproduce the issue.
from time import sleep
from gi.repository import GLib, Gtk, Gdk
# (Open and select a window before the program starts.)
print("Open your window and focus on it.")
for i in range(5):
print("Program starts in %d seconds" % (5 - i))
sleep(1)
# Get an active window.
screen = Gdk.Screen.get_default()
window = Gdk.Screen.get_active_window(screen)
def loop():
# Close the window in the middle of this callback.
print(window.get_geometry()) # Crash
return True
GLib.timeout_add(100, loop)
Gtk.main()
If I a open a window before loop()
starts executing, and then suddenly close the window in the middle of loop()
, the program crashes with a X11 error.
Gdk-ERROR **: 00:13:54.693: The program 'test.py' received an X Window System error.
This probably reflects a bug in the program.
The error was 'BadDrawable (invalid Pixmap or Window parameter)'.
(Details: serial 175 error_code 9 request_code 14 (core protocol) minor_code 0)
(Note to programmers: normally, X errors are reported asynchronously;
that is, you will receive the error a while after causing it.
To debug your program, run it with the GDK_SYNCHRONIZE environment
variable to change this behavior. You can then get a meaningful
backtrace from your debugger if you break on the gdk_x_error() function.)
Obviously, it crashes because the underlying X11 window behind this GDKWindow already disappears before get_geometry()
. But how do I ensure that a GDKWindow is still valid? I tried using is_destroyed()
, process_all_updates()
and flush()
, but none had any effect.
Solved: As the duplicate question showed, X is an asynchronous protocol, so it's simply impossible to ensure a window exists. Instead, what we should do is installing an exception handler to catch the errors from X after they occured. In GDK, exception handling can be implemented by gdk_x11_display_error_trap_push()
(start ignoring and recording exceptions) and gdk_x11_display_error_trap_pop()
(returns an error code if there was one).
The correct program is something like the following.
from time import sleep
from gi.repository import GLib, Gtk, Gdk, GdkX11
# (Open and select a window before the program starts.)
print("Open your window and focus on it.")
for i in range(5):
print("Program starts in %d seconds" % (5 - i))
sleep(1)
# Get an active window.
screen = Gdk.Screen.get_default()
window = Gdk.Screen.get_active_window(screen)
def loop():
# Close the window in the middle of this callback.
# ignore and catch all errors at this point.
display = GdkX11.X11Display.get_default()
GdkX11.X11Display.error_trap_push(display)
# do something that may throw an X error
print(window.get_geometry())
# stop ignoring errors, return the error code if any
error = GdkX11.X11Display.error_trap_pop(display)
if (error == 0):
print("previous operation succeed.")
else:
print("previous operation failed, error %d." % error)
# do error recovery here
return True
GLib.timeout_add(100, loop)
Gtk.main()
Output:
(x=0, y=0, width=952, height=545)
previous operation succeed.
(x=0, y=0, width=952, height=545)
previous operation succeed.
(x=-1013446544, y=22086, width=-1013446512, height=22086)
previous operation failed, error 9.
(x=-1013446544, y=22086, width=-1013446512, height=22086)
previous operation failed, error 9.