JuliaPy / PyCall.jl

Package to call Python functions from the Julia language
MIT License
1.45k stars 186 forks source link

Passing julia function to python listener function to receive async events #985

Closed Sixzero closed 2 years ago

Sixzero commented 2 years ago

I would like to receive keyboard presses pressed in the background, to run some julia codes based on when and which key is getting pressed.

My solution: I use a python package called "pynput" with Pycall. pynput needs a callback function which fires on keypresses, I want to forward the keys pressed to a julia function, so I pass a julia function as a callback which gets called on keypresses.

Problem: I get segfault whenever the julia function is getting called in the pynput callback function.

I would like to know what cause the issue, I feel like there is something I don't know about, why and how it could happen.

I am testing with this MVP.

The python code: keyboard_core.py


from glob import glob
from pynput import keyboard

is_running = False

def on_press(fn):
    def on_pressable(key):
        global is_running
        try:
            print(key)
            if key == keyboard.Key.esc:
                is_running = False
                # Stop listeners
                return False
            if fn is not None:
                print('Julia function getting called:')
                fn(key)

        except AttributeError as e:
            print("Error", e)
            pass
    return on_pressable

def init(fn=None):
    global is_running
    print("Init starting.")
    # Collect events until released
    if not is_running:
        is_running = True
        with keyboard.Listener(on_press=on_press(fn)) as listener:
            listener.join() 
    print("Init finished.")

if __name__ == '__main__':
    init()

Julia code:

using PyCall

py"""
import sys
sys.path.insert(0, ".")
"""

coll = pyimport("keyboard_core")
test_fn(a) = println("Char pressed:", a)

coll.init(test_fn)

I hope someone will have an easier solution to monitoring keyboard presses and can help.

Sixzero commented 2 years ago

Ah, I guess the problem is that pynput uses threads and Pycall is probably not supporting passing functions to threads, so the solution is to use a synchonous solution for this problem.