Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*mut core_graphics::sys::macos::CGEventSource` cannot be sent between threads safely #96

Open
ghost opened this issue Aug 19, 2020 · 4 comments
Labels

Comments

@ghost
Copy link

ghost commented Aug 19, 2020

Describe the bug
I'm trying to write a simple program that handles incoming MIDI and sends keystrokes based on that. Unfortunately, I cannot compile written code due to Traits not being implemented.
Error from VS Code

`*mut core_graphics::sys::macos::CGEventSource` cannot be sent between threads safely
within `[closure@src/main.rs:82:9: 91:10 keystroke:enigo::macos::macos_impl::Enigo]`, the trait `std::marker::Send` is not implemented for `*mut core_graphics::sys::macos::CGEventSource`
required because it appears within the type `core_graphics::event_source::CGEventSource`
required because it appears within the type `enigo::macos::macos_impl::Enigo`
required because it appears within the type `[closure@src/main.rs:82:9: 91:10 keystroke:enigo::macos::macos_impl::Enigo]`

To Reproduce
Besically, the code here is straight from midir example, with couple of added lines:

    let keystroke = Enigo::new();
    let _conn_in = midi_in.connect(
        in_port,
        "midir-read-input",
        move |stamp, message, _| {
            println!("{}: {:?} (len = {})", stamp, message, message.len());
            if message[0] == 144 && message[1] == 60 {
                keystroke.key_sequence_parse("{+META}{-META}");
            }
            println!("{}: Matched MIDI input, sending configured keystroke", stamp)
        },
        (),
    )?;

Environment (please complete the following information):

  • OS: macOS Catalina 10.15.6
  • rustc 1.45.2 (d3fb005a3 2020-07-31)
  • enigo 0.0.14
  • midir 0.6.2

Additional context
Don't think it's the problem with midir, but might be good to check there as well. I'm not very well versed with Rust just yet.

@pythoneer
Copy link
Member

pythoneer commented Aug 25, 2020

Hi and thanks for opening the issue. Unfortunately i am currently not able to investigate this further due to my mac being unavailable for a short time. But just from reading the issue it looks like you are trying to send Enigo into another thread and it appears that this is not safe to do (at least on macOS). Can you try to instantiate the Enigo instance inside your callback?

        move |stamp, message, _| {
            let keystroke = Enigo::new(); // moved the instantiation to this place
            println!("{}: {:?} (len = {})", stamp, message, message.len());
            if message[0] == 144 && message[1] == 60 {
                keystroke.key_sequence_parse("{+META}{-META}");
            }
            println!("{}: Matched MIDI input, sending configured keystroke", stamp)
        }

@ghost
Copy link
Author

ghost commented Aug 26, 2020

Hi there! It definitely works and it compiles without any issues – thank you.
I just wasn't sure if that's a common practice. Won't it instantiate multiple instances with every incoming message (per the example) because of it being a continuously working and looping function?

@indianakernick
Copy link

I'm doing something very similar and running into the same problem. Creating a new Enigo instance for every message seems to work but I get this error spammed in the console.

pid(15751)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!

So I used a channel so that the Enigo instance is only accessed from the main thread. Here's a fully working example of using a channel.

use tokio::sync::mpsc;
use enigo::{Enigo, Key, KeyboardControllable};

pub enum EnigoCommand {
    KeyClick(Key),
    KeyDown(Key),
    KeyUp(Key),
}

#[tokio::main]
async fn main() {
    let mut enigo = Enigo::new();
    let (ch_tx, mut ch_rx) = mpsc::unbounded_channel::<EnigoCommand>();

    tokio::spawn(async move {
        // application specific stuff...
        // send commands to ch_tx
        ch_tx.send(EnigoCommand::KeyClick(Key::Layout('E')));
    });

    while let Some(command) = ch_rx.recv().await {
        match command {
            EnigoCommand::KeyClick(key) => enigo.key_click(key),
            EnigoCommand::KeyDown(key) => enigo.key_down(key),
            EnigoCommand::KeyUp(key) => enigo.key_up(key),
        }
    }
}

It would be nice if Enigo implemented Send. Maybe the command enum could be added to the library to make this workaround a little less tedious? The only function that returns something is key_sequence_parse_try so this should work fine for almost all cases.

@pentamassiv
Copy link
Collaborator

There has not been any progress in making Enigo safe to send, but we do have something like the command enum now. They are called Token. The serde example demonstrates how to use them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants