Я написал программу для создания снимков экрана в GTK+-3 с использованием библиотеки GDK, в основном она работает, но иногда я обнаруживаю, что программа случайным образом вылетает с ошибками X11. Я понял, что сбои возникают, если окно открывается и закрывается мгновенно, и я смог написать следующую программу, чтобы воспроизвести проблему.
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()
Если я открою окно до начала выполнения loop()
, а затем внезапно закрою окно в середине loop()
, программа вылетит с ошибкой X11.
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.)
Очевидно, он падает, потому что базовое окно X11 за этим GDKWindow уже исчезает до get_geometry()
. Но как мне убедиться, что GDKWindow по-прежнему действителен? Я пробовал использовать is_destroyed()
, process_all_updates()
и flush()
, но ни один из них не дал никакого эффекта.
Решено: как показал повторяющийся вопрос, X является асинхронным протоколом, поэтому просто невозможно гарантировать, что окно существует. Вместо этого мы должны установить обработчик исключений, чтобы перехватывать ошибки из X после их возникновения. В GDK обработка исключений может быть реализована с помощью gdk_x11_display_error_trap_push()
(начать игнорировать и записывать исключения) и gdk_x11_display_error_trap_pop()
(возвращать код ошибки, если она была).
Правильная программа выглядит примерно так.
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()
Выход:
(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.
.get_active_window(screen)
то, что ты хочешь? - person stovfl   schedule 16.03.2020get_active_window()
действительно предотвращает возникновение проблемы, но это возможно только потому, что скорость обновления достаточно высока. Добавление снимка экранаGdk.pixbuf_get_from_window(window, 0, 0, 1, 1)
и попытка открыть и закрыть окно вызовет проблему, потому что это требует времени. Я считаю, что конечная проблема заключается в том, что нет атомарного способа обеспечить доступность окна, поэтому у нас есть проблема с TOCTOU. К сожалению, если нет решения, запуск захвата экрана в отдельном процессе, перехват SIGABRT и перезапуск самого себя кажется единственным выходом... - person 比尔盖子   schedule 16.03.2020