using pynput for key events instead of tkinter - python

Here is my entire program. I want to change from using tkinter with a gui, to using pynput no gui for get_key events part of my code. Can anyone show me what code to use? This code is a talking vending machine that reads from the machine contents list, which is a file that gets updated by the vending machine company.
I dont want to use a gui as there will be no monitor attached. It's a Raspberry.
from gtts import gTTS
import pygame
from io import BytesIO
import sys
import time
import csv
pygame.init()
if sys.version_info[0] == 3:
# for Python3
from tkinter import *
else:
# for Python2
from Tkinter import *
def say(text):
tts = gTTS(text=text, slow=False, lang='en-us', lang_check=False)
fp = BytesIO()
tts.write_to_fp(fp)
fp.seek(0)
pygame.mixer.init()
pygame.mixer.music.load(fp)
pygame.mixer.music.play()
def load_list():
with open(r"/home/pi/VendyLogProject/vendylist.csv", mode="r") as infile:
return sorted(list(csv.reader(infile)))
def refresh_list():
global vl, vl2, baseposition
new_items = load_list()
if vl != new_items:
vl = new_items
vl2 = [item[0] for item in vl]
baseposition = vl[0]
vl = load_list()
vl2 = [item[0] for item in vl]
baseposition = vl[-1] # so when reading through it reads entry 0 first, then 1
def current(event=None):
global baseposition # baseposition was defined outside of the function, therefore we call global
say(baseposition[1]+baseposition[0])
def back(event=None):
global baseposition
currentposition = vl.index(baseposition)
if currentposition == 0:
baseposition = vl[-1]
say(baseposition[1]+baseposition[0])
else:
previousposition = int(currentposition) - 1 # previousposition is previous position
baseposition = vl[previousposition]
say(baseposition[1]+baseposition[0])
def forward(event=None):
global baseposition
currentposition = vl.index(baseposition)
if currentposition == (len(vl) - 1):
baseposition = vl[0]
say(baseposition[1]+baseposition[0])
else:
nextposition = int(currentposition) + 1 # nextposition is next position
baseposition = vl[nextposition]
say(baseposition[1]+baseposition[0])
def readnumber(int):
global vl
for item in vl:
global baseposition
currentposition = vl.index(baseposition)
if int == item[0]:
baseposition = vl[vl.index(item)]
say(baseposition[1]+baseposition[0])
def help():
say("Welcome to Vendy log! Use the plus and minus keys to go through the list of snacks or push a number to hear its contents!")
root = Tk()
prompt = ' VendyLog '
label1 = Label(root, text=prompt, width=len(prompt))
label1.pack()
#keys buffer
keybuf = []
def test_after():
if keybuf:
num = ''.join(keybuf)
keybuf.clear()
def get_key(event):
keybuf.append(event.char)
event.char = ''.join(keybuf)
root.after(500,test_after)
if event.char == '-':
back()
elif event.char == '+':
forward()
elif event.char == '.':
current()
elif event.char in vl2:
readnumber(event.char)
elif event.char == '00':
help()
elif event.char == '462.':
sys.exit()
def refresh_list_and_enqueue_next_refresh():
refresh_list()
root.after(60000, refresh_list_and_enqueue_next_refresh)
refresh_list_and_enqueue_next_refresh()
root.bind_all('<Key>', get_key)
root.mainloop()

