Reputation: 11923
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
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
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