Show Gtk GUI on HTTP request via Rocket.

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

Show Gtk GUI on HTTP request via Rocket

问题

正确的解决方法是确保GTK只在单个线程中初始化,以避免多线程冲突。您可以使用gtk::init函数在Rocket的启动函数中进行初始化,以确保GTK只在主线程中初始化一次。

修改您的main.rs如下所示:

  1. use gtk::prelude::*;
  2. use gtk::{Application, ApplicationWindow, Button};
  3. use rocket::{get, launch, routes, Build, Rocket};
  4. use std::sync::Mutex;
  5. // 使用Mutex确保只有一个线程在初始化GTK
  6. lazy_static::lazy_static! {
  7. static ref GTK_INIT_MUTEX: Mutex<()> = Mutex::new(());
  8. }
  9. #[launch]
  10. fn rocket() -> Rocket<Build> {
  11. // 初始化GTK
  12. let _guard = GTK_INIT_MUTEX.lock().unwrap();
  13. gtk::init().expect("Failed to initialize GTK");
  14. rocket::build().mount("/", routes![do_get])
  15. }
  16. #[get("/")]
  17. fn do_get() -> String {
  18. show_gui();
  19. "Gui shown!".to_string()
  20. }
  21. fn show_gui() {
  22. let application = Application::builder()
  23. .application_id("com.example.FirstGtkApp")
  24. .build();
  25. application.connect_activate(|app| {
  26. let window = ApplicationWindow::builder()
  27. .application(app)
  28. .title("First GTK Program")
  29. .default_width(350)
  30. .default_height(70)
  31. .build();
  32. let button = Button::with_label("Click me!");
  33. button.connect_clicked(|_| {
  34. eprintln!("Clicked!");
  35. });
  36. window.set_child(Some(&button));
  37. window.show();
  38. });
  39. application.run();
  40. }

通过在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

  1. use gtk::prelude::*;
  2. use gtk::{Application, ApplicationWindow, Button};
  3. use gtk4 as gtk;
  4. use rocket::{get, launch, routes, Build, Rocket};
  5. #[launch]
  6. fn rocket() -&gt; Rocket&lt;Build&gt; {
  7. rocket::build().mount(&quot;/&quot;, routes![do_get])
  8. }
  9. #[get(&quot;/&quot;)]
  10. fn do_get() -&gt; String {
  11. show_gui();
  12. &quot;Gui shown!&quot;.to_string()
  13. }
  14. fn show_gui() {
  15. let application = Application::builder()
  16. .application_id(&quot;com.example.FirstGtkApp&quot;)
  17. .build();
  18. application.connect_activate(|app| {
  19. let window = ApplicationWindow::builder()
  20. .application(app)
  21. .title(&quot;First GTK Program&quot;)
  22. .default_width(350)
  23. .default_height(70)
  24. .build();
  25. let button = Button::with_label(&quot;Click me!&quot;);
  26. button.connect_clicked(|_| {
  27. eprintln!(&quot;Clicked!&quot;);
  28. });
  29. window.set_child(Some(&amp;button));
  30. window.show();
  31. });
  32. application.run();
  33. }

Cargo.toml

  1. [package]
  2. name = &quot;gui&quot;
  3. version = &quot;0.1.0&quot;
  4. edition = &quot;2021&quot;
  5. # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
  6. [dependencies]
  7. gtk4 = &quot;0.6.6&quot;
  8. rocket = { version = &quot;0.5.0-rc.3&quot;, features = [&quot;json&quot;] }

However, upon the second request, my program panics:

  1. $ cargo run
  2. Compiling gui v0.1.0 (/home/neumann/gui)
  3. Finished dev [unoptimized + debuginfo] target(s) in 2.54s
  4. Running `target/debug/gui`
  5. &#128295; Configured for debug.
  6. &gt;&gt; address: 127.0.0.1
  7. &gt;&gt; port: 8000
  8. &gt;&gt; workers: 12
  9. &gt;&gt; max blocking threads: 512
  10. &gt;&gt; ident: Rocket
  11. &gt;&gt; IP header: X-Real-IP
  12. &gt;&gt; limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
  13. &gt;&gt; temp dir: /tmp
  14. &gt;&gt; http/2: true
  15. &gt;&gt; keep-alive: 5s
  16. &gt;&gt; tls: disabled
  17. &gt;&gt; shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
  18. &gt;&gt; log level: normal
  19. &gt;&gt; cli colors: true
  20. &#128236; Routes:
  21. &gt;&gt; (do_get) GET /
  22. &#128225; Fairings:
  23. &gt;&gt; Shield (liftoff, response, singleton)
  24. &#128737;️ Shield:
  25. &gt;&gt; X-Content-Type-Options: nosniff
  26. &gt;&gt; Permissions-Policy: interest-cohort=()
  27. &gt;&gt; X-Frame-Options: SAMEORIGIN
  28. &#128640; Rocket has launched from http://127.0.0.1:8000
  29. GET / text/html:
  30. &gt;&gt; Matched: (do_get) GET /
  31. &gt;&gt; Outcome: Success
  32. &gt;&gt; Response succeeded.
  33. GET / text/html:
  34. &gt;&gt; Matched: (do_get) GET /
  35. 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
  36. note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
  37. &gt;&gt; Handler do_get panicked.
  38. &gt;&gt; This is an application bug.
  39. &gt;&gt; A panic in Rust must be treated as an exceptional event.
  40. &gt;&gt; Panicking is not a suitable error handling mechanism.
  41. &gt;&gt; Unwinding, the result of a panic, is an expensive operation.
  42. &gt;&gt; Panics will degrade application performance.
  43. &gt;&gt; Instead of panicking, return `Option` and/or `Result`.
  44. &gt;&gt; Values of either type can be returned directly from handlers.
  45. &gt;&gt; A panic is treated as an internal server error.
  46. &gt;&gt; Outcome: Failure
  47. &gt;&gt; No 500 catcher registered. Using Rocket default.
  48. &gt;&gt; Response succeeded.

