英文:
Show Gtk GUI on HTTP request via Rocket
问题
正确的解决方法是确保GTK只在单个线程中初始化,以避免多线程冲突。您可以使用gtk::init
函数在Rocket的启动函数中进行初始化,以确保GTK只在主线程中初始化一次。
修改您的main.rs
如下所示:
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, Button};
use rocket::{get, launch, routes, Build, Rocket};
use std::sync::Mutex;
// 使用Mutex确保只有一个线程在初始化GTK
lazy_static::lazy_static! {
static ref GTK_INIT_MUTEX: Mutex<()> = Mutex::new(());
}
#[launch]
fn rocket() -> Rocket<Build> {
// 初始化GTK
let _guard = GTK_INIT_MUTEX.lock().unwrap();
gtk::init().expect("Failed to initialize GTK");
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();
}
通过在Rocket启动函数中使用lazy_static
库的Mutex
来确保只有一个线程在初始化GTK。这将解决多线程冲突的问题,使您的程序能够在第二个请求时正常运行。
英文:
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?
答案1
得分: 1
以下是您提供的代码的翻译:
根据 @Jmb 的提示,我提出了以下解决方案,适用于我:
```rust
#![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("无法发送到线程");
"Gui 显示!".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("无法接收消息"), "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("第一个 GTK 程序")
.default_width(350)
.default_height(70)
.build();
let button = Button::with_label("点击我!");
button.connect_clicked(|_| {
eprintln!("点击了!");
});
window.set_child(Some(&button));
window.show();
});
application.run();
}
英文:
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();
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论