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.
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()