I'm trying to use pystray without blocking the main thread. Based on the pystray docs we can use the function run_detached() to start without blocking.
I'm using pystray on windows so, apparently I don't need to pass any argument to run_detached() to work.
The first thing I tried is to run this code:
import pystray
from pystray import MenuItem as item
from PIL import Image, ImageTk
def show_window(icon):
print('Test')
def quit_window(icon):
icon.stop()
icon = 'icon.ico'
image=Image.open(icon)
menu=pystray.Menu(item('Show', show_window, default=True), item('Quit', quit_window))
icon=pystray.Icon("name", image, "My System Tray Icon", menu)
icon.run_detached()
But I received this error:
Exception in thread Thread-2:
Traceback (most recent call last):
File "...\lib\threading.py", line 973, in _bootstrap_inner
self.run()
File "...\lib\threading.py", line 910, in run
self._target(*self._args, **self._kwargs)
File "...\lib\site-packages\pystray\_base.py", line 384, in <lambda>
threading.Thread(target=lambda: self.run(setup)).start()
NameError: name 'setup' is not defined
So I tried to bypass this error by changing the line 384 in _base.py removing the setup variable
#threading.Thread(target=lambda: self.run(setup)).start()
threading.Thread(target=lambda: self.run()).start()
The code worked like expected and created the tray icon with the menu buttons working properly.
The problem is when I press "Quit" because the stop() function is not working like when I use icon.run().
The thread appears to keep running and the tray icon stay frozen and the program don't end.
Is there another way to make this work properly?
EDIT:
I found this issue in the official git repository LINK and it appears to be a bug already reported. I want to know if is possible to make a workaround.
Modifying the stop() function further to exit from the thread using os._exit will work if you don't need the calling thread to remain available.
Related
I'm currently working with Tkinter to create a GUI for my Script. Among other things there is a function which writes and save some Data in some Files. To visualize the progress for the User i got an label which shows the progress. When i press the button to execute the function
button_download_data = tk.Button(text="Get Data",command=gatherData)
the window freezes and beside the counter for the progress increasaes it isnt shown due to the window is frozen. My solution was to start the function in a new thread using the threading modul.
button_download_data = tk.Button(text="Get Data",command=threading.Thread(target=gatherData).start)
Now the progress is showed but i cant press the button again because i get an error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\WPy64-31050\python-3.10.5.amd64\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "C:\WPy64-31050\python-3.10.5.amd64\lib\threading.py", line 930, in start
raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once
I tried to "kill" the thread when the function is done with raise Exception() and sys.Exit() but it doesn't work at all.
I figuerd out that i can outsource the thread start out of the tkinter button line with:
def gatherDate():
do something
def threadStart():
threading.Thread(target=gatherData).start
button_download_data = tk.Button(text="Get Data",command=threadStart)
and i think it might help to start a new thread on button press and not the same again but i cant imagine how.
You should be able to handle this by creating a separate function to spawn new worker threads as needed - here's a very basic example
import tkinter as tk
from threading import Thread
from time import sleep # for example - simulate a long-running process
def get_data():
print('foo') # do whatever you need to do here
sleep(2.0) # simulate the thread 'working' on something...for example
def spawn_thread():
t = Thread(target=get_data, daemon=True)
t.start()
root = tk.Tk()
button_download_data = tk.Button(root, text='Get Data', command=spawn_thread)
button_download_data.pack()
if __name__ == '__main__':
root.mainloop()
You could simplify spawn_thread a little by skipping the variable assignment t=... and doing Thread(target=get_data, daemon=True).start() instead (as long as you don't need access to the Thread object t for anything else)
Ubuntu v22.04
MSS v7.0.1
Python 3.10
This issue is practically identical to: Python: Tkinter + MSS = mss.exception.ScreenShotError: XDefaultRootWindow() failed - however, their question is unsolved. For a minimum reproducible example, see there.
How my project functions
I have a project that involves controlling video game automation scripts/macros via a Python GUI. Using the tool goes something like this:
Run the program, which opens a navigable single-page GUI.
Select the game you'd like to automate.
Select the script you'd like to run.
Configure the options for the script (this involves opening a new Toplevel window in Tkinter).
Press Play to run the script.
System: When the Play button is pressed, my program screenshots the video game client to locate its window position as well as various UI element positions. This occurs in a function called win.initialize().
Optional: The user may pause, resume, or stop the script, or switch to a different script on the fly.
The problem
Please see this video for a demonstration:
The issue I'm experiencing happens between step 4 and 6. Strangely, the first time I open the options menu, select/save my options, and close that little pop-up window, the script will run successfully - i.e., MSS will not encounter an error when screenshotting the game client. If I stop/restart the script (which does not require me to re-select options), it will still work properly. However, if I change the options by opening the pop-up window again, MSS will throw this error when it attempts to screenshot:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.10/tkinter/__init__.py", line 1921, in __call__
return self.func(*args)
File "/home/my_project/env/lib/python3.10/site-packages/customtkinter/widgets/ctk_button.py", line 372, in clicked
self.command()
File "/home/my_project/src/view/info_frame.py", line 139, in play_btn_clicked
self.controller.play_pause()
File "/home/my_project/src/controller/bot_controller.py", line 21, in play_pause
self.model.play_pause()
File "/home/my_project/src/model/bot.py", line 103, in play_pause
if not self.__initialize_window():
File "/home/my_project/src/model/bot.py", line 132, in __initialize_window
self.win.initialize()
File "/home/my_project/src/model/runelite_bot.py", line 47, in initialize
if not super().initialize():
File "/home/my_project/src/utilities/window.py", line 128, in initialize
a = self.__locate_minimap(client_rect)
File "/home/my_project/src/utilities/window.py", line 257, in __locate_minimap
if m := imsearch.search_img_in_rect(imsearch.BOT_IMAGES.joinpath("minimap.png"), client_rect):
File "/home/my_project/src/utilities/imagesearch.py", line 52, in search_img_in_rect
im = rect.screenshot()
File "/home/my_project/src/utilities/geometry.py", line 64, in screenshot
with mss.mss() as sct:
File "/home/my_project/env/lib/python3.10/site-packages/mss/factory.py", line 34, in mss
return linux.MSS(**kwargs)
File "/home/my_project/env/lib/python3.10/site-packages/mss/linux.py", line 297, in __init__
self.root = self.xlib.XDefaultRootWindow(self._get_display(display))
File "/home/my_project/env/lib/python3.10/site-packages/mss/linux.py", line 184, in validate
raise ScreenShotError(f"{func.__name__}() failed", details=details)
mss.exception.ScreenShotError: XDefaultRootWindow() failed
Screenshot error: XDefaultRootWindow() failed, {'retval': <mss.linux.LP_XWindowAttributes object at 0x7f31d8d38ac0>,
'args': (<mss.linux.LP_Display object at 0x7f31d8d38740>,)}
This indicates that the issue has something to do with opening and closing the TkToplevel (pop-up) widget. I cannot explain why this works the first time I select options. From what I can see, the Toplevel widget is being destroyed properly whenever the user closes it or presses the save button.
Does anyone know what might be going on here?
It's a bug in the current MSS. Please report it on this.
The current implementation changes the current X11 error handler and leaves it afterwards, and it causes a conflict with Tcl/Tk(the backend of Python tkinter).(See this for detail.)
To avoid this, you can initialize an MSS instance before calling any tkinter API and reuse it, so not disturbing the tkinter module, like the following example.
import tkinter as tk
import mss
mss_obj = mss.mss()
root = tk.Tk()
...
try:
root.mainloop()
finally:
mss_obj.close()
I've been trying to build an application which takes input as text and gives output as speech.
I referred this site to get to know about Text-To-Speech modules in python:
https://pythonprogramminglanguage.com/text-to-speech/
when i ran the program it did the job perfectly but i couldn't use other functions like pause or resume. So i tried to create a new thread for the speech function so that i can alter it speech whenever i want to.
Here is the program:
import threading
import win32com.client as wincl
speak = wincl.Dispatch("SAPI.SpVoice")
t=threading.Event()
def s():
global t
t.set()
data="""This is a story of two tribal Armenian boys who belonged to the
Garoghlanian tribe. """
s=speak.Speak(data)
t1=threading.Thread(target=s)
t1.start
However i am trying to implement the program in GUI using tkinter.
I want the application to read the text when the user is clicking the button.
Since tkinter's button takes command as a function, i made a function for the initialization and starting of the new thread but it is producing an error which i could not interpret and find a solution.
Here is the program thats making error:
import threading
import win32com.client as wincl
speak = wincl.Dispatch("SAPI.SpVoice")
t=threading.Event()
def s():
global t
t.set()
data="""This is a story of two tribal Armenian boys who belonged to the
Garoghlanian tribe. """
s=speak.Speak(data)
def strt():
t1=threading.Thread(target=s)
t1.start()
Here is the error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Application\Python\lib\threading.py", line 916, in _bootstrap_inner
self.run()
File "C:\Application\Python\lib\threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "C:\Users\absan\Desktop\Python\Project-SpeakIt\SI-1.py", line 32, in
speakITheart
s=speak.Speak(data)
File "C:\Users\absan\AppData\Local\Temp\gen_py\3.6\C866CA3A-32F7-11D2-9602-
00C04F8EE628x0x5x4.py", line 2980, in Speak
, 0)
pywintypes.com_error: (-2147352567, 'Exception occurred.', (0, None, None,
None, 0, -2147221008), None)
EDIT:
Guys i somehow found a way to fix it when i was writing this post. I just added these lines to the program
import pyttsx3
engine = pyttsx3.init()
i really don't know how or why it fixed the error but it works!!
So this post might be helpful for someone who is facing the same problem.
Cheers!!
I like the solution from the comments. Just include in non-main thread this line to be able to use COM in it (right before the Speak call)
pythoncom.CoInitialize()
self.engine.Speak(self.msg)
I have a simple Tkinter window, started from an IPython console (so mainloop() is not called, in case that's relevant)
When I press Ctrl+C in the console, nothing happens... until the Tkinter window does something, at which point it catches the KeyboardInterrupt and raises an error.
The question is: how do I catch that KeyboardInterrupt? It seems that no matter where I put a try: block, it is too late already...
Here's an example:
class App(object):
def __init__(self,):
self.root = tk.Tk()
self.root.bind('<FocusIn>', self.focus_in)
def focus_in(self, event):
print 'I got focus'
in the IPython console:
app = App()
Now press Ctrl+C in the console. Then click on the App window... and bang:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/lib-tk/Tkinter.py", line 1481, in __call__
def __call__(self, *args):
KeyboardInterrupt
Adding a 'try/except KeyboardInterrupt' block inside focus_in does not help.
So? Is this a bug? Should TKinter even see that interrupt? Can I disable SIGINT while IPython is idle? (Naturally I still want it when it is running some code...)
One way could be to create a mainloop in another thread that's always waiting for the KeyboardInterrupt exception, although I don't know if this is a good aproach but I believe It will fix your problem.
I will show a reduced portion of the code that gives me a problem.
_tkinter.TclError: image "pyimageN" doesn't exist - where N stays for 1, 2, 3, etc...
There is a first class that shows a menu using an image in the background.
class MenuWindow(): #in this class we show the main part of the program
def __init__(self):
self.Menu=Tk()
self.MCanvas=Canvas(self.Menu)
self.MCanvas.bind("<ButtonPress-1>",self.MenuClick)
#unuseful lines that configure the window and the canvas#
self.Background=PhotoImage(height=600,width=700)#a simple tkinter.PhotoImage object
#other unuseful lines that draw the photoimage ( without reading any file, with the method put())#
self.MCanvas.create_image((x,y),image=self.Background,state="normal")
#unuseful lines that continue the drawing of the canvas#
And a second class that shows another window, using another image in the background. This class is launched by the first class via click binding of the function self.MenuClick.
class EditorWindow(): #in this class we show the main part of the program
def __init__(self):
self.Eenu=Tk()
self.ECanvas=Canvas(self.Eenu)
#unuseful lines that configure the window and the canvas#
self.Background=PhotoImage(height=600,width=700)
#other unuseful lines that draw the photoimage ( without reading any file , with the method put() )#
self.ECanvas.create_image((x,y),image=self.Background,state="normal")#in this line i get the error
#unuseful lines that continue the drawing of the canvas#
The complere traceback is the following:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.1/lib/python3.1/tkinter/__init__.py", line 1399, in __call__
return self.func(*args)
File "/Users/albertoperrella/Desktop/slay.py", line 70, in MenuClick
EditorWindow(self)
File "/Users/albertoperrella/Desktop/slay.py", line 85, in __init__
self.ECanvas.create_image((3,3),image=self.Background,state="normal",anchor="nw")
File "/Library/Frameworks/Python.framework/Versions/3.1/lib/python3.1/tkinter/__init__.py", line 2140, in create_image
return self._create('image', args, kw)
File "/Library/Frameworks/Python.framework/Versions/3.1/lib/python3.1/tkinter/__init__.py", line 2131, in _create
*(args + self._options(cnf, kw))))
_tkinter.TclError: image "pyimage2" doesn't exist
The two classes are made in a similar way, so I don't know why I get the error with the second one. I am sure that it isn't a writing error e.g.(conttruct instead of construct) and that the images I am using actually exist.
So I think that:
I am making some concept mistakes,
or it is a bug (or subtle behaviour of Tkinter) in python.
I solved myself the problem :
The second class I defined was the problem cause it used another root window, alias Tk(). An equivalent to the normal Tk() window is the Toplevel() that is the same as a root but hasn't its own interpreter context.
Shortly, to solve the problem I had to change the first line of the init() method of the EditorWindow class from
self.Eenu=Tk()
to
self.Eenu=Toplevel()
Just change the master argument in ImageTk.PhotoImage() to the respective window, like this:
ImageTk.PhotoImage(Image.open("photo.png"), master=self.window)
And everything will be alright.