-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathautoclicker.py
More file actions
182 lines (152 loc) · 6.14 KB
/
autoclicker.py
File metadata and controls
182 lines (152 loc) · 6.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
"""Simple autoclicker that clicks where the cursor is placed.
Usage:
- Run: python autoclicker.py
- Press F6 to toggle auto-clicking on/off.
- Press ESC to exit the program.
Notes:
- On macOS you must grant Accessibility permissions to the terminal or Python interpreter
to allow synthetic mouse events (System Settings -> Privacy & Security -> Accessibility).
"""
import threading
import time
import argparse
try:
from pynput import mouse, keyboard
_HAS_PYNPUT = True
except Exception:
# Allow importing the module in environments without pynput (tests, CI, browsers)
mouse = None
keyboard = None
_HAS_PYNPUT = False
import tkinter as tk
from threading import Thread
try:
from plyer import notification
_HAS_PLYER = True
except Exception:
_HAS_PLYER = False
class AutoClicker:
"""AutoClicker clicks the current mouse position at a given interval.
Controls:
- F6: toggle running
- ESC: exit
"""
def __init__(self, interval=0.1):
self.interval = float(interval)
self.running = False
self._thread = None
self._stop_event = threading.Event()
# Only create a real mouse controller if pynput is available.
# Tests can replace this attribute with a dummy controller.
self.mouse_controller = mouse.Controller() if _HAS_PYNPUT else None
self._dry_run = False
def _click_loop(self):
while not self._stop_event.wait(self.interval):
if self.running:
if self._dry_run:
print(f"[dry-run] Click at {time.time():.3f}")
else:
# click at current position (if controller available)
if self.mouse_controller is None:
print("[warning] No mouse controller available; skipping click.")
else:
self.mouse_controller.click(mouse.Button.left, 1)
def start(self):
if self._thread is None or not self._thread.is_alive():
self._stop_event.clear()
self._thread = threading.Thread(target=self._click_loop, daemon=True)
self._thread.start()
def stop(self):
self._stop_event.set()
if self._thread is not None:
self._thread.join()
def toggle(self):
self.running = not self.running
def set_dry_run(self, dry: bool):
"""If dry_run is True, don't perform real clicks — just print simulated clicks."""
self._dry_run = bool(dry)
def main():
parser = argparse.ArgumentParser(description="Simple autoclicker")
parser.add_argument("--interval", "-i", type=float, default=0.1, help="Interval between clicks in seconds")
parser.add_argument("--dry-run", action="store_true", help="Don't perform real clicks; print actions instead")
parser.add_argument("--start", action="store_true", help="Start clicking immediately")
parser.add_argument("--gui", action="store_true", help="Show a small Tkinter GUI")
parser.add_argument("--duration", type=float, default=None, help="If provided, stop after this many seconds")
args = parser.parse_args()
clicker = AutoClicker(interval=args.interval)
clicker.set_dry_run(args.dry_run)
clicker.start()
print("AutoClicker running. Press F6 to toggle clicking, ESC to exit.")
if args.start:
clicker.toggle()
print("Clicking:", "ON" if clicker.running else "OFF")
# If a duration was provided, run for that long then exit
if args.duration is not None:
end_time = time.time() + float(args.duration)
try:
while time.time() < end_time:
time.sleep(0.1)
finally:
clicker.stop()
return
# GUI mode: create a small Tkinter window with a toggle button and status
if args.gui:
def notify(title, msg):
if _HAS_PLYER:
notification.notify(title=title, message=msg)
else:
print(f"[notify] {title}: {msg}")
root = tk.Tk()
root.title("AutoClicker")
status_var = tk.StringVar(value="OFF")
def update_status_label():
status_var.set("ON" if clicker.running else "OFF")
def on_toggle():
clicker.toggle()
update_status_label()
notify("AutoClicker", f"Clicking {'ON' if clicker.running else 'OFF'}")
status_label = tk.Label(root, textvariable=status_var, font=("Helvetica", 16))
status_label.pack(padx=10, pady=10)
toggle_btn = tk.Button(root, text="Toggle", command=on_toggle)
toggle_btn.pack(padx=10, pady=5)
# Run Tkinter in the main thread; but ensure the click loop runs on background thread
try:
root.mainloop()
finally:
clicker.stop()
return
# If pynput keyboard is available, listen to F6/ESC as before.
if _HAS_PYNPUT and keyboard is not None:
def on_press(key):
try:
if key == keyboard.Key.f6:
clicker.toggle()
print("Clicking:", "ON" if clicker.running else "OFF")
elif key == keyboard.Key.esc:
# stop everything
clicker.stop()
print("Exiting...")
return False
except Exception as e:
print("Key handler error:", e)
# listen to keyboard events until ESC pressed
with keyboard.Listener(on_press=on_press) as listener:
listener.join()
else:
# Fallback interactive loop for environments without pynput
try:
print("Interactive mode (no pynput). Type 't' + Enter to toggle, 'q' + Enter to quit.")
while True:
cmd = input().strip().lower()
if cmd == 't':
clicker.toggle()
print("Clicking:", "ON" if clicker.running else "OFF")
elif cmd == 'q':
clicker.stop()
print("Exiting...")
break
except (KeyboardInterrupt, EOFError):
clicker.stop()
print("Exiting...")
if __name__ == "__main__":
main()