I edited this comment because after I played with OpenGL and Pygame I found the answer of how to use pynput with tkinter.
This is the code sample what I write it to test if works.
# // Imports
import tkinter, pynput
from tkinter import messagebox
# // Global variables
# // If we define variable type I found that will speed up execution a little
root:object = tkinter.Tk()
app_title:str = "Tkinter and Pynput"
app_size:tuple = (300, 150)
listener_stop:bool = False
# // Logics Keyboard
class Keyboard:
# On button pressed
# On my case I found the "Fn" button from laptop is not triggered...
#staticmethod
def Pressed(key) -> bool:
# If listener_stop is True then stop listening
if listener_stop: print("Keyboard Events are stoped!"); return False
# Else show pressed key
else: print(f"Keyboard pressed: {key}")
# On button released
#staticmethod
def Released(key) -> None:
print(f"Keyboard released: {key}")
# Listen keybboard buttons
#staticmethod
def Listener() -> None:
k_listen = pynput.keyboard.Listener(on_press=Keyboard.Pressed,
on_release=Keyboard.Released
)
k_listen.start()
# // Logics Mouse
class Mouse:
# On move
#staticmethod
def Move(x, y) -> bool:
# If listener_stop is True then stop listening
if listener_stop: print("Mouse Events are stoped!"); return False
else: print(f"Mouse: Moved to {x}x{y}")
# On scroll
#staticmethod
def Scroll(x, y, dx, dy) -> None:
where = "down" if dy < 0 else "up"
print(f"Mouse: Scrolled {where} at {x}x{y}")
# On click
# On my case I found mouse wheel press is not triggered...
#staticmethod
def Click(x, y, button, pressed) -> None:
action = "pressed" if pressed else "released"
print(f"Mouse: {button} was {action} at {x}x{y}")
# Listen keybboard buttons
#staticmethod
def Listener() -> None:
m_listen = pynput.mouse.Listener(on_move=Mouse.Move,
on_click=Mouse.Click,
on_scroll=Mouse.Scroll
)
m_listen.start()
# // Logics Define GUI
class MainApp:
def __init__(self, master):
self.master = master
# Create tkinter interface
self.X = (self.master.winfo_screenwidth() - app_size[0]) // 2
self.Y = (self.master.winfo_screenheight() - app_size[1]) // 2
self.master.wm_title(app_title)
self.master.wm_geometry(f"{app_size[0]}x{app_size[1]}+{self.X}+{self.Y}")
# Magic hapen here :P
self.Screen(self.master)
self.InputEvents()
# Define Screen Informations
def Screen(self, root) -> None:
# Set the main frame
self.frm_main = tkinter.Frame(root)
self.frm_main.pack(expand=True, fill="x", side="top")
# Defain frame components
self.title = tkinter.Label(self.frm_main, text=app_title, font=("Comic Sans MS", 18, "bold"), fg="tomato")
self.title.pack(expand=False, fill="x", side="top")
# Input events
def InputEvents(self) -> None:
Keyboard.Listener()
Mouse.Listener()
# Safe Quit
def SafeQuit(self, master:object = root) -> None:
global listener_stop
if messagebox.askokcancel(f"{app_title} Quit", f"Are you shore you want to quit {app_title}?"):
# We need to make shore if the window was closed and
# listening event are still runing, then make them stop
# You will see in fact they are not stoped (from console point view)
# for that reason we check listener_stop on Mouse Move and on Keyboard Key press.
# If for some reason the app is quit and listener has not stoped then on first action did will stop
# Mouse move after app quit >> Mouse listener will stop
# Keyboard button pressed after app quit >> Keyboard listener will stops
if listener_stop == False:
listener_stop = True
print("Events Listening are stoped!")
master.destroy()
# // Run if this is tha main file
if __name__ == "__main__":
app:object = MainApp(root)
root.protocol("WM_DELETE_WINDOW", app.SafeQuit)
root.mainloop()
I updated the code again with stop lisening keyboard and mouse event.
PS: An updated version of them can be found on my github

Related

Detect NO KEY PRESSES with keyboard module python

