2

I am making a program that toggles on and off by a certain key on the keyboard (using pynput). I placed the keyboard listener loop in the first thread, and the action loop in the second. The problem is that after I start the code, it doesn't listen to the keyboard immediately, only after 9-10 seconds have passed. And sometimes it refuses to react to Esc button, and sometimes it works. How to fix the lag? Is the code ok?

from threading import Thread
from pynput import keyboard
import time

flag = False
kill = False

def on_press(key):
    global flag
    global kill

    if key == keyboard.KeyCode.from_char('a'):
        print('pressed A')
        flag = not flag
    if key == keyboard.Key.esc:
        kill = True
        return False

def thr2():
    print('joining...')
    with keyboard.Listener(on_press=on_press) as listen:
        listen.join()

def thr1():
    while True:
        if kill:
            break
        if flag:
            print('looping....')
            time.sleep(0.4)

if __name__ == "__main__":
    thread1 = Thread(target=thr1)
    thread2 = Thread(target=thr2)
    thread1.start()
    thread2.start()
Mirrah
  • 125
  • 2
  • 9

2 Answers2

2

It looks like the actual delay is coming from the pynput keyboard.Listener context handler itself. I can't tell you whats happening under the hood but the delay is not coming from the way you are managing your threads.

# pynput library creating keyboard.Listener thread causes the delay
with keyboard.Listener(on_press=on_press) as listen:  
    print('listen thread created')  # This does not happen until after the delay
    listen.join()

You may want to rephrase the question so that it is specific to pynput keyboard.Listener

Jake Hill
  • 76
  • 3
  • have you tried running the code? do you experience the same lag? I noticed that delay doesn't happen unless I put it under the thread BUT when I use multiprocessing instead and change Thread(target=...) to Process(target=...) the delay is gone. But I cannot use multiprocessing because it does not recognize global variables for some reason and this is what I am trying to figure out right now. Do you by chance know how to pass 'flag' and 'kill' variables between processes? – Mirrah Jan 19 '20 at 07:54
  • I did experience the same delay when running it both within a thread and outside of a thread. The fact that it is already in a thread should not matter because the keyboard.Listen handler is creating a new thread anyway. Using multiprocessing instead of threads makes things difficult because Processes do not have explicit access to anything that exists outside of them. – Jake Hill Jan 21 '20 at 05:41
2

Here is a solution that works nicely with multiprocessing:

import sys
from pynput import keyboard
from time import sleep
from multiprocessing import Process, Event
from functools import partial


def thr2(kill_event, flag_event):

    def on_press(kill_event, flag_event, key):
        if key == keyboard.KeyCode.from_char('a'):
            print('pressed A')
            if flag_event.is_set():
                flag_event.clear()
            else:
                flag_event.set()
        if key == keyboard.Key.esc:
            print('esc')
            kill_event.set()
            sys.exit(0)

    with keyboard.Listener(on_press=partial(on_press, kill_event, flag_event)) as listen:
        listen.join()


def thr1(kill_event, flag_event):
    while True:
        if kill_event.is_set():
            print('kill')
            sys.exit(0)
        if flag_event.is_set():
            print('looping....')
            sleep(0.4)


if __name__ == "__main__":
    kill_event = Event()
    flag_event = Event()

    thread1 = Process(target=thr1, args=(kill_event, flag_event))
    thread2 = Process(target=thr2, args=(kill_event, flag_event))

    thread1.start()
    thread2.start()

    thread1.join()  # Join processes here to avoid main process exit
    thread2.join()
Jake Hill
  • 76
  • 3