pythonmultithreadingtkinterpynput

How to make pynput.keyboard.listener thread keep running in the background even when main app is terminated


I have a main GUI app made out of Tkinter, which includes a button called "start" , which when pressed initiates the pynput.keyboard.listener thread .

Its listens to the keystrokes all right .

But when I terminate the app (by clicking 'x') , the keystrokes listener thread also terminates .

I want the thread to be persistent and keep listening , even when the GUI is closed .

Here is the code .

counter.py


import pynput.keyboard
import threading
from threading import Event
import ctypes

class KeypressCounter(threading.Thread):
    
    def __init__(self):

        # import pynput.keyboard
        # import threading
        # from threading import Event
        # import ctypes

        super().__init__()
        self.key_count = 0 
        self.stop_event = Event()

    def run(self):
        listener = pynput.keyboard.Listener(on_press=self.on_press,daemon=False)
        listener.start()
    def on_press(self, key):
        self.key_count += 1
        if key == pynput.keyboard.Key.esc:
            print("esc pressed") 
            self.on_endsession()
        print("counter:",self.key_count)    
    def on_endsession(self):
        with open("key_count.txt", "w") as f:
            print("writing to file")
            f.write(str(self.key_count))

        self.stop_event.set()

def main():
    keypress_counter = KeypressCounter()
    keypress_counter.start()

    while True:
        if keypress_counter.stop_event.is_set():
            break                    
        

if __name__ == "__main__":
    main()

The tkinter frame , app.py


from tkinter import *
from counter import KeypressCounter 

def start() :
      KeyCounter  = KeypressCounter()
      KeyCounter.start()


window_size = '1200x600'

    # Create the root window
window = Tk()


window.geometry(window_size)

keyBoardFrame  = Frame(window,width=100,height=100,bg='green')

button  = Button(keyBoardFrame,text='start',bg='red',fg='white',command = start)
button.grid(row=0,column=0,sticky='nsew')

window.mainloop()

The counter is written to key_count.txt before it terminates(don't know how ) .

And after window is closed, nothing gets written to the key_count.txt .

but I want the thread to keep listening to all the keystrokes in the background .


Solution

  • Since the listener is a daemon thread, it will be terminated when the main thread terminates.

    You can change the listener to a non-daemon thread so that the main thread will wait for it after closing the main window. Also KeypressCounter does not need to inherit from threading.Thread in this case:

    class KeypressCounter:
        def __init__(self):
            self.key_count = 0
    
        def start(self):
            # change listener to self.listener so that it can be accessed in other class methods
            self.listener = pynput.keyboard.Listener(on_press=self.on_press)
            self.listener.daemon = False  # set it to a non-daemon thread
            self.listener.start()
    
        def on_press(self, key):
            self.key_count += 1
            if key == pynput.keyboard.Key.esc:
                print("esc pressed")
                self.on_endsession()
            print("counter:",self.key_count)
    
        def on_endsession(self):
            with open("key_count.txt", "w") as f:
                print("writing to file")
                f.write(str(self.key_count))
    
            # terminate the listener
            self.listener.stop()