0

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()
MaLoLHDX
  • 3
  • 4
  • always put full error message (starting at word "Traceback") in question (not comment) as text (not screenshot). There are other useful information. – furas Jul 11 '20 at 16:16
  • error shows that you have string and you shouldn't use `.value` – furas Jul 11 '20 at 16:17
  • BTW: instead `int(hold.get()) == 1` you can compare strings `hold.get() == "1"` – furas Jul 11 '20 at 16:19
  • BTW: very big mistake - `after()` (similar to `command=` and `bind()`) needs function's name without `()` - if you need function with arguments then use `after(..., function, arg1, arg2, ...)`. At this moment it executes this function at start and it uses result from function in `after()` – furas Jul 11 '20 at 16:25
  • your code is unreadable - you should move nested function outside class or create class methods instead of nested function – furas Jul 11 '20 at 16:29
  • your problem can be that you assign the same mouse controllor to `mousect = Controller()` and `keyct = Controller()` but you should use different controllers - `pynput.keyboard.Controller` and `pynput.mouse.Controller`. These controllers are different and they may expect different values. Keyboard controller may expect string but mouse controllre may expect object with `.value` – furas Jul 11 '20 at 16:37

1 Answers1

2

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

furas
  • 134,197
  • 12
  • 106
  • 148
  • So, I've applied all your improvements and it works! Although I still have another problem: I can't autoclick/hold keys like ctrl, shift, etc... Pynput throws out a Value error, pyautogui doesn't do anything. Full code here: https://pastebin.com/LK4X2HGA – MaLoLHDX Jul 13 '20 at 09:23
  • what error? You could remove `try/except` to see full error. Or at least you should use `except ValueError as ex: print(ex)` to see something more. – furas Jul 13 '20 at 13:31
  • I have removed the try/except, but it gives me the same details of the error: https://pastebin.com/sqZGPciw – MaLoLHDX Jul 13 '20 at 13:52
  • `Listener` gives you special object with information about key and you put it in `Entry` as string and later you get it from `Entry` 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(self, key): self.key = key ....` and later `press(self.key)`, `release(self.key)` – furas Jul 13 '20 at 14:50