I'm currently trying to create a program where it detects the mouse movement on my computer and pops up a window if any movement is detected. However, I've run into some issues with Tkinter threading. I have read multiple other posts on the same issue and understand that Tkinter isn't thread-safe and that it can only be run in the main thread. However, I'm still unsure about the fix for my program. I'd appreciate it if someone could help me take a look.
Essentially, my (simplified) program has two scripts, access_detection.py and password_prompt.py. The first file is responsible for using pynput.mouse to track mouse movement, and the second file is responsible for popping up a Tkinter window and prompting the user for a password whenever mouse movement from the first script is detected.
> password_prompt.py (pops up a window for the user to enter their passcode)
def confirm_passcode(self):
self.access_granted = True
def prompt_password(self) -> bool:
# initialize window
entry = Entry(self.win, width=25, textvariable=self.password, show="*")
entry.pack(pady=((int(self.win.winfo_screenheight()/2) - 100), 0))
button = ttk.Button(self.win, text="Confirm Password", command=self.confirm_passcode)
button.pack(pady=10)
while True:
self.win.attributes('-fullscreen', True)
self.win.attributes('-topmost', True)
if self.access_granted == True:
return True
elif self.access_granted == False and self.access_attempts >= 3:
return False
self.win.update()
> access_detection.py (tracks mouse movement)
def on_move_mouse(self, x, y):
self.security_passed = popup_obj.prompt_password()
if self.security_passed == False:
# do something here
else:
# stops this program
def detection_start(self):
with mouse_listener(on_move=self.on_move_mouse) as mouse_monitor:
mouse_monitor.join()
# driver
if __name__ == "__main__":
monitor = # a class with detection_start()
monitor.detection_start()
The issue raised: RuntimeError: main thread is not in main loop
Currently, the main method is in access_detection.py and I believe the pynput.mouse threading is using the main thread, causing the error since Tkinter isn't thread-safe. Is there a way that I could have these two threads running simultaneously and still accomplish the goal of tracking mouse movement and popping up a window when movement is detected? (with Tkinter in the main thread if it has to be necessary).
The original code is a bit lengthy. It could be found here if you need more info. (ignore main.py)
Related
So I'm using Pygame to create a fancy display for a program I am writing. I chose Pygame because it's easy to get started and does a great job with animations. I want the display to be as big as I can make it so as much information can be shown as possible. Here is the kicker however, I still want to be able to get to the console of the program.
Pygame forces a fullscreen window to the front, so you cant tab out, and moving the windows to another windows desktop crashes the display. I would do a key trick to switch the pygame mode, but I cannot use pygame.event.get() because of how the program the threaded.
Is there a way to make it a full-screen window so that I can tab out and leave it up in the background? I dont really want it to just be a normal window because it is not as big that way.
The display crashes after I tab out and back in, here is what that looks like:
I also get a non-zero exit code: -805306369 (0xCFFFFFFF)
Here is a broken down version of the code that still gives me this error, you'll notice there are some things in here you wouldn't have if this was your full program, but I wanted to retain as much architecture as I could.
import pygame
import os
BACKGROUND = (9, 17, 27)
os.environ['SDL_VIDEO_WINDOW_POS'] = "0,0"
pygame.init()
pygame.font.init()
infoObject = pygame.display.Info()
SIZE = (infoObject.current_w, infoObject.current_h)
X_CENTER = SIZE[0]/2
Y_CENTER = SIZE[1]/2
# create a borderless window that's as big as the entire screen
SCREEN = pygame.display.set_mode((SIZE[0], SIZE[1]), pygame.NOFRAME)
clock = pygame.time.Clock()
TextFont = pygame.font.SysFont('Courant', 30)
class DisplayState:
state = type(bool)
def __init__(self):
self.state = True
def get_state(self):
return self.state
def change_state(self, new_state):
self.state = new_state
def main(display_state_object):
running = True
while running:
if display_state_object.get_state():
SCREEN.fill(BACKGROUND)
pygame.display.flip()
else:
return 1
return
if __name__ == "__main__":
main(DisplayState())
EDIT
I think it is a multi-threading problem! See this code:
Produces Error
def start_display():
display(params)
def display(params):
pygame loop
if __name__ == "__main__":
display_thread = threading.Thread(target=start_display)
display_thread.start()
Does not produce error
def display(params):
pygame loop
if __name__ == "__main__":
display_thread = threading.Thread(target=display(params))
display_thread.start
# marker
One problem with the version that does work, the program does not seem to be continuing forwards outside the thread (ie the marker is never reached). Is this how the threading library works? It may explain why I had the middle man function present. Maybe this is a different problem and deserves its own question?
EDIT
Setting up the thread like this allows the main thread to continue, but brings back the pygame error:
threading.Thread(target=display, args=(DisplayState(),))
There's no easy way to do this on windows/sdl using the real fullscreen mode, and the usual way to solve this is to use a borderless window.
Here's how to create such a "fake" fullscreen window in pygame:
import pygame
import os
# you can control the starting position of the window with the SDL_VIDEO_WINDOW_POS variable
os.environ['SDL_VIDEO_WINDOW_POS'] = "0,0"
pygame.init()
# now let's see how big our screen is
info = pygame.display.Info()
# and create a borderless window that's as big as the entire screen
screen = pygame.display.set_mode((info.current_w, info.current_h), pygame.NOFRAME)
You have to call one of the pygame event functions (e.g. pygame.event.pump() or pygame.event.get()) each frame or the window will become unresponsive and the program will appear to have crashed. If you call one of those functions, you should be able to press Alt+Tab (in Windows) to get back to the desktop without crashing the program (if you select the desktop, the window will be minimized and if you select another window, it will just be brought to the front).
def main(display_state_object):
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
# Press Esc to quit.
if event.key == pygame.K_ESCAPE:
running = False
if display_state_object.get_state():
SCREEN.fill(BACKGROUND)
pygame.display.flip()
else:
return 1
return
I have a custom Event Loop for Pyglet that can be simplified to this simple piece of code
while True:
pyglet.clock.tick()
for window in pyglet.app.windows:
window.switch_to()
# rendering code
window.flip()
window.dispatch_events() # <--- program hangs
When there is only one window, the event loop works fine but when I attempt to have two windows, the loop hangs at window.dispatch_events(). Calling the functions in different order yield the same problem. I also tried debugging:
pyglet.options['debug_win32'] = True
Error Message:
...
_user32.UnregisterHotKey(self._hwnd, 0)
b'Hot key is not registered.\r\n'
After some more tinkering I managed to get the windows responsive with a dirty hack, but still there are quite a few issues that arise with event processing. Fix was calling an update immediately after setting window visible.
Sample Initialisation code:
if __name__=="__main__":
engine = Engine()
engine.run()
class Engine: # test program
def __init__(self):
self.application = customLib.Application()
# define windows, by default they are set as not visible
self.window1 = self.application.guiManager.createWindow("Window 1",
(1000, 700),
cursorPath="../data/cursor.png")
self.window2 = self.application.guiManager.createWindow("Window 2",
(1000, 700),
cursorPath="../data/cursor.png")
# load up code such as loading textures and .obj files
self.window1.pygletWindow.set_visible()
self.window1.update() # calling this after setting self.window2 visible will cause original error
self.window2.pygletWindow.set_visible()
self.window2.update()
def run(self):
self.application.run()
### CUSTOMLIB ###
class Application:
...
def run(self):
while True:
pyglet.clock.tick()
self.guiManager.update()
class GUIManager: # manages windows
...
def update(self):
for window in self.windows:
window.update()
class Window: # pyglet window higher level wrapper
pygletWindow = None
...
def update(self):
e = self.pygletWindow.dispatch_events()
# all events are intercepted by a custom class, it transforms all unhandled events (keyboard, mouse, and close button for now) into a list of events that can then be looped through rather than requiring twenty different functions.
# the event list is acquired, looped through and then cleared
for event in self.eventManager.get():
if event.type == customLib.CLOSE:
# close application
elif event.type == customLib.K_W:
# move player forward
# rendering
self.pygletWindow.switch_to()
# window is cleared
# window content rendered
self.pygletWindow.flip()
The way around the previous problem seems a little foolish and not robust. With the current event loop I have, window feedback sometimes does not work. For example, to close the window I must sometimes click it several times or, when moving the window around it won't always grab the window. I dispatch_events() at every loop and my custom event handler does not handle window motion events. The glitching of event handling only occurs with multiple windows. With a single window the event loop is flawless. This "bug" can be fixed by using varients of pyglet EventLoop.
I have concluded that a custom Event Loops' cons outweigh its pros. It is much easier to subclass EventLoop (since it's optimised for the OS) and override any pesky routines. Even better is to make a whole new event loop copying initialisation code of EventLoop and any backend routines (utilising PlatformEventLoop), which then allows you to very easily implement custom routines. I will be leaving this question up because there is very little documentation about this matter.
I have a Raspberry Pi with the Piface adaptor board. I have made a GUI which controls the LED's on the Piface board.
I wrote a small piece of code to make the LED's run up and down continuously, like Knight Riders car, using a While loop.
I then wrote another piece of code that created a GUI. In the GUI is a button that starts the LED's running up and down continuously with the While loop piece of code.
What I want to do is to have that GUI button start the LED running sequence, and then the same button stop the sequence at any time.
I do understand that the code is sitting/stuck in the While loop. And hence any buttons in the GUI are not going to have an effect.
So is there a better way of doing it? Any pointers would be appreciated.
Thanks in advance.
Another option is to run the LED while loop in a separate thread. Like in
the next code. The while loop is stopped by toggling the shared led_switch
variable.
"""
blinking LED
"""
import tkinter as tk
import threading
import time
led_switch=False
def start_stop():
global led_switch
led_switch=not led_switch
if led_switch:
t=threading.Thread(target=LED)
t.start()
def LED():
while led_switch:
print('LED on')
time.sleep(1)
print('LED off')
time.sleep(1)
root=tk.Tk()
button=tk.Button(root,command=lambda: start_stop(),text='start/stop')
button.pack()
tk.mainloop()
If you have a while loop and a GUI you can use generators to still use the loop and let the GUI run properly.
I sketch the Idea here and create an example for the Tkinter GUI.
You want to write your code as a loop and still use it in a GUI:
from Tkinter import *
from guiLoop import guiLoop # https://gist.github.com/niccokunzmann/8673951
#guiLoop
def led_blink(argument):
while 1:
print("LED on " + argument)
yield 0.5 # time to wait
print("LED off " + argument)
yield 0.5
t = Tk()
led_blink(t, 'shiny!') # run led_blink in this GUI
t.mainloop()
Output while the GUI is responsive:
LED on shiny!
LED off shiny!
LED on shiny!
LED off shiny!
...
Sadly Tkinter is the only GUI I know and it is a bad example because you can always update the GUI in your loop with the update() method of GUI elements:
root = Tk()
while 1:
print("LED on")
t = time.time() + 0.5
while t > time.time(): root.update()
print("LED off")
t = time.time() + 0.5
while t > time.time(): root.update()
But with such a guiLoop you can have multiple loops:
t = Tk()
led_blink(t, 'red')
led_blink(t, 'blue')
led_blink(t, 'green')
t.mainloop()
Here are some examples for starting and stopping the loop with a button.
If you're using Tkinter, there's a very easy pattern for running a loop. Given that the UI (in just about every UI toolkit) is already running an infinite loop to process events, you can leverage this to run code periodically.
Let's assume you have a python object "led" which has a method for toggling it on and off. You can have it switch from on to off every 100ms with something as simple as these three lines of code:
def blink(led):
led.toggle()
root.after(100, blink, led)
The above code will run forever, causing the led to blink every 100ms. If you want to be able to start and stop the blinking with a button, introduce a flag:
def blink(led):
if should_blink:
led.toggle()
root.after(100, blink, led)
When you set the toggle to True, the led will start blinking. When it's False, it will stop blinking.
The main thing to take away from this is that you already have an infinite loop running, so there's no need to create one of your own, and no need to use something as complex as threading. Simply create a function that does one frame of animation, or calls some function or does some unit of work, then have the function request that it be run again in the future. How far in the future defines how fast your animation or blink will run.
I wanted to create a simple gui with a play and stop button to play an mp3 file in python. I created a very simple gui using Tkinter that consists of 2 buttons (stop and play).
I created a function that does the following:
def playsound () :
sound = pyglet.media.load('music.mp3')
sound.play()
pyglet.app.run()
I added that function as a command to the button play. I also made a different function to stop music:
def stopsound ():
pyglet.app.exit
I added this function as a command to the second button. But the problem is that when I hit play, python and the gui freeze. I can try to close the window but it does not close, and the stop button is not responsive. I understand that this is because the pyglet.app.run() is executing till the song is over but how exactly do I prevent this? I want the gui to stop the music when I click on the button. Any ideas on where I can find a solution to this?
You are mixing two UI libraries together - that is not intrinsically bad, but there are some problems. Notably, both of them need a main loop of their own to process their events. TKinter uses it to communicate with the desktop and user-generated events, and in this case, pyglet uses it to play your music.
Each of these loops prevents a normal "top down" program flow, as we are used to when we learn non-GUI programming, and the program should proceed basically with callbacks from the main loops. In this case, in the middle of a Tkinter callback, you put the pyglet mainloop (calling pyglet.app.run) in motion, and the control never returns to the Tkinter library.
Sometimes loops of different libraries can coexist on the same process, with no conflicts -- but of course you will be either running one of them or the other. If so, it may be possible to run each library's mainloop in a different Python thread.
If they can not exist together, you will have to deal with each library in a different process.
So, one way to make the music player to start in another thread could be:
from threading import Thread
def real_playsound () :
sound = pyglet.media.load('music.mp3')
sound.play()
pyglet.app.run()
def playsound():
global player_thread
player_thread = Thread(target=real_playsound)
player_thread.start()
If Tkinter and pyglet can coexist, that should be enough to get your music to start.
To be able to control it, however, you will need to implement a couple more things. My suggestion is to have a callback on the pyglet thread that is called by pyglet every second or so -- this callback checks the state of some global variables, and based on them chooses to stop the music, change the file being played, and so on.
I would do something like:
import pyglet
from pyglet.gl import *
class main (pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(800, 600, fullscreen = False)
self.button_texture = pyglet.image.load('button.png')
self.button = pyglet.sprite.Sprite(self.button_texture)
self.sound = pyglet.media.load('music.mp3')
self.sound.play()
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_press(self, x, y, button, modifiers):
if x > self.button.x and x < (self.button.x + self.button_texture.width):
if y > self.button.y and y < (self.button.y + self.button_texture.height):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == 65307: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.button.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
This solution is the easiest one:
import pyglet
foo=pyglet.media.load("/data/Me/Music/Goo Goo Dolls/[1998] Dizzy Up The Girl/11 - Iris.mp3")
foo.play()
def exiter(dt):
pyglet.app.exit()
print "Song length is: %f" % foo.duration
# foo.duration is the song length
pyglet.clock.schedule_once(exiter, foo.duration)
pyglet.app.run()
source: http://ubuntuforums.org/showthread.php?t=1651906
There is a media player implementation in the pyglet documentation:
http://www.pyglet.org/doc/programming_guide/playing_sounds_and_music.html
The script you should look at is media_player.py
Hopefully this will get you started
At the moment I'm trying to use Python to detect when the left mouse button is being held and then start to rapidly send this event instead of only once. What I basically want to do is that when the left mouse button is held it clicks and clicks again until you let it go. But I'm a bit puzzled with the whole Xlib, I think it's very confusing actually. Any help on how to do this would be really awesome. That's what I've got so far:
#!/usr/bin/env python
import Xlib
import Xlib.display
def main():
display = Xlib.display.Display()
root = display.screen().root
while True:
event = root.display.next_event()
print event
if __name__ == "__main__":
main()
But there is unfortunately no output in the console. After a quick search on the internet I found the following:
root.change_attributes(event_mask=Xlib.X.KeyPressMask)
root.grab_key(keycode, Xlib.X.AnyModifier, 1, Xlib.X.GrabModeAsync,
Xlib.X.GrabModeAsync)
This is seemingly import to catch a special event with the given keycode. But firstly what keycode does the left-mouse click have, if any at all? And secondly how can I detect when it is being held down and then start sending the mouseclick event rapidly. I would be really grateful for help. (Maybe a way to stop this script with a hotkey would be cool aswell...)
Actually you want Xlib.X.ButtonPressMask | Xlib.X.ButtonReleaseMask, to get events for button presses and releases (different from key presses and releases). The events are ButtonPress and ButtonRelease, and the detail instance variable gives you the button number. From when you get the press event, to when you get the release event, you know the button is being held down. Of course you can also receive key events and do something else (e.g. exit your script) when a certain key is pressed.
Edit: this version works fine for me, for example...:
import Xlib
import Xlib.display
def main():
display = Xlib.display.Display(':0')
root = display.screen().root
root.change_attributes(event_mask=
Xlib.X.ButtonPressMask | Xlib.X.ButtonReleaseMask)
while True:
event = root.display.next_event()
print event
if __name__ == "__main__":
main()