Dialog in thread freeze whole app despite Gdk.threads_enter/leave() - python

I've got an application which makes something in background. To inform the user it update some widgets with its progress. That works.
But somethings there is an error or something else in this background operation so it has to display a dialog. This freeze my whole application although I handle everything with the Threading-Lock. An example of code with exactly my problem is this:
import threading, time
from gi.repository import Gtk, Gdk
def background(label, parent):
for t in range(5):
label.set_text(str(t))
time.sleep(1)
Gdk.threads_enter()
dlg = Gtk.MessageDialog(
type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
message_format="Time is gone.",
title="Info")
dlg.run()
dlg.destroy()
Gdk.threads_leave()
def main():
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
label = Gtk.Label()
window.add(label)
window.show_all()
thread = threading.Thread(target=background, args=(label, window))
Gdk.threads_init()
Gdk.threads_enter()
thread.start()
Gtk.main()
Gdk.threads_leave()
if __name__=="__main__":
main()

In gtk3, all gtk functions like adding/removing/changing widgets must be executed by the gtk thread (the thread that's running Gtk.main()).
The fixed code:
import threading, time
from gi.repository import Gtk, Gdk, GLib # need GLib for GLib.PRIORITY_DEFAULT
# a short utility function that I like to use.
# makes the Gtk thread execute the given callback.
def add_mainloop_task(callback, *args):
def cb(args):
args[0](*args[1:])
return False
args= [callback]+list(args)
Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT, cb, args)
def background(label, parent):
for t in range(5):
#~ label.set_text(str(t)) # let the gtk thread do this.
add_mainloop_task(label.set_text, str(t))
time.sleep(1)
#~ Gdk.threads_enter() # don't need this.
dlg = Gtk.MessageDialog(
type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
message_format="Time is gone.",
title="Info")
# put these two functions calls inside a little function, and let the gtk thread execute it.
def run_dialog(dlg):
dlg.run()
dlg.destroy()
add_mainloop_task(run_dialog, dlg)
#~ Gdk.threads_leave() # don't need this.
def main():
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
label = Gtk.Label()
window.add(label)
window.show_all()
thread = threading.Thread(target=background, args=(label, window))
Gdk.threads_init()
#~ Gdk.threads_enter() # don't need this.
thread.start()
Gtk.main()
#~ Gdk.threads_leave() # don't need this.
if __name__=="__main__":
main()

Related

GTK freezes when opening dialog if user is moving the main window

The following code freezes when a user is moving the main window, and a dialog box will pop up.
https://imgur.com/a/aAh6Xee
Has anyone noticed this problem before?
I'm using idle_add to display the message dialog, but that doesn't solve the problem.
from time import sleep
import gtk
import pygtk
pygtk.require("2.0")
from threading import Thread
import gobject
class PyApp(gtk.Window):
def __init__(self):
super(PyApp, self).__init__()
self.connect("destroy", gtk.main_quit)
self.set_size_request(250, 100)
self.set_position(gtk.WIN_POS_CENTER)
self.set_title("Test")
btn = gtk.Button("Click Here")
btn.connect("clicked", self.on_click)
self.add(btn)
self.show_all()
def decorator_threaded(func):
def wrapper(*args, **kwargs):
gtk.gdk.threads_enter()
thread = Thread(target=func, args=args, kwargs=kwargs)
thread.start()
return thread
return wrapper
#decorator_threaded
def running_on_another_thread(self):
sleep(2) # Heavy task
gobject.idle_add(self.error_message)
def on_click(self, widget):
self.running_on_another_thread()
def error_message(self):
md = gtk.MessageDialog(self,
gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,
gtk.BUTTONS_CLOSE, "Error")
md.run()
md.destroy()
PyApp()
gtk.gdk.threads_init()
gtk.main()
I also tried using Gtk 3.0 and noticed the same error.
import threading
import time
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk
def app_main():
win = Gtk.Window(default_height=100, default_width=250)
win.connect("destroy", Gtk.main_quit)
win.set_position(Gtk.WindowPosition.CENTER)
def error_message():
md = Gtk.MessageDialog(
transient_for=win,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.CLOSE,
text="Error",
)
md.run()
md.destroy()
def example_target():
time.sleep(2) # Heavy task
GLib.idle_add(error_message)
win.show_all()
thread = threading.Thread(target=example_target)
thread.daemon = True
thread.start()
if __name__ == "__main__":
app_main()
Gtk.main()
The solution is to call timeout_add on the thread to schedule the MessageDialog to run on the main GUI thread.
The class MessageBox also worked in Gtk 2 using pygtk.
import threading
import time
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk
class MessageBox(Gtk.MessageDialog):
def __init__(self, parent, message):
Gtk.MessageDialog.__init__(self, transient_for=parent,
flags=0,
message_type=Gtk.MessageType.ERROR,
buttons=Gtk.ButtonsType.CLOSE,
text=message)
self.set_default_response(Gtk.ResponseType.OK)
self.connect('response', self._handle_clicked)
def _handle_clicked(self, *args):
self.destroy()
def show_dialog(self):
GLib.timeout_add(0, self._do_show_dialog)
def _do_show_dialog(self):
self.show_all()
return False
def app_main():
win = Gtk.Window(default_height=100, default_width=250)
win.connect("destroy", Gtk.main_quit)
win.set_position(Gtk.WindowPosition.CENTER)
def error_message():
dialog = MessageBox(win, "Error")
dialog.show_dialog()
def example_target():
time.sleep(2) # Heavy task
error_message()
win.show_all()
thread = threading.Thread(target=example_target)
thread.daemon = True
thread.start()
if __name__ == "__main__":
app_main()
Gtk.main()
Note: The issue cannot be reproduced on Linux.
References:
https://bytes.com/topic/python/answers/717463-showing-message-dialog-thread-using-gtk#post2858146
https://mail.gnome.org/archives/gtk-app-devel-list/2007-October/msg00034.html.

