pythontkinterautomationpynput

Pynput Autoclicker with Tkinter GUI wont terminate


So, I'm trying to build an autoclicker with pynput but when i close it doesn't terminate and it bothers me so much. I used auto-py-to-exe and it makes multiple programs open if you inspect in the task manager on windows. Like it wont close.

Here's the code:

from random import random
from threading import Thread
from time import sleep
from pynput.mouse import Controller, Button
from pynput.keyboard import KeyCode, Key, Listener
import tkinter as tk
from tkinter import PhotoImage, ttk
import sys

# Autoclicker Logic

mouse = Controller()

# Autoclicker Class


class Autoclicker(Thread):

    # Variables

    def __init__(self, delay, key):
        super().__init__()
        self.delay = delay
        self.key = key
        self.clicking = False

    # Clicker

    def run(self):
        while True:
            if self.clicking:
                mouse.click(Button.left)
            sleep(self.delay * random() + 0.5 * self.delay)


# Keypresses

def keypress(key):
    if key == autoclicker.key:
        autoclicker.clicking = not autoclicker.clicking

# Delay Logic


def update_delay(val):
    delay_label.config(text=f"Delay: {float(val):.2f}s")
    autoclicker.delay = float(val)

# Updtae the keys


def update_key(key):
    autoclicker.key = key


# Initialize autoclicker with default delay of 0.1s and key "Tab"
autoclicker = Autoclicker(delay=0.1, key=Key.tab)
autoclicker.start()

# Create tkinter window and widgets
window = tk.Tk()
window.title("AutoClicker")
window.geometry("400x150")

# Delay

delay_label = tk.Label(window, text=f"Delay: {autoclicker.delay:.2f}s")
delay_label.pack()

delay_slider = tk.Scale(window, from_=0.01, to=1.0, resolution=0.01, orient=tk.HORIZONTAL,
                        length=300, label="Delay (seconds)", command=update_delay)
delay_slider.set(autoclicker.delay)
delay_slider.pack()

# Start/ Stop Logic

key_options = ["Tab", "Shift", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8",
               "F9", "F10", "F11", "F12", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
key_label = tk.Label(window, text="Key to Start/Stop:")
key_label.pack()

key_var = tk.StringVar(window)
key_var.set(key_options[0])

key_dropdown = tk.OptionMenu(window, key_var, *key_options,
                             command=lambda key: update_key(getattr(Key, key.lower(), Key.tab)))
key_dropdown.pack()

# Icon

icon = PhotoImage(file='logo.png')
window.iconphoto(False, icon)

# Start key listener
with Listener(on_press=keypress) as listener:
    # Start tkinter event loop

    window.mainloop()

I tried quit(), exit() and sys.exit() but nothing worked.

Please try the code to see what I mean. Its not easy to understand in text. Like I run close the GUI Window but the terminal is still open. Not even Chat GPT knows. Please masterminds of stackoverflow help once again.


Solution

  • In a way you have two separate event loops running, one of them is the window.mainloop and the other is the while True: loop in the Autoclicker.run method. So when you click the close window button you are only destroying one of those loops while the other is left running.

    One way you can avoid this is to not use a while True loop in your thread and instead assign it to run while some state flag is True that can be turned off externally when you wish to shut down the app. Then you can use your window protocol handler to manually shutdown the thread when the close button is clicked.

    For example:

    from random import random
    from threading import Thread
    from time import sleep
    from pynput.mouse import Controller, Button
    from pynput.keyboard import KeyCode, Key, Listener
    import tkinter as tk
    from tkinter import PhotoImage, ttk
    import sys
    
    # Autoclicker Logic
    
    mouse = Controller()
    
    # Autoclicker Class
    
    
    class Autoclicker(Thread):
    
        # Variables
    
        def __init__(self, delay, key):
            super().__init__()
            self.delay = delay
            self.key = key
            self.clicking = False
            self._active = False  # controls the state of the thread loop
    
        # Clicker
    
        def run(self):
            self._active = True  # set it to True at first run
            while self._active:    
                if self.clicking:
                    mouse.click(Button.left)
                sleep(self.delay * random() + 0.5 * self.delay)
    
        def off(self):   # this can be run externally to stop the thread
            self._active = False
    
    # Keypresses
    
    def keypress(key):
        if key == autoclicker.key:
            autoclicker.clicking = not autoclicker.clicking
    
    # Delay Logic
    
    
    def update_delay(val):
        delay_label.config(text=f"Delay: {float(val):.2f}s")
        autoclicker.delay = float(val)
    
    # Updtae the keys
    
    
    def update_key(key):
        autoclicker.key = key
    
    
    # Initialize autoclicker with default delay of 0.1s and key "Tab"
    autoclicker = Autoclicker(delay=0.1, key=Key.tab)
    autoclicker.start()
    
    # Create tkinter window and widgets
    window = tk.Tk()
    window.title("AutoClicker")
    window.geometry("400x150")
    
    # Delay
    
    delay_label = tk.Label(window, text=f"Delay: {autoclicker.delay:.2f}s")
    delay_label.pack()
    
    delay_slider = tk.Scale(window, from_=0.01, to=1.0, resolution=0.01, orient=tk.HORIZONTAL,
                            length=300, label="Delay (seconds)", command=update_delay)
    delay_slider.set(autoclicker.delay)
    delay_slider.pack()
    
    # Start/ Stop Logic
    
    key_options = ["Tab", "Shift", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8",
                   "F9", "F10", "F11", "F12", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
    key_label = tk.Label(window, text="Key to Start/Stop:")
    key_label.pack()
    
    key_var = tk.StringVar(window)
    key_var.set(key_options[0])
    
    key_dropdown = tk.OptionMenu(window, key_var, *key_options,
                                 command=lambda key: update_key(getattr(Key, key.lower(), Key.tab)))
    key_dropdown.pack()
    
    # Icon
    
    icon = PhotoImage(file='L5etY.jpg')
    window.iconphoto(False, icon)
    
    def on_closing():   
        autoclicker.off()   # this will turn off the clicker
        autoclicker.join()  # wait for it to stop
        window.destroy()    # then destroy the window
    
    # This will trigger the callback when the close button is pressed
    window.protocol("WM_DELETE_WINDOW", on_closing)  
    
    # Start key listener
    with Listener(on_press=keypress) as listener:
        # Start tkinter event loop
    
        window.mainloop()