antoyo
antoyo

Reputation: 11923

Second gtk_main_quit() not working

In an application, I nest gtk event loops to be able to return a value in a callback. However, I have some issues with a callback already calling gtk_main_quit() because multiple calls to this function does not seem to exit as many nestings of event loop.

Here is an example of my issue:

extern crate gtk;

use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;

use gtk::{Button, ButtonExt, ContainerExt, Continue, Inhibit, WidgetExt, Window, WindowType};

fn main() {
    gtk::init().unwrap();
    let window = Window::new(WindowType::Toplevel);

    let quit = Arc::new(AtomicBool::new(false));

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    let button = Button::new_with_label("Click");
    let quit2 = quit.clone();
    button.connect_clicked(move |_| {
        let quit = quit2.clone();
        thread::spawn(move || {
            quit.store(true, Ordering::Relaxed);
        });
        println!("Run");
        gtk::main();
    });
    window.add(&button);
    window.show_all();

    gtk::idle_add(move || {
        if quit.load(Ordering::Relaxed) {
            println!("Quit");
            gtk::main_quit();
            gtk::main_quit();
            quit.store(false, Ordering::Relaxed);
        }
        Continue(true)
    });

    gtk::main();
}

As you can see in the gtk::idle_add call, I call gtk::main_quit() twice, which should exit the application when the button is pressed because gtk::main() was also called twice (one at the end of the main function, the other one in the button clicked callback). But the application does not exit when I click the button.

The documentation of gtk seems to indicate that this is the expected behaviour:

Makes the innermost invocation of the main loop return when it regains control.

(emphasis is mine)

So, I believe that this does not exit the application because calling gtk::main_quit() twice won't allow the gtk main loop to "regain control".

My question is, what should I do between the two calls to gtk::main_quit() to stop the 2 nestings of event loop?

Upvotes: 0

Views: 1305

Answers (2)

user4815162342
user4815162342

Reputation: 154906

Short answer: the solution is to replace the second gtk::main_quit with:

gtk::idle_add(|| {
    gtk::main_quit();
    Continue(false)
});

As before, the first gtk::main_quit() will arrange for the inner main loop to quit. Additionally, the idle handler will be picked up by the outer main loop, causing it to immediately terminate as well.

This can be generalized with a pseudo-recursive function that repeats the process as many times as necessary:

fn deep_main_quit(n: usize) {
    if n == 0 {
        return;
    }
    gtk::main_quit();
    gtk::idle_add(move || {
        deep_main_quit(n - 1);
        Continue(false)
    });
}

Note that the use of idle_add to continually check for a flag will result in busy-looping, which you almost certainly want to avoid. (On my machine, running your program takes up a full CPU core.) In general, the preferred approach is to wait on a condition variable. But if you just need to tell the GUI thread to do something, as shown in your code, you can just call glib::idle_add from a different thread. The provided callback will be queued and executed in the GUI thread.

With this change, one thread releasing two levels of gtk_main in the GUI thread would look like this:

fn main() {
    gtk::init().unwrap();
    let window = Window::new(WindowType::Toplevel);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    let button = Button::new_with_label("Click");
    button.connect_clicked(|_| {
        thread::spawn(|| {
            glib::idle_add(|| {
                println!("Quit");
                deep_main_quit(2);
                Continue(false)
            });
        });
        println!("Run");
        gtk::main();
    });
    window.add(&button);
    window.show_all();

    gtk::main();
}

Upvotes: 3

antoyo
antoyo

Reputation: 11923

I've fixed my issue by manually creating a glib MainLoop and using it instead of nesting calls gtk event loop.

Upvotes: 0

Related Questions