Run a schedule with python GTK

I'm building a GTK application with Python but I want to update labels at a specific time every hour.
I tried with schedule but apparently it doesn't work with pygobject. This is my sample code
application = Application()
application.connect("destroy", Gtk.main_quit)
application.show_all()
Gtk.main()
print('go here') # This line never run
schedule.every(30).seconds.do(application.update_code)
while 1:
schedule.run_pending()
time.sleep(1)
Update Thanks for the answer of JohnKoch, this solution was applied to my repo that works as TOTP Authenticator on Linux, You can visit and I will appreciate it very much if you leave a star or any issues about my repo.
Gtk.main()
Runs the main loop until Gtk.main_quit() is called.
So schedule below never has the chance to run. You need to hook a function to Gtk main loop, for example, by calling GLib.timeout_add.
Here is an example,
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk
import schedule
window = Gtk.Window(title="Hello World")
window.set_default_size(600, 400)
window.connect("destroy", Gtk.main_quit)
label = Gtk.Label(label="0")
window.add(label)
window.show_all()
count = 0
def update_label():
global count
count = count + 1
label.set_text(str(count))
schedule.every(2).seconds.do(update_label)
def run_schedule():
schedule.run_pending()
return True
GLib.timeout_add(1000, lambda: run_schedule())
Gtk.main()

How to do background task in gtk3-python?

