Show Gtk GUI on HTTP request via Rocket.

huangapple go评论90阅读模式
英文:

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() -&gt; Rocket&lt;Build&gt; {
    rocket::build().mount(&quot;/&quot;, routes![do_get])
}

#[get(&quot;/&quot;)]
fn do_get() -&gt; String {
    show_gui();
    &quot;Gui shown!&quot;.to_string()
}

fn show_gui() {
    let application = Application::builder()
        .application_id(&quot;com.example.FirstGtkApp&quot;)
        .build();

    application.connect_activate(|app| {
        let window = ApplicationWindow::builder()
            .application(app)
            .title(&quot;First GTK Program&quot;)
            .default_width(350)
            .default_height(70)
            .build();

        let button = Button::with_label(&quot;Click me!&quot;);
        button.connect_clicked(|_| {
            eprintln!(&quot;Clicked!&quot;);
        });
        window.set_child(Some(&amp;button));

        window.show();
    });

    application.run();
}

Cargo.toml

[package]
name = &quot;gui&quot;
version = &quot;0.1.0&quot;
edition = &quot;2021&quot;

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
gtk4 = &quot;0.6.6&quot;
rocket = { version = &quot;0.5.0-rc.3&quot;, features = [&quot;json&quot;] }

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`
&#128295; Configured for debug.
   &gt;&gt; address: 127.0.0.1
   &gt;&gt; port: 8000
   &gt;&gt; workers: 12
   &gt;&gt; max blocking threads: 512
   &gt;&gt; ident: Rocket
   &gt;&gt; IP header: X-Real-IP
   &gt;&gt; limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
   &gt;&gt; temp dir: /tmp
   &gt;&gt; http/2: true
   &gt;&gt; keep-alive: 5s
   &gt;&gt; tls: disabled
   &gt;&gt; shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
   &gt;&gt; log level: normal
   &gt;&gt; cli colors: true
&#128236; Routes:
   &gt;&gt; (do_get) GET /
&#128225; Fairings:
   &gt;&gt; Shield (liftoff, response, singleton)
&#128737;️ Shield:
   &gt;&gt; X-Content-Type-Options: nosniff
   &gt;&gt; Permissions-Policy: interest-cohort=()
   &gt;&gt; X-Frame-Options: SAMEORIGIN
&#128640; Rocket has launched from http://127.0.0.1:8000
GET / text/html:
   &gt;&gt; Matched: (do_get) GET /
   &gt;&gt; Outcome: Success
   &gt;&gt; Response succeeded.
GET / text/html:
   &gt;&gt; Matched: (do_get) GET /
thread &#39;rocket-worker-thread&#39; panicked at &#39;Attempted to initialize GTK from two different threads.&#39;, /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
   &gt;&gt; Handler do_get panicked.
   &gt;&gt; This is an application bug.
   &gt;&gt; A panic in Rust must be treated as an exceptional event.
   &gt;&gt; Panicking is not a suitable error handling mechanism.
   &gt;&gt; Unwinding, the result of a panic, is an expensive operation.
   &gt;&gt; Panics will degrade application performance.
   &gt;&gt; Instead of panicking, return `Option` and/or `Result`.
   &gt;&gt; Values of either type can be returned directly from handlers.
   &gt;&gt; A panic is treated as an internal server error.
   &gt;&gt; Outcome: Failure
   &gt;&gt; No 500 catcher registered. Using Rocket default.
   &gt;&gt; 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() -&gt; Rocket&lt;Build&gt; {
    let sender = gtk_spawn();
    rocket::build().manage(sender).mount(&quot;/&quot;, routes![do_get])
}

#[allow(clippy::needless_pass_by_value)]
#[get(&quot;/&quot;)]
fn do_get(sender: &amp;State&lt;SyncSender&lt;&amp;&#39;static str&gt;&gt;) -&gt; String {
    sender.send(&quot;show&quot;).expect(&quot;cannot send to thread&quot;);
    &quot;Gui shown!&quot;.to_string()
}

#[must_use]
pub fn gtk_spawn() -&gt; SyncSender&lt;&amp;&#39;static str&gt; {
    let (sender, receiver) = sync_channel::&lt;&amp;&#39;static str&gt;(32);
    thread::spawn(move || {
        while matches!(receiver.recv().expect(&quot;could not receive message&quot;), &quot;show&quot;) {
            show_gui();
        }
    });
    sender
}

fn show_gui() {
    let application = Application::builder()
        .application_id(&quot;com.example.FirstGtkApp&quot;)
        .build();

    application.connect_activate(|app| {
        let window = ApplicationWindow::builder()
            .application(app)
            .title(&quot;First GTK Program&quot;)
            .default_width(350)
            .default_height(70)
            .build();

        let button = Button::with_label(&quot;Click me!&quot;);
        button.connect_clicked(|_| {
            eprintln!(&quot;Clicked!&quot;);
        });
        window.set_child(Some(&amp;button));

        window.show();
    });

    application.run();
}

huangapple
  • 本文由 发表于 2023年7月6日 17:47:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76627546.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定