I have a problem with an application on CentOs / RH to show download statuses from the API. Everything works fine using the PyCharm IDE, but after compiling with PyInstaller (one folder) the application is very unstable and I can't find an error. I run the thread and download API statuses every 10 seconds and if there is a change, I update the icon and send a notification. After left-clicking on the icon, the statuses are displayed in Gtk.ApplicationWindow
.
Sometimes Gtk.StatusIcon
can not change status or be inactive - left / right clicking doesn't work (but notifications of status changes come)
the application may end unexpectedly
I suspect the problem is in threads, but I can't find a proper solution.
class ExampleSystemTrayInit(Gtk.StatusIcon):
def __init__(self):
super().__init__()
app.tray = Gtk.StatusIcon()
app.tray.set_from_file(ico_start)
app.tray.connect('popup-menu', self.on_right_click)
app.tray.connect('activate', self.on_left_click, app)
def on_right_click(self, icon, event_button, event_time):
self.menu = Gtk.Menu()
quit = Gtk.MenuItem("Quit")
quit.connect('activate', self.quitApp)
self.menu.append(quit)
self.menu.show_all()
self.menu.popup(None, None, Gtk.StatusIcon.position_menu, app.tray, event_button, event_time)
def on_left_click(self, icon, app):
try:
self.app = app
if app.all_status:
data = self.app.all_status
Controller.show_window(self, data, self.app, refresh=False)
#call to class MainWindow(Gtk.ApplicationWindow) and show some data
else:
pass;
except Exception as e:
print(e)
app.tray.set_from_file(ico_disconnect)
def quitApp(self, par):
app.quit()
class ExampleSystemTray(Gtk.Application):
def __init__(self, *args, **kwargs):
super().__init__(
application_id="example-system-tray2.app"
)
self.tray = None
self.mainWindow = None
def do_activate(self):
if not hasattr(self, "my_app"):
self.hold()
self.my_app_settings = "Primary application instance."
self.systray = ExampleSystemTrayInit()
TrayController(app)
else:
print("Already running!")
def do_startup(self):
Gdk.threads_init()
Gdk.threads_enter()
Gtk.Application.do_startup(self)
Gdk.threads_leave()
if __name__ == "__main__":
GObject.threads_init()
app = ExampleSystemTray()
app.run()
class TrayController(threading.Thread):
def __init__(self, app):
self.app = app
self.cache = None
threading.Thread.__init__(self, name='TrayController', )
self.interval = 10
self.finished = threading.Event()
self.daemon = True
self.start()
def run(self):
while True:
try:
self.finished.wait(self.interval)
if not self.finished.is_set():
self.connect(self.app)
except Exception as e:
print(e)
def cancel(self):
"""Stop the timer if it hasn't finished yet."""
self.finished.set()
def connect(self, app):
try:
self.result = GetJson.get_json(self, api_view)
if self.result == None:
self.cache = None
self.show_status(2)
app.all_status = None
elif (self.cache != self.result):
self.cache = self.result
app.all_status = AllStatusInstance(self.result)
self.show_status(app.all_status.status)
Controller.show_main_window(self, app.all_staus, app, refresh=True)
#call to class MainWindow(Gtk.ApplicationWindow) if window is visble, then refresh
except Exception as e:
print(e)
self.show_status(2)
app.all_status = None
def show_status(self, status):
self.status = status
if self.status == 0:
return self.change_tray_icon(ico_red, notify_red, tag_red, widget=Gtk.StatusIcon)
elif self.status == 1:
return self.change_tray_icon(ico_gray, notify_gray, tag_gray, widget=Gtk.StatusIcon)
elif self.status == 2:
return self.change_tray_icon(ico_disconnect, notify_disconnect, tag_disconnect,
widget=Gtk.StatusIcon)
def change_tray_icon(self, icon, notification, tag, widget):
if self.app.tray.get_title() != tag:
self.app.tray.set_from_file(icon)
self.app.tray.set_title(tag)
self.notify("Notification:", notification, icon)
def notify(self, title, body, link):
notify2.init("Alert", mainloop=None)
icon = GdkPixbuf.Pixbuf.new_from_file(link)
n = notify2.Notification(title, body)
n.set_icon_from_pixbuf(icon)
n.set_urgency(notify2.URGENCY_CRITICAL)
n.show()
class AllStatusInstance(object):
__instance = None
def __new__(cls, val):
if AllStatusInstance.__instance is None:
AllStatusInstance.__instance = object.__new__(cls)
AllStatusInstance.__instance.val = val
return AllStatusInstance.__instance
Indeed, the problem is threads, in the sense that you cannot use GTK API from multiple threads, ever, as the documentation clearly states.
You should use Gio.Task
if you have a blocking, synchronous operation and you wish to update some UI state at the end of it; or, if you're using a thread object in Python, always use GLib.MainContext.invoke_full()
to invoke a function within the same thread that is running the GTK main loop.
What you should not do, is this:
def do_startup(self):
Gdk.threads_init()
Gdk.threads_enter()
Gtk.Application.do_startup(self)
Gdk.threads_leave()
and then call GTK API from a separate thread than the one that is running GTK's event loop; that is entirely undefined, non-portable behaviour.