I have this main thread:
Gui.py
from gi.repository import Gtk, Gdk
import Process
import gobject
class gui():
def __init__(self):
self.window = Gtk.Window()
self.window.connect('delete-event', Gtk.main_quit)
self.box = Gtk.Box()
self.window.add(self.box)
self.label = Gtk.Label('idle')
self.box.pack_start(self.label, True, True, 0)
self.progressbar = Gtk.ProgressBar()
self.box.pack_start(self.progressbar, True, True, 0)
self.button = Gtk.Button(label='Start')
self.button.connect('clicked', self.on_button_clicked)
self.box.pack_start(self.button, True, True, 0)
self.window.show_all()
gobject.threads_init()
Gdk.threads_enter()
Gtk.main()
Gdk.threads_leave()
def working1():
self.label.set_text('working1')
t = Process.Heavy()
t.heavyworks1()
self.label.set_text('idle')
def on_button_clicked(self, widget):
Gdk.threads_enter()
working1()
Gdk.threads_leave()
if __name__ == '__main__':
gui = gui()
This code will generate this gui:
and I have second modul which will do the logic.
Process.py
import threading
class Heavy(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def heavyworks1(self):
#doing heavy works1
#return result
def heavyworks2(self, *param):
#doing heavy works2
#return result
When I execute this, the operation works, but the gui became freeze. How to do it well?
EDIT:
as user4815162342 said, I change my code to this:
from gi.repository import Gtk, Gdk, GLib
import Process
import gobject
import threading
class gui():
def __init__(self):
self.window = Gtk.Window()
self.window.connect('delete-event', Gtk.main_quit)
self.box = Gtk.Box()
self.window.add(self.box)
self.label = Gtk.Label('idle')
self.box.pack_start(self.label, True, True, 0)
self.progressbar = Gtk.ProgressBar()
self.box.pack_start(self.progressbar, True, True, 0)
self.button = Gtk.Button(label='Start')
self.button.connect('clicked', self.on_button_clicked)
self.box.pack_start(self.button, True, True, 0)
self.window.show_all()
gobject.threads_init()
GLib.threads_init()
Gdk.threads_init()
Gdk.threads_enter()
Gtk.main()
Gdk.threads_leave()
def init_progress(self, func, arg):
self.label.set_text('working1')
self.worker = threading.Thread(target=func, args=[arg])
self.running = True
gobject.timeout_add(200, self.update_progress)
self.worker.start()
def update_progress(self):
if self.running:
self.progressbar.pulse()
return self.running
def working(self, num):
Process.heavyworks2(num)
gobject.idle_add(self.stop_progress)
def stop_progress(self):
self.running = False
self.worker.join()
self.progressbar.set_fraction(0)
self.label.set_text('idle')
def on_button_clicked(self, widget):
self.init_progress(self.working, 100000)
if __name__ == '__main__':
gui = gui()
with that code, program sometimes working but sometimes getting this error.
1.
**
Gtk:ERROR:/build/buildd/gtk+3.0-3.4.2/./gtk/gtktextview.c:3726:gtk_text_view_validate_onscreen: assertion failed: (priv->onscreen_validated)
Aborted (core dumped)
2.
*** glibc detected *** python: free(): invalid next size (fast): 0x09c9f820 ***
3.
Segmentation fault (core dumped)
You didn't actually start the thread, you only instantiated an object that can be used to start it. A full solution requires a careful separation of responsibilities between your GUI thread and your worker thread(s). What you want to do is the following:
Do your heavy calculation in the separate thread, spawned and joined by the GUI code. The calculation should not spawn its own threads, nor does it need to be aware of threads (except for being thread-safe, of course).
When the thread is done, use gobject.idle_add() to tell the GUI that the progress indicator can be withdrawn. (gobject.idle_add is about the only GTK function that is safe to call from another thread.)
With such a setup, the GUI remains fully responsive and progress bar updated no matter what the calculation does, and the GUI thread is guaranteed to notice when the calculation finishes. Two additional points regarding your current code:
Instantiate threading.Thread instead of inheriting from it. That way you don't need to bother with implementing run(). In both cases you have to call thread.start(), though, to start off the thread.
Don't call threads_enter() and threads_leave(), unless you really know what you are doing. Just remember that as long as you call all your GTK functions from a single thread (the same thread in which you initialized GTK), you'll be fine.
Here is proof-of-concept code that implements the above suggestions:
def working1(self):
self.label.set_text('working1')
self.work_thread = threading.Thread(self.run_thread)
self.running = True
gobject.timeout_add(200, self.update_progress)
self.work_thread.start()
# the GUI thread now returns to the mainloop
# this will get periodically called in the GUI thread
def update_progress(self):
if self.running:
self.progressbar.pulse() # or set_fraction, etc.
return self.running
# this will get run in a separate thread
def run_thread(self):
Process.heavyworks1() # or however you're starting your calculation
gobject.idle_add(self.stop_progress)
# this will get run in the GUI thread when the worker thread is done
def stop_progress(self):
self.running = False
self.work_thread.join()
self.label.set_text('idle')
As you suggested you need to start another thread for this. Usually threading in python is pretty straightforward but it can get tricky with GUIs.
This should be of help: Python. Doing some work on background with Gtk GUI

PyGTK, Threads and WebKit

In my PyGTK app, on button click I need to:
Fetch some html (can take some time)
Show it in new window
While fetching html, I want to keep GUI responsive, so I decided to do it in separate thread. I use WebKit to render html.
The problem is I get empty page in WebView when it is in separated thread.
This works:
import gtk
import webkit
webView = webkit.WebView()
webView.load_html_string('<h1>Hello Mars</h1>', 'file:///')
window = gtk.Window()
window.add(webView)
window.show_all()
gtk.mainloop()
This does not work, produces empty window:
import gtk
import webkit
import threading
def show_html():
webView = webkit.WebView()
webView.load_html_string('<h1>Hello Mars</h1>', 'file:///')
window = gtk.Window()
window.add(webView)
window.show_all()
thread = threading.Thread(target=show_html)
thread.setDaemon(True)
thread.start()
gtk.mainloop()
Is it because webkit is not thread-safe. Is there any workaround for this?
According to my experience, one of the things that sometimes doesn't work as you expect with gtk is the update of widgets in separate threads.
To workaround this problem, you can work with the data in threads, and use glib.idle_add to schedule the update of the widget in the main thread once the data has been processed.
The following code is an updated version of your example that works for me (the time.sleep is used to simulate the delay in getting the html in a real scenario):
import gtk, glib
import webkit
import threading
import time
# Use threads
gtk.gdk.threads_init()
class App(object):
def __init__(self):
window = gtk.Window()
webView = webkit.WebView()
window.add(webView)
window.show_all()
self.window = window
self.webView = webView
def run(self):
gtk.main()
def show_html(self):
# Get your html string
time.sleep(3)
html_str = '<h1>Hello Mars</h1>'
# Update widget in main thread
glib.idle_add(self.webView.load_html_string,
html_str, 'file:///')
app = App()
thread = threading.Thread(target=app.show_html)
thread.start()
app.run()
gtk.main()
I don't know anything about webkit inner workings, but maybe you can try it with multiple processes.

Python. Doing some work on background with Gtk GUI

python 3.2.2
gtk3 3.2.2
python-gobject 3.0.2
I'm trying to display a GUI and do some work in the background. As I understand it should look something like this:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
from threading import Thread
from gi.repository import Gtk, Gdk
class Gui():
def run(self):
self.Window = Gtk.Window()
self.Window.set_border_width(8)
self.Window.set_title("Некий GUI")
self.Window.connect('destroy', lambda x: self.stop())
self.outBut = Gtk.Button.new_from_stock(Gtk.STOCK_OK)
self.outBut.set_size_request(150, 35)
self.outBut.connect('clicked', lambda x: self.passfun)
self.Window.add(self.outBut)
self.Window.show_all()
def stop(self):
Gtk.main_quit()
def passfun(self):
pass
class LoopSleep(Thread):
def run(self):
i = 1
while True:
print(i)
i = i + 1
#time.sleep(1)
gui = Gui()
gui.run()
loopSleep = LoopSleep()
loopSleep.start()
Gdk.threads_init()
Gdk.threads_enter()
Gtk.main()
Gdk.threads_leave()
But it does not work. Several cycles occurs when you press the button. And the cycle runs after the window is closed. But not together.
What I do wrong?
Can't claim to be any expert on python threading nor gtk3 but after playing around a little with your example I found something that appears to work the way you want it. Instead of sub classing Thread i use threading.start(target=loop_sleep), and placed that inside Gui.
Glib.threads_init() also seem to be needed.
#!/usr/bin/env python3
from gi.repository import Gtk,Gdk, GLib
import threading
import time
class Gui(Gtk.Window):
def __init__(self):
self.Window = Gtk.Window()
self.Window.set_border_width(8)
self.Window.set_title("Некий GUI")
self.Window.connect('destroy', lambda x: self.stop())
self.outBut = Gtk.Button.new_from_stock(Gtk.STOCK_OK)
self.outBut.set_size_request(150, 35)
self.Window.connect('destroy', lambda x: self.stop())
self.Window.add(self.outBut)
self.Window.show_all()
threading.Thread(target=loop_sleep).start()
def stop(self):
Gtk.main_quit()
def passfun(self):
pass
def loop_sleep():
i = 1
while True:
print(i)
i = i + 1
#time.sleep(1)
app = Gui()
GLib.threads_init()
Gdk.threads_init()
Gdk.threads_enter()
Gtk.main()
Gdk.threads_leave()

Categories

Resources