I want to show a Gtk Window upon a HTTP request to a Rocket server in my program.
Here's a MRE:
src/main.rs
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Button};
use gtk4 as gtk;
use rocket::{get, launch, routes, Build, Rocket};
#[launch]
fn rocket() -> Rocket<Build> {
rocket::build().mount("/", routes![do_get])
}
#[get("/")]
fn do_get() -> String {
show_gui();
"Gui shown!".to_string()
}
fn show_gui() {
let application = Application::builder()
.application_id("com.example.FirstGtkApp")
.build();
application.connect_activate(|app| {
let window = ApplicationWindow::builder()
.application(app)
.title("First GTK Program")
.default_width(350)
.default_height(70)
.build();
let button = Button::with_label("Click me!");
button.connect_clicked(|_| {
eprintln!("Clicked!");
});
window.set_child(Some(&button));
window.show();
});
application.run();
}
Cargo.toml
[package]
name = "gui"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gtk4 = "0.6.6"
rocket = { version = "0.5.0-rc.3", features = ["json"] }
However, upon the second request, my program panics:
$ cargo run
Compiling gui v0.1.0 (/home/neumann/gui)
Finished dev [unoptimized + debuginfo] target(s) in 2.54s
Running `target/debug/gui`
🔧 Configured for debug.
>> address: 127.0.0.1
>> port: 8000
>> workers: 12
>> max blocking threads: 512
>> ident: Rocket
>> IP header: X-Real-IP
>> limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
>> temp dir: /tmp
>> http/2: true
>> keep-alive: 5s
>> tls: disabled
>> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
>> log level: normal
>> cli colors: true
📬 Routes:
>> (do_get) GET /
📡 Fairings:
>> Shield (liftoff, response, singleton)
🛡️ Shield:
>> X-Content-Type-Options: nosniff
>> Permissions-Policy: interest-cohort=()
>> X-Frame-Options: SAMEORIGIN
🚀 Rocket has launched from http://127.0.0.1:8000
GET / text/html:
>> Matched: (do_get) GET /
>> Outcome: Success
>> Response succeeded.
GET / text/html:
>> Matched: (do_get) GET /
thread 'rocket-worker-thread' panicked at 'Attempted to initialize GTK from two different threads.', /home/neumann/.cargo/registry/src/index.crates.io-6f17d22bba15001f/gtk4-0.6.6/src/rt.rs:95:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
>> Handler do_get panicked.
>> This is an application bug.
>> A panic in Rust must be treated as an exceptional event.
>> Panicking is not a suitable error handling mechanism.
>> Unwinding, the result of a panic, is an expensive operation.
>> Panics will degrade application performance.
>> Instead of panicking, return `Option` and/or `Result`.
>> Values of either type can be returned directly from handlers.
>> A panic is treated as an internal server error.
>> Outcome: Failure
>> No 500 catcher registered. Using Rocket default.
>> Response succeeded.
What is the correct way to resolve this issue?
Based on @Jmb's hint I came up with the following solution, that works for me:
#![allow(clippy::let_underscore_untyped, clippy::no_effect_underscore_binding)]
use gtk4::prelude::*;
use gtk4::{Application, ApplicationWindow, Button};
use rocket::{get, launch, routes, Build, Rocket, State};
use std::sync::mpsc::{sync_channel, SyncSender};
use std::thread;
#[launch]
fn rocket() -> Rocket<Build> {
let sender = gtk_spawn();
rocket::build().manage(sender).mount("/", routes![do_get])
}
#[allow(clippy::needless_pass_by_value)]
#[get("/")]
fn do_get(sender: &State<SyncSender<&'static str>>) -> String {
sender.send("show").expect("cannot send to thread");
"Gui shown!".to_string()
}
#[must_use]
pub fn gtk_spawn() -> SyncSender<&'static str> {
let (sender, receiver) = sync_channel::<&'static str>(32);
thread::spawn(move || {
while matches!(receiver.recv().expect("could not receive message"), "show") {
show_gui();
}
});
sender
}
fn show_gui() {
let application = Application::builder()
.application_id("com.example.FirstGtkApp")
.build();
application.connect_activate(|app| {
let window = ApplicationWindow::builder()
.application(app)
.title("First GTK Program")
.default_width(350)
.default_height(70)
.build();
let button = Button::with_label("Click me!");
button.connect_clicked(|_| {
eprintln!("Clicked!");
});
window.set_child(Some(&button));
window.show();
});
application.run();
}