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()
Related
I'm making a game via Python on a Raspberry Pi. I'm using the GPIOs to light up an LED and detect a button switch.
I wanted to incorporate an ESC on the keyboard so we can exit at any time.
But whenever I add in the ESC key code into the main while loop. It doesn't work. The LED and Buttons work, but when I press on the ESC key, it doesn't do anything.
The loop runs to refresh/run a stopwatch and listen to an LED button via the GPIO.
I wanted some advice on how things like ESC key are handled in games. Especially with fast paced games where the loop and cycles are very fast.
Any tips or suggestions would be greatly appreciated. Thanks in advance.
Please see the code below:
# Importing all libraries
import RPi.GPIO as GPIO
import sys, time, atexit, pygame
# Setup GPIO and Pygame
GPIO.setmode(GPIO.BCM)
pygame.init()
# Define Tuples and Variables
leds = (16,17,22,9,5)
switches = (19,4,27,10,11)
button_pressed = False
taskcomplete = False
# Pygame visual variables
screen = pygame.display.set_mode( (1024,240) )
counterfont = pygame.font.Font('DSEG14Modern-Regular.ttf', 70)
# Set Pygame refresh rate variable = clock
clock = pygame.time.Clock()
# Clock variables
sec_val = 0
sec = 0
mins = 0
hours = 0
# Status variables
paused = False
running = True
# Start the clock
start_time = pygame.time.get_ticks()
# Defining Functions
# Function that renders segment display on screen
def time_convert(sec):
sec = sec % 60
sec_val = ("Timer: {0}".format(round((sec), 2)))
counting_text = counterfont.render(str(sec_val), 3, (134,145,255))
counting_rect = counting_text.get_rect(left = screen.get_rect().left)
screen.fill( (0,0,0) )
screen.blit(counting_text, (300,40))
pygame.display.update()
# Stopwatch function to compute for a SS:MS based stopwatch
def stop_Watch():
end_time = time.time()
time_lapsed = end_time - start_time
sec_val = time_convert(time_lapsed)
# Press Button 1 to start the game
def but_3():
while GPIO.input(switches[2]) == GPIO.LOW:
GPIO.output(leds[2],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[2],False)
print(" Button 3 is pressed! Exit")
start_time = time.time()
def buttonPress(channel):
# This function gets called every time a button is pressed, if the button pressed is the same as the button
# that is illuminated, then we set the "correct_button" variable to True,
# otherwise we set the "incorrect_button" variable to True.
# We need to set some variables to global so that this function can change their value.
button_pressed = True
def exit():
# This function gets called when we exit our script, using Ctrl+C
print("GPIO Clean Up!")
GPIO.cleanup()
pygame.quit()
# This tells our script to use the "exit()" without this, our "exit()" function would never be called.
atexit.register(exit)
#Loop through the leds to set them up
for led in leds:
# Set the led to be an ouput
GPIO.setup(led, GPIO.OUT)
# Turn the led off
GPIO.output(led,False)
# Loop through the switches to set them up
for switch in switches:
# Set the switch to be an input
GPIO.setup(switch, GPIO.IN)
# Add rising edge detection
GPIO.add_event_detect(switch, GPIO.RISING, bouncetime=300)
# Add the function "buttonPress" to be called when switch is pressed.
GPIO.add_event_callback(switch, buttonPress)
# Main sequence code
# Setup Pygame refresh rate to 120 fps
clock.tick(120)
# Start timer
start_time = time.time()
# Main loop
while running:
# Press Button 1 to start the game
while GPIO.input(switches[0]) == GPIO.LOW:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
print("escape pressed")
running = False
GPIO.output(leds[0],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[0],False)
print(" Button 1 is pressed! Exit")
running = False
exit()
It's because it's in another while loop I'm guessing, so it's not in the running while loop anymore. You can add pygame.quit() to make it quit that way though:
# Main loop
while running:
# Press Button 1 to start the game
while GPIO.input(switches[0]) == GPIO.LOW:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
print("escape pressed")
pygame.quit()
running = False
GPIO.output(leds[0],True)
time.sleep(0.01)
stop_Watch()
GPIO.output(leds[0],False)
print(" Button 1 is pressed! Exit")
running = False
exit()
Or, since you have a function named exit() that does the same thing, you can add exit() to those places instead.
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
For the code:
from turtle import Screen, Turtle, bgcolor
# --- functions ---
def delSec(string):
if len(string) == 1:
return "0" + string
else:
return string
def tick():
global milisecs, ticking
turtle.clear()
if milisecs < 0:
turtle.write("TIMER DONE", align='center', font=FONT)
ticking = False
return
else:
turtle.write(delSec(str(milisecs//(60*60*10)))+":"+delSec(str((milisecs%(60*60*10))//(60*10)))+":"+delSec(str((milisecs%(60*10))//10))+":"+str(milisecs%10), align='center', font=FONT)
## turtle.write(delSec(str(milisecs//(60*60)))+":"+delSec(str((milisecs%(60*60))//(60)))+":"+delSec(str((milisecs%(60))//1)), align='center', font=FONT)
if not paused:
milisecs -= 1
screen.ontimer(tick, 100)
def reset():
global milisecs, ticking, key_reset, key_pause, key_both
#global paused
print("reset")
screen.onkey(None, key_reset) # Disable event handler inside handler
screen.onkey(None, key_pause) # Disable event handler inside handler
screen.onkey(None, key_both)
milisecs = sum(time*10)
if not ticking:
ticking = True
tick()
#paused = False
screen.onkey(reset, key_reset) # Reenable event handler
screen.onkey(pause, key_pause) # Reenable event handler
screen.onkey(reset, key_both)
screen.onkey(pause, key_both)
def pause():
global paused
print("pause/unpause")
paused = not paused
# --- main ---
bgcolor('dodgerblue')
FONT = ("Arial", 60, "normal")
strings = input("Please enter the time: ").strip().split(' ')
time = [60 ** (len(strings) - index - 1) * int(unit) for index, unit in enumerate(strings)]
milisecs = -1
ticking = False
paused = False
key_reset = "r"
key_pause = "p"
key_both = "b"
screen = Screen()
turtle = Turtle()
turtle.hideturtle()
turtle.color('white')
reset()
screen.listen()
screen.mainloop()
I want to both pause and reset the timer when I click the key "b". I tried doing this by running both functions reset and pause. However, as expected, it overwrites the first command of running the first function and only runs the second function when I click b (in this case, the function pause) I also tried the line screen.onkey(pause, reset, key_both), but that created an error.
Could someone help me figure out how to run multiple functions? I can't find any parameter of .onkey() where it doesn't disable previous key-based functions. Please help!
Frustratingly, what you're asking for is built-in to the mouse event functions:
onclick(fun, btn=1, add=None)
where the add argument is used to indicate whether fun overrides the current setting or simply adds another function to execute in this circumstance. However, this wasn't included on the various onkey*() event handlers.
Below is what I consider a generic way to go about this:
from turtle import Screen
KEY_RESET = 'r'
KEY_PAUSE = 'p'
KEY_BOTH = 'b'
def reset():
screen.onkey(None, KEY_RESET) # Disable event handlers inside handler
screen.onkey(None, KEY_BOTH)
print("reset")
screen.onkey(reset, KEY_RESET) # Reenable event handlers
screen.onkey(both, KEY_BOTH)
def pause():
screen.onkey(None, KEY_PAUSE) # Disable event handlers inside handler
screen.onkey(None, KEY_BOTH)
print("pause")
screen.onkey(pause, KEY_PAUSE) # Reenable event handlers
screen.onkey(both, KEY_BOTH)
def both():
reset()
pause()
screen = Screen()
reset()
screen.listen()
screen.mainloop()
such that I can run multiple functions when I click b with just one
line of code?
Yes, there's a hack we can do with lambda and a tuple but we shouldn't be thinking this way. "Just one line of code" should not be your goal at this point.
def startgame():
start.state = False
print start.state
def restart():
end.state = False
start.state = True
print game.state, end.state
s.listen()
s.onkey(startgame, "Return")
s.onkey(restart, 'r')
# This Loop stops when you hit Enter
while start.state:
start.enter()
s.reset()
# I tried repeating it here but it doesn't work
while end.state:
end.enter()
s.reset()
game.state = 'playing'
Both loops are nested in a main while loop but the second one is nested in another while loop (If that helps) so it would look something like this
while True:
while start.state:
start.flash()
s.reset()
while True:
# main game code here
while end.state:
end.flash()
s.reset()
game.state = 'playing'
break
I simply want it to show the end screen and have 'Press r to play again' flash on screen until the player hits r and then it should restart the game and go back to the start screen. The end.state variable wont update during the while loop, but start.state does during its while loop.
Your drawn out (in time) while loops have no place in an event-driven environment like turtle. In general, we need to control everything with events, specifically timer events. Let's rewrite your code as a state machine. (Since I don't have your object classes, entities like start.state become globals like start_state in my example code.)
from turtle import Screen, Turtle
start_state = True
end_state = False
def start_game():
global start_state
start_state = False
def end_game():
global end_state
if not start_state:
end_state = True
def restart_game():
global end_state, start_state
end_state = False
start_state = True
def flash(text):
turtle.clear()
turtle.write(text, align="center", font=('Arial', 24, 'bold'))
color = turtle.pencolor()
turtle.pencolor(turtle.fillcolor())
turtle.fillcolor(color)
def game_states():
if start_state:
flash("Start")
elif end_state:
flash("End")
else:
flash("Playing")
screen.ontimer(game_states, 500)
screen = Screen()
turtle = Turtle()
turtle.hideturtle()
turtle.fillcolor(screen.bgcolor())
screen.onkey(start_game, 'Return')
screen.onkey(restart_game, 'r')
screen.onkey(end_game, 'e')
screen.listen()
game_states()
screen.mainloop()
The state machine rules are:
Start via <Return> -> Playing
Playing via <e> -> End and via <r> -> Start
End via <r> -> Start
Pygame is an extension you download that offers built in functions. I'm currently working on a song player, and have a button that pauses the song and unpauses the song. How can I combine these two buttons so that I click once and it pauses and then click again and it unpauses etc? I have two functions right now that needs to be made into one, as they will be assigned to one button..
This is the code I have that needs to be changed:
def pausesong():
pygame.mixer.music.pause()
def unpausesong():
pygame.mixer.music.unpause()
#resume song #pause the song
pause=False
a=0 #for testing
def pausesong():
global index,a
global pause
a=a+1
#print(pause,a)
if pause == False:
pygame.mixer.music.pause()
pause = True
print(pause,a)
else:
pygame.mixer.music.unpause()
pause = False
print(pause,a)
I'm not familiar with pygame, but a simple approach would be to add a flag variable that you can check.
def pausesong():
pygame.mixer.music.pause()
paused = True
def unpausesong():
pygame.mixer.music.unpause()
paused = False
And then in your button event...
if paused:
unpausesong()
else:
pausesong()
def play_pause():
paused = not paused
if paused: pygame.mixer.music.unpause()
else: pygame.mixer.music.pause()