So I'm trying to make a program that can hold/autoclick keyboard keys. The problem is, the program doesn't understand the key to press from the variable (obtained from Tkinter Entry) I give it.
When I use pynput to press the key, it says that I haven't given it a value:
AttributeError: 'str' object has no attribute 'value'
When I use pyautogui to press the key, it doesn't do anything, it doesn't even return an error.
Part of the code not working:
#Autoclicking keyboard key (using pynput) (not working)
#hold = whether to autoclick or hold key
#clkdel = delay between keyboard presses
#keyprsd = key to be pressed (tk.StringVar)
#lf7 = listener for when to start/stop
if int(hold.get()) == 1:
print('Starting keyboard pressing...')
while self.run == True:
print('Pressed')
master.after(clkdel, keyct.press(keyprsd.get()))
lf7.stop()
Full code:
import tkinter as tk
from pynput.keyboard import Key, Listener
from pynput.mouse import Button, Controller
from pynput import keyboard
import pyautogui
class GUI:
def __init__(self, master):
#Defining variables
hold = tk.IntVar()
keyprsd = tk.StringVar()
self.run = False
msbt = tk.IntVar()
mousect = Controller()
keyct = Controller()
#Creating main window
master = master
master.title('Key Clicker')
master.geometry('250x250')
master.resizable(False, False)
#Creating radio buttons:
#Autoclick button
self.autoclick = tk.Radiobutton(master, text='Autoclick', variable=hold, value=1)
self.autoclick.grid(row=0, column=0, sticky='en', padx=30, pady=5)
#Hold button
self.holdbt = tk.Radiobutton(master, text='Hold', variable=hold, value=2)
self.holdbt.grid(row=0, column=1, sticky='wn', padx=0, pady=5)
#Creating keyboard button label
self.kbtlabel = tk.Label(master, textvariable=keyprsd, width=10, bg='Light Blue')
self.kbtlabel.grid(row=1, column=0, sticky='wens')
#Creating keyboard button detection:
#Functions to detect key to be pressed
def lforsetkey():
lst = Listener(on_press=ksetcall)
lst.start()
def ksetcall(key):
print('{} was pressed'.format(key))
keyprsd.set(key)
return False
#Functions to detect when to start and stop autoclicking/holding
def f7press(key):
print('{} was pressed'.format(key))
if key == keyboard.Key.f7:
self.run = False
if key == keyboard.Key.f6:
self.run = True
stmouse()
if key == keyboard.Key.f8:
self.run = True
stkey()
def lforf7():
global lf7
lf7 = Listener(on_press=f7press)
lf7.start()
lforf7()
#Creating key selection button
self.kbtsel = tk.Button(master, text='Click and press a key', command=lforsetkey)
self.kbtsel.grid(row=2, column=0, sticky='wen', padx=0)
#Creating list for mouse buttons
self.mslft = tk.Radiobutton(master, text='Left Click', variable=msbt, value=1)
self.mslft.grid(row=1, column=1, sticky='en', padx=10)
self.msrft = tk.Radiobutton(master, text='Right Click', variable=msbt, value=2)
self.msrft.grid(row=2, column=1, sticky='en', padx=10)
#Creating autoclick frequency label
self.clklb = tk.Label(master, text='Autoclick frequency (ms)')
self.clklb.grid(row=3, column=0)
#Creating autoclick frequency entry
self.clkent = tk.Entry(master)
self.clkent.grid(row=3, column=1)
#Creating mouse autoclick button
def stmouse():
self.run = True
lforf7()
try:
clkdel = int(self.clkent.get())
except ValueError:
print('Value Error')
#Autoclicking left button
if int(msbt.get()) == 1 and int(hold.get()) == 1:
print('Starting mouse autoclick...')
while self.run == True:
print('Clicked')
master.after(clkdel, mousect.click(Button.left))
lf7.stop()
#Autoclicking right button
elif int(msbt.get()) == 2 and int(hold.get()) == 1:
print('Starting mouse autoclick...')
while self.run == True:
print('Clicked')
master.after(clkdel, mousect.click(Button.right))
lf7.stop()
#Holding left button
elif int(msbt.get()) == 1 and int(hold.get()) == 2:
print('Starting mouse holding...')
while self.run == True:
pyautogui.mouseDown(button='left')
lf7.stop()
pyautogui.mouseUp(button='left')
#Holding right button
elif int(msbt.get()) == 2 and int(hold.get()) == 2:
print('Starting mouse holding...')
while self.run == True:
pyautogui.mouseDown(button='right')
lf7.stop()
pyautogui.mouseUp(button='right')
else:
print('Error')
self.msbt = tk.Button(master, text='Click to start holding/autoclicking mouse', command=stmouse)
self.msbt.grid(row=4, column=0, columnspan=2, sticky='swe', pady=5)
#Creating keyboard button
def stkey():
#Setting up variables and starting listener
self.run = True
lforf7()
try:
clkdel = int(self.clkent.get())
except ValueError:
print('Value Error')
#Autoclicking keyboard key (using pynput) (not working)
#hold = whether to autoclick or hold key
#clkdel = delay between keyboard presses
#keyprsd = key to be pressed (tk.StringVar)
#lf7 = listener for when to start/stop
if int(hold.get()) == 1:
print('Starting keyboard pressing...')
while self.run == True:
print('Pressed')
master.after(clkdel, keyct.press(keyprsd.get()))
lf7.stop()
#Holding keyboard key (also not working)
elif int(hold.get()) == 2:
print('Holding keyboard...')
while self.run == True:
pyautogui.keyDown(keyprsd.get())
lf7.stop()
print('Stopping')
pyautogui.keyUp(keyprsd.get())
else:
print('Error')
self.kbt = tk.Button(master, text='Click to start holding/autoclicking keyboard', command=stkey)
self.kbt.grid(row=5, column=0, columnspan=2, sticky='swe', pady=5)
root = tk.Tk()
GUI(root)
root.mainloop()
You main problem is that you import only mouse controller
from pynput.mouse import Button, Controller
and you use it for mouse and keyboard
mousect = Controller()
keyct = Controller()
but they have different controllers which work in different way.
Keyboard controller can get string but mouse controller expects object with .value
You should use differenet controlers
from pynput import mouse
from pynput import keyboard
mousect = mouse.Controller()
keyct = keyboard.Controller()
and this resolves main problem.
But there is other problem - after()
(similar to command=
and bind()
) expects function name without arguments and you could use lambda
(like in command=
or bind()
) or you have to use arguments after function name
master.after(clkdel, keyct.press, keyprsd.get())
master.after(clkdel, mousect.click, Button.left)
master.after(clkdel, mousect.click, Button.right)
BTW: other problem is that your code is unreadable. You should move nested function outside class or create class method and then you will no need global
but self.
. You could use also more readable names - ie. key_controller
is more readable then keyct
.
See: PEP 8 -- Style Guide for Python Code
EDIT:
Listener
gives special object with information about key and you put it in StringVar
as string and later you get it from StringVar
as string and use this string in press()
- and it can works for normal keys but not for sprecial keys.
You have to keep original object and use this object in press()
for all keys (normal and special). ie.
def ksetcall(key):
self.key = key # keep original object
print('{} was pressed'.format(key))
keyprsd.set(key)
return False
and later
keyct.press(self.key)
instead of keyct.press(keyprsd.get())