I'm making a tkinter program and want to detect a key press and do something when there is, however if no key is pressed the keyboard.read_key() will not execute and not let any other code run until a key is pressed.
Is there an alternative I can use or a way to check if no key was pressed?
def on_press():
global running
global turn
if keyboard.read_key() == 'q':
ws.destroy()
elif keyboard.read_key() == 'a':
if turn:
print('turning off')
running = False
turn = False
else:
print('turning on')
running = True
turn = True
start()
else:
start()
def start():
global running
global turn
print('start')
if running:
print('clicking')
mouse.click(Button.left, cps_val)
time.sleep(1)
on_press()
on_press()
Key is no pressed for the most of the time. Because computer is fast and human is slow so I would assumed that that key is no pressed at the beginnig and I would run code at once (without checking if it no pressed). And I would run "clicking code" all time in while at the beginning and I would use keys only to pause this loop (key a) or exit this loop (key q) - without checking if key is no pressed.
import keyboard
#import mouse
import time
# --- functions ---
def pause():
global turn
if turn:
print('turning off')
turn = False
else:
print('turning on')
turn = True
def finish():
global running
running = False
def main():
global counter
print('start')
while running:
if turn:
counter += 1
print('clicking', counter)
#mouse.click(Button.left, cps_val)
time.sleep(1)
# --- main ---
turn = True
running = True
counter = 0
keyboard.add_hotkey('q', finish)
keyboard.add_hotkey('a', pause)
main()
If it has to run with tkinter then while loop will block tkinter (tkinter will freeze because it will have no time to get key/mouse events from system, send them to widgets, update widgets, and redraw widgets in window).
I would use tkinter.after(milliseconds, function) instead of sleep() and while and tkinter will have time to work.
import tkinter as tk
import keyboard
#import mouse
import time
# --- functions ---
def pause():
global turn
if turn:
print('turning off')
turn = False
else:
print('turning on')
turn = True
def finish():
global running
running = False
root.destroy()
def main():
print('start')
loop() # run first time at once
#root.after(1000, loop) # run first time after 1s
def loop():
global counter
if turn:
counter += 1
#print('clicking', counter)
label['text'] = f'clicking {counter}'
#mouse.click(Button.left, cps_val)
if running:
root.after(1000, loop) # run again after 1s
# --- main ---
turn = True
running = True
counter = 0
keyboard.add_hotkey('q', finish)
keyboard.add_hotkey('a', pause)
root = tk.Tk()
label = tk.Label(root, text='clicking 0')
label.pack()
main()
root.mainloop()

Is there a way to bypass the need to reference self in an argument (Python)

