Send over thread a non Sendable object

Hello here,

I'm using TrayItem object to display a systray application icon. It is working :

Cargo.toml

[package]
name = "demo"
version = "0.1.0"
edition = "2021"

[dependencies]
tray-item = "0.7.0"
gtk = "0.15.4"

main.rs

use tray_item::TrayItem;

fn main() {
    gtk::init().unwrap();
    let mut tray = TrayItem::new("Demo", "emblem-shared").unwrap();
    tray.add_menu_item("Quitter", || {
        gtk::main_quit();
    })
    .unwrap();

    gtk::main();
}

My need is to change the systray icon during application running. But gtk::main(); line is blocking (doc).

So, I try to start a thread where I could manipulate tray object :

    let protected_tray = Arc::new(Mutex::new(tray));
    let thread_tray = Arc::clone(&protected_tray);
    thread::spawn(move || {
        let mut tray__ = thread_tray.lock().unwrap();
        tray__.set_icon("/path/to/my/icon.png").unwrap();
    });

But this produce error :

   Compiling demo v0.1.0 (/home/bastiensevajol/Projets/demo)
error[E0277]: `*mut libappindicator_sys::_AppIndicator` cannot be sent between threads safely
   --> src/main.rs:18:5
    |
18  |     thread::spawn(move || {
    |     ^^^^^^^^^^^^^ `*mut libappindicator_sys::_AppIndicator` cannot be sent between threads safely
    |
    = help: within `TrayItem`, the trait `Send` is not implemented for `*mut libappindicator_sys::_AppIndicator`
    = note: required because it appears within the type `libappindicator::AppIndicator`
    = note: required because it appears within the type `tray_item::api::linux::TrayItemLinux`
    = note: required because it appears within the type `TrayItem`
    = note: required because of the requirements on the impl of `Sync` for `Mutex<TrayItem>`
    = note: required because of the requirements on the impl of `Send` for `Arc<Mutex<TrayItem>>`
    = note: required because it appears within the type `[closure@src/main.rs:18:19: 20:6]`
note: required by a bound in `spawn`
   --> /home/bastiensevajol/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:646:8
    |
646 |     F: Send + 'static,
    |        ^^^^ required by this bound in `spawn`

error[E0277]: `NonNull<GObject>` cannot be sent between threads safely
   --> src/main.rs:18:5
    |
18  |     thread::spawn(move || {
    |     ^^^^^^^^^^^^^ `NonNull<GObject>` cannot be sent between threads safely
    |
    = help: within `TrayItem`, the trait `Send` is not implemented for `NonNull<GObject>`
    = note: required because it appears within the type `ObjectRef`
    = note: required because it appears within the type `gtk::Menu`
    = note: required because it appears within the type `tray_item::api::linux::TrayItemLinux`
    = note: required because it appears within the type `TrayItem`
    = note: required because of the requirements on the impl of `Sync` for `Mutex<TrayItem>`
    = note: required because of the requirements on the impl of `Send` for `Arc<Mutex<TrayItem>>`
    = note: required because it appears within the type `[closure@src/main.rs:18:19: 20:6]`
note: required by a bound in `spawn`
   --> /home/bastiensevajol/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:646:8
    |
646 |     F: Send + 'static,
    |        ^^^^ required by this bound in `spawn`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `demo` due to 2 previous errors

Arc & Mutex are not a safe way to use object in threads :thinking: ? My goal is possible to achieve ?

Thank you for reading me !

What is the full error message? That is not the full error message.

Anything that changes the UI (including the tray icon) must happen in the main thread, that's the limitation of GTK. So you likely want to have some kind of event listener, which will be run by GTK, and put the tray icon changing code inside it.

2 Likes

I edited the message with full error log

Hello, Ok I got it. I don't know gtk API so I don't know how to do that. I will search for that !

Code for the solution :

Add glib = "0.15.11" to Cargo.toml dependencies.
Use glib::timeout_add_local function :

use std::time::Duration;
use tray_item::TrayItem;

fn main() {
    gtk::init().unwrap();
    let mut tray = TrayItem::new("Demo", "emblem-shared").unwrap();
    tray.add_menu_item("Quitter", || {
        gtk::main_quit();
    })
    .unwrap();

    glib::timeout_add_local(Duration::from_millis(500), move || {
        tray.set_icon("/path/to/my/icon.png")
            .unwrap();
        glib::Continue(true)
    });

    gtk::main();
}

And perfectly working !

1 Like

For reference, if people were to run into this issue but outside of the very specific GTK context, the following crate can be quite useful:

https://lib.rs/diplomatic-bag

  • The TL,DR of that crate is: the !Send value is constructed inside a special thread (since, by virtue of being !Send, once constructed, it can't leave that thread), and then a mini-runtime inside it is spinned, which shall run, serialized / sequentially, the operations that other threads would like to perform on that value.

I don't think it would help here, since with GTK, not only are things !Send (which ::diplomatic-bag can help with), but the main() function hogs that thread forever, so any other operations that would be enqueued to be run after it would never actually happen, or would happen too late).

6 Likes

The send_wrapper crate also provides a type specifically designed for this general issue. To quote its documentation:

A wrapper which allows you to move around non-Send-types between threads, as long as you access the contained value only from within the original thread and make sure that it is dropped from within the original thread.

Of course, it can be dangerous to use unless you are truly certain which thread you are running on.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.