Search code examples
pythonkeyboardtkinter-entrypyautoguipynput

Cannot get keyboard key from variable


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

Solution

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