What is the correct way to resolve this issue?

答案1

得分: 1

以下是您提供的代码的翻译:

  1. 根据 @Jmb 的提示,我提出了以下解决方案,适用于我:
  2. ```rust
  3. #![allow(clippy::let_underscore_untyped, clippy::no_effect_underscore_binding)]
  4. use gtk4::prelude::*;
  5. use gtk4::{Application, ApplicationWindow, Button};
  6. use rocket::{get, launch, routes, Build, Rocket, State};
  7. use std::sync::mpsc::{sync_channel, SyncSender};
  8. use std::thread;
  9. #[launch]
  10. fn rocket() -> Rocket<Build> {
  11. let sender = gtk_spawn();
  12. rocket::build().manage(sender).mount("/", routes![do_get])
  13. }
  14. #[allow(clippy::needless_pass_by_value)]
  15. #[get("/")]
  16. fn do_get(sender: &State<SyncSender<&'static str>>) -> String {
  17. sender.send("show").expect("无法发送到线程");
  18. "Gui 显示!".to_string()
  19. }
  20. #[must_use]
  21. pub fn gtk_spawn() -> SyncSender<&'static str> {
  22. let (sender, receiver) = sync_channel::<&'static str>(32);
  23. thread::spawn(move || {
  24. while matches!(receiver.recv().expect("无法接收消息"), "show") {
  25. show_gui();
  26. }
  27. });
  28. sender
  29. }
  30. fn show_gui() {
  31. let application = Application::builder()
  32. .application_id("com.example.FirstGtkApp")
  33. .build();
  34. application.connect_activate(|app| {
  35. let window = ApplicationWindow::builder()
  36. .application(app)
  37. .title("第一个 GTK 程序")
  38. .default_width(350)
  39. .default_height(70)
  40. .build();
  41. let button = Button::with_label("点击我!");
  42. button.connect_clicked(|_| {
  43. eprintln!("点击了!");
  44. });
  45. window.set_child(Some(&button));
  46. window.show();
  47. });
  48. application.run();
  49. }
英文:

Based on @Jmb's hint I came up with the following solution, that works for me:

  1. #![allow(clippy::let_underscore_untyped, clippy::no_effect_underscore_binding)]
  2. use gtk4::prelude::*;
  3. use gtk4::{Application, ApplicationWindow, Button};
  4. use rocket::{get, launch, routes, Build, Rocket, State};
  5. use std::sync::mpsc::{sync_channel, SyncSender};
  6. use std::thread;
  7. #[launch]
  8. fn rocket() -&gt; Rocket&lt;Build&gt; {
  9. let sender = gtk_spawn();
  10. rocket::build().manage(sender).mount(&quot;/&quot;, routes![do_get])
  11. }
  12. #[allow(clippy::needless_pass_by_value)]
  13. #[get(&quot;/&quot;)]
  14. fn do_get(sender: &amp;State&lt;SyncSender&lt;&amp;&#39;static str&gt;&gt;) -&gt; String {
  15. sender.send(&quot;show&quot;).expect(&quot;cannot send to thread&quot;);
  16. &quot;Gui shown!&quot;.to_string()
  17. }
  18. #[must_use]
  19. pub fn gtk_spawn() -&gt; SyncSender&lt;&amp;&#39;static str&gt; {
  20. let (sender, receiver) = sync_channel::&lt;&amp;&#39;static str&gt;(32);
  21. thread::spawn(move || {
  22. while matches!(receiver.recv().expect(&quot;could not receive message&quot;), &quot;show&quot;) {
  23. show_gui();
  24. }
  25. });
  26. sender
  27. }
  28. fn show_gui() {
  29. let application = Application::builder()
  30. .application_id(&quot;com.example.FirstGtkApp&quot;)
  31. .build();
  32. application.connect_activate(|app| {
  33. let window = ApplicationWindow::builder()
  34. .application(app)
  35. .title(&quot;First GTK Program&quot;)
  36. .default_width(350)
  37. .default_height(70)
  38. .build();
  39. let button = Button::with_label(&quot;Click me!&quot;);
  40. button.connect_clicked(|_| {
  41. eprintln!(&quot;Clicked!&quot;);
  42. });
  43. window.set_child(Some(&amp;button));
  44. window.show();
  45. });
  46. application.run();
  47. }

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:

确定