Blocking Gtk signal handlers without freezing your application

I've been working on a Gtk project a bit recently (A web browser called Roland, which I'll talk about another day), and I recently came across an interaction problem.

WebKit2.WebView has a signal called script-dialog, which is fired whenever a JavaScript dialog, e.g. window.prompt, is called. There's no way to handle this asynchronously, and I couldn't figure out a way to handle it synchronously without freezing up the UI. I knew there had to be a way however, because, well, the default implementation could certainly do it with a dialog box.

After looking up things on spinning up new Gtk mainloops, I found some documentation which states:

It is possible to create new instances of GMainLoop recursively. This is often used in GTK+ applications when showing modal dialog boxes.

The Main Event Loop: GLib Reference Manual

"Could it really be that simple? Do I just call Gtk.main() again?" I asked myself. So I sat down and wrote out what I thought would theoretically work, and behold! It just worked. Within your signal handler you simply start another event loop, wait on the event you're interested in, and when it occurs you quit.

Because it's recursive, it will stop the inner most loop and let the outer loop continue, with the UI never having frozen. What follows is a small example demonstrating how it works.

from gi.repository import WebKit2, Gtk


def get_something_async():
    def activate(*args):
        entry.disconnect(id)
        Gtk.main_quit()

    id = entry.connect('activate', activate)

    # This will block until the activate function above is called, which
    # stops the secondary loop.
    Gtk.main()

    return entry.get_text()


def script_dialog(view, dialog):
    if dialog.get_dialog_type() == WebKit2.ScriptDialogType.PROMPT:
        result = get_something_async()
        dialog.prompt_set_text(result)
        return True
    elif dialog.get_dialog_type() == WebKit2.ScriptDialogType.ALERT:
        print('message', dialog.get_message())
        return True
    return False


view = WebKit2.WebView()
view.connect('script-dialog', script_dialog)
view.load_uri('file:///path/to/prompt.html')

window = Gtk.Window()

window.connect('destroy', Gtk.main_quit)

entry = Gtk.Entry()
vbox = Gtk.VBox()
vbox.add(view)
vbox.add(entry)
window.add(vbox)
window.show_all()

Gtk.main()

Where prompt.html looks like this:

<html>
<body>
  <script type="text/javascript" charset="utf-8">
    var name = window.prompt('whats your name?');
    window.alert('have name ' + name);
  </script>
</body>
</html>

It turns out that's all there is to it. Enjoy!