The purpose of my code is to create and print a list of all the keyboard and mouse interactions that occur. The code was working, but then I decided to convert it into a class. From there, I was not able to reference the functions because it considered self to be the first argument.
Here is the code with the class that does not work.
import pyautogui
from pynput.mouse import Listener
MouseListener = Listener
from pynput.keyboard import Listener
KeyboardListener = Listener
from pynput import keyboard
######################
#
# m = move
# p = mouse press
# r = mouse release
# d = scroll down
# u = scroll up
# 1 = key down
# 2 = key up
#
######################
temp = []
class Recorder():
#record mouse movement
def on_move(self, x, y):
temp.append('m{0}'.format(
(x, y)))
#record mouse click
def on_click(self, x, y, button, pressed):
temp.append('{0}{1}'.format(
'p' if pressed else 'r',
(x, y)))
#record mouse scroll
def on_scroll(self, x, y, dx, dy):
temp.append('{0}{1}'.format(
'd' if dy < 0 else 'u',
(x, y)))
#record keyboard press
def on_press(self, key):
try:
temp.append('1({0})'.format(
key.char))
except AttributeError:
temp.append('1({0})'.format(
key))
#record keyboard release and end if esc
def on_release(self, key):
temp.append('2({0})'.format(
key))
if key == keyboard.Key.esc:
# Stop listener
print(temp)
keyboard_listener.stop()
mouse_listener.stop()
return False
mouse_listener = MouseListener(
on_move=Recorder.on_move,
on_click=Recorder.on_click,
on_scroll=Recorder.on_scroll)
keyboard_listener = KeyboardListener(
on_press=Recorder.on_press,
on_release=Recorder.on_release)
keyboard_listener.start()
mouse_listener.start()
keyboard_listener.join()
mouse_listener.join()
And below is the code that performs properly.
import pyautogui
from pynput.mouse import Listener
MouseListener = Listener
from pynput.keyboard import Listener
KeyboardListener = Listener
from pynput import keyboard
##########
#
# m = move
# p = mouse press
# r = mouse release
# d = scroll down
# u = scroll up
# 1 = key down
# 2 = key up
#
##########
temp = []
#mouse recorder
def on_move(x, y):
temp.append('m{0}'.format(
(x, y)))
def on_click(x, y, button, pressed):
temp.append('{0}{1}'.format(
'p' if pressed else 'r',
(x, y)))
def on_scroll(x, y, dx, dy):
temp.append('{0}{1}'.format(
'd' if dy < 0 else 'u',
(x, y)))
#keyboard recorder
def on_press(key):
try:
temp.append('1({0})'.format(
key.char))
except AttributeError:
temp.append('1({0})'.format(
key))
def on_release(key):
temp.append('2({0})'.format(
key))
if key == keyboard.Key.esc:
# Stop listener
print(temp)
keyboard_listener.stop()
mouse_listener.stop()
return False
#activation
mouse_listener = MouseListener(
on_move=on_move,
on_click=on_click,
on_scroll=on_scroll)
keyboard_listener = KeyboardListener(
on_press=on_press,
on_release=on_release)
keyboard_listener.start()
mouse_listener.start()
keyboard_listener.join()
mouse_listener.join()
You can use the decorator #staticmethod for this. Add #staticmethod above the functions in question. For example:
class Recorder:
#record mouse movement
#staticmethod
def on_move(x, y,temp):
temp.append('m{0}'.format((x, y)))
You can now use the function without instantiating an object.
Call the function with:
Recorder.on_move(x,y,temp)
Documentation

After() method in tkinter for timer

