Seism0h
Seism0h

Reputation: 31

How to combine callback-based library with asyncio library in Python?

I have the following issue. I want to read out keystrokes with the pynput library and send them over websockets. Pynput proposes the following usage

from pynput import keyboard

def on_press(key):
    try:
        print('alphanumeric key {0} pressed'.format(
            key.char))
    except AttributeError:
        print('special key {0} pressed'.format(
            key))

def on_release(key):
    print('{0} released'.format(
        key))
    if key == keyboard.Key.esc:
        # Stop listener
        return False

# Collect events until released
with keyboard.Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()

# ...or, in a non-blocking fashion:
listener = keyboard.Listener(
    on_press=on_press,
    on_release=on_release)
listener.start()

(taken from https://pynput.readthedocs.io/en/latest/keyboard.html)

In contrast to that, the websocket-client library is called as follows:

import asyncio
import websockets

async def hello():
    uri = "ws://localhost:8765"
    async with websockets.connect(uri) as websocket:
        name = input("What's your name? ")

        await websocket.send(name)
        print(f"> {name}")

        greeting = await websocket.recv()
        print(f"< {greeting}")

asyncio.get_event_loop().run_until_complete(hello())

(taken from https://websockets.readthedocs.io/en/stable/intro.html).

I am now struggling how this can be done as the websocket library is asynchronous and pynput is synchronous. I somehow have to inject a "websocket.send()" into on_press/on_release - but currently I am struggling with this.

Upvotes: 3

Views: 1692

Answers (1)

user4815162342
user4815162342

Reputation: 154911

Note that your pynput example contains two different variants of using pynput, from which you need to choose the latter because it is easier to connect to asyncio. The keyboard listener will allow the program to proceed with the asyncio event loop, while invoking the callbacks from a separate thread. Inside the callback functions you can use call_soon_threadsafe to communicate the key-presses to asyncio, e.g. using a queue. For example (untested):

def transmit_keys():
    # Start a keyboard listener that transmits keypresses into an
    # asyncio queue, and immediately return the queue to the caller.
    queue = asyncio.Queue()
    loop = asyncio.get_event_loop()
    def on_press(key):
        # this callback is invoked from another thread, so we can't
        # just queue.put_nowait(key.char), we have to go through
        # call_soon_threadsafe
        loop.call_soon_threadsafe(queue.put_nowait, key.char)
    pynput.keyboard.Listener(on_press=on_press).start()
    return queue

async def main():
    key_queue = transmit_keys()
    async with websockets.connect("ws://localhost:8765") as websocket:
        while True:
            key = await key_queue.get()
            await websocket.send(f"key pressed: {key}")

asyncio.run(main())

Upvotes: 6

Related Questions