I want to start a timer when the user clicks a button for the first time in my number click game. I tried to use the after method for this, but when I click a button, the timer stays at 0. The rest of the code works fine without any error messages.
Here's the code:
import tkinter as tk
from random import randint
# create window
window = tk.Tk()
window.title('Clicker')
# create list for random numbers
new_list = []
# define time count:
def time_event():
global current_time, after_id
if clock_started:
current_time += 1
clock["text"] = str(current_time)
after_id = clock.after(1000, time_event)
# define click event
def click_event(event):
global clock_started
new_button = event.widget
clicked_val = int(new_button["text"])
clock_started = True
if not clock_started:
clock_started = True
clock.after(1000, time_event)
if clicked_val == new_list[0]:
del new_list[0]
new_button["state"] = tk.DISABLED
if len(new_list) == 0:
clock.started = False
clock.after_cancel(after_id)
# create buttons
for i in range(25):
new_num = randint(1, 999)
while i in new_list:
new_num = randint(1, 999)
new_list.append(new_num)
new_list.sort()
new_button = tk.Button(window, text=new_num)
new_button.grid(column=i // 5, row=i % 5)
new_button.bind("<Button-1>", click_event)
# create clock
current_time = 0
clock = tk.Label(window, text=str(current_time))
clock.grid(column=2, row=6)
clock_started = False
# run game
window.mainloop()
In your code, clock_started has been initialized to True which implies that this condition if not clock_started: will not be satisfied to begin with and hence the timer doesn't work without giving an error. Your final click_event(event) should look like this:
def click_event(event):
global clock_started
new_button = event.widget
clicked_val = int(new_button["text"])
clock_started = False
if not clock_started:
clock_started = True
clock.after(1000, time_event)
if clicked_val == new_list[0]:
del new_list[0]
new_button["state"] = tk.DISABLED
if len(new_list) == 0:
clock.started = False
clock.after_cancel(after_id)

Convert Hold Left Click to Multiple Left Clicks

I am trying to write a python program that would convert left click being held down to multiple left clicks. Essentially it just spams left click when left click is pressed down. I have written code that does this except that it gets stuck in a loop of because it triggers itself with the left clicks it sends. Here is my code:
from pynput.mouse import Listener
from threading import Thread
import pyautogui
import time
flag = False
def clicking():
while flag:
time.sleep(0.5)
pyautogui.click(button='left')
print("Clicking...")
def clicked(x, y, button, pressed):
global flag
if pressed == True:
if button == button.left:
print("Left Click")
flag = True
thread = Thread(target=clicking)
thread.start()
else:
flag = False
with Listener(on_click=clicked) as listener:
listener.join()
How would I modify this code to stop if from triggering itself and getting stuck in a loop. Thanks!
I use this code as an autoclicker you may be able to do some adjustments to set a listener to a left click.
import time
import threading
from pynput.mouse import Button, Controller
from pynput.keyboard import Listener, KeyCode, Key
def on_press(key):
if key == start_stop_key:
if click_thread.running:
click_thread.stop_clicking()
else:
click_thread.start_clicking()
elif key == exit_key:
click_thread.exit()
listener.stop()
class ClickMouse(threading.Thread):
def __init__(self, delay, button):
super(ClickMouse, self).__init__()
self.delay = delay
self.button = button
self.running = False
self.program_running = True
def start_clicking(self):
self.running = True
def stop_clicking(self):
self.running = False
def exit(self):
self.stop_clicking()
self.program_running = False
def run(self):
while self.program_running:
while self.running:
mouse.click(self.button)
time.sleep(self.delay)
time.sleep(0.1)
start_stop_key = KeyCode(char='+')
#key to start
exit_key = KeyCode(char='ยก')
#key to stop the iteration
button = Button.left
#Either Button.left or Button.right
delay = 0.1
#Time between clicks
click_thread = ClickMouse(delay, button)
mouse = Controller()
click_thread.start()
with Listener(on_press = on_press) as listener:
listener.join()
Good luck

"after" looping indefinitely: never entering mainloop

This is my first post. I started coding when considering a career swap two months ago and am working on a Tetris clone. I've implemented most of the core features, but cannot get the game to refresh continually with an after loop.
I'm using Tkinter to produce my Gui and am trying out event oriented programming.
My understanding is that after(Time, Event) from Tkinter should schedule whatever the Event callback function is to occur after a delay specified by Time. I think that the code is supposed to continue executing subsequent items after this.
My frame refresh function (game.updateBoard()) does most of the necessary events for tetris to work, then calls itself using after. I call it once when initializing an instance of the game.
Instead of proceeding to mainloop(), the game.updateboard() function calls itself via after indefinitely.
I suspect that it is not behaving how I thought after worked which would be to continue to execute the script until the specified delay occurs. I think it is waiting for the callback to terminate to continue.
I tried to find a resource on this but could not.
If you have suggestions for fixing this question, the attached code, or for coding in general, I am very happy to hear them! This is a learning process and I'll gladly try pretty much anything you suggest.
Here is the relevant portion of the code:
class game():
def __init__(self): #Set up board and image board
self.pieces = ["L","J","S","Z","T","O","I"]
self.board = boardFrame()
self.root = Tk()
self.root.title("Tetris")
self.root.geometry("250x525")
self.frame = Frame(self.root)
#set up black and green squares for display
self.bSquare = "bsquare.gif"
self.gSquare = "square.gif"
self.rSquare = "rsquare.gif"
self.image0 = PhotoImage(file = self.bSquare)
self.image1 = PhotoImage(file = self.gSquare)
self.image2 = PhotoImage(file = self.rSquare)
#get an initial piece to work with
self.activeBlock = piece(self.pieces[random.randint(0,6)])
#Tells program to lower block every half second
self.blockTimer = 0
self.updateBoard()
self.root.bind('<KeyPress-Up>', self.turn)
self.root.bind('<KeyPress-Right>', self.moveR)
self.root.bind('<KeyPress-Left>', self.moveL)
self.root.bind('<KeyPress-Down>',self.moveD)
print("Entering mainloop")
self.root.mainloop()
def turn(self, event):
self.activeBlock.deOccupy(self.board)
self.activeBlock.turn()
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveR(self, event):
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([1,0], self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveL(self, event):
if self.activeBlock.checkLeft(self.board) == False:
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([-1,0], self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveD(self, event): #find
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([0,-1],self.board)
if self.activeBlock.checkBottom(self.board) == True:
self.activeBlock.occupy(self.board)
self.activeBlock = piece(self.pieces[random.randint(0,6)])
## self.activeBlock = piece(self.pieces[1])
print("bottomed")
self.activeBlock.occupy(self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def drawGrid(self, dGrid):
#Generate squares to match tetris board
for widget in self.frame.children.values():
widget.destroy()
self.activeBlock.occupy(self.board)
for x in range(9,-1,-1):
for y in range(20,-1,-1):
if self.board.grid[x][y] == 1:
self.frame.displayA = Label(self.frame, image=self.image1)
## self.frame.displayA.image = self.image1
self.frame.displayA.grid(row=21-y, column=x)
else:
self.frame.displayA = Label(self.frame, image = self.image0)
## self.frame.displayA.image = self.image0
self.frame.displayA.grid(row=21-y, column=x)
self.frame.displayA = Label(self.frame, image = self.image2)
self.frame.displayA.grid(row = 21 - self.activeBlock.center[1], column = self.activeBlock.center[0])
self.frame.grid()
def updateBoard(self):
self.blockTimer += 1
"print updateBoard Loop"
## 1)check for keyboard commands
#1.1 move block by keyboard commands
#2) see if block has bottomed out, if it has, have it enter itself into the grid and generate a new block.
if self.activeBlock.checkBottom(self.board) == True:
self.activeBlock.occupy(self.board)
self.activeBlock = piece(self.pieces[random.randint(0,6)])
print("bottomed")
self.activeBlock.occupy(self.board)
#2.2 - if block has not bottomed and 50 frames (~.5 seconds) have passed, move the active block down a square after clearing its old space.
elif self.blockTimer%12 == 0:
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([0,-1], self.board)
self.activeBlock.occupy(self.board)
## 4) check for filled rows
for y in range(1,21):
for x in range(10):
rowFull = True
if self.board.grid[x][y] == 0:
rowFull == False
#4.1 if any row is filled, delete it and then move all rows above the deleted row down by one
if rowFull == True:
for x2 in range(10):
self.board.grid[x2][y] = 0
for y2 in range(y+1,21):
if self.board.grid[x2][y2] == 1:
self.board.grid[x2][y2] = 0
self.board.grid[x2][y2-1] = 1
#4.11 if the row is full and the row above it was full, delete the row again as well as the row above it, and move all rows down by 2
for x in range(10):
rowFull = True
if self.board.grid[x][y] == 0:
rowFull == False
if rowFull == True:
for x2 in range(10):
try:
self.board.grid[x2][y] = 0
self.board.grid[x2][y+1] = 0
except:
pass
for y2 in range(y+2,21):
try:
if self.board.grid[x2][y2] == 1:
self.board.grid[x2][y2] = 0
self.board.grid[x2][y2-2] = 1
except:
pass
#5) if there is a block in the top row, end the game loop
for x in range(10):
if self.board.grid[x][20] == 1:
game = "over"
#6) update image
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
self.frame.after(500, self.updateBoard())
Game = game()
You want to do self.frame.after(500, self.updateBoard).
The difference here is subtle, (self.updateBoard instead of self.updateBoard()). In your version, you're passing the result of your function to the after method instead of passing the function. This results in the infinite recursion that you described.

Categories