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.
Related
I have two callbacks installed for the clicked signal on the done button. Is there a way to take out (not execute) one of them e.g.
import threading
import time
from gi.repository import Gtk, GLib
class Test():
def __init__(self):
win = Gtk.Window()
win.set_title("XYZ")
win.set_border_width(10)
box = Gtk.VBox(spacing=10)
win.add(box)
done_button = Gtk.Button(label="DONE")
done_button.connect("clicked", self.callback1)
#remove callback ??? callback1 should not be called when button is clicked.
done_button.connect("clicked", self.callback2)
box.pack_end(done_button, False, False, 0)
win.show_all()
win.maximize()
win.connect("delete-event", Gtk.main_quit)
def callback1(self, widget):
print "callback1"
def callback2(self, widget):
print "callback2"
if __name__ == '__main__':
test = Test()
Gtk.main()
What can be done to remove callback1.
you need to get the id of the signal in order to be able to disconnect it, so change the connect to:
b_id = done_button.connect("clicked", self.callback1)
and then use the disconnect function of the GObject module:
GObject.signal_handler_disconnect(done_button, b_id)
or as suggested by elya5 (so you don't even have to import GObject):
done_button.disconnect(b_id)
Remember to import the GObject module first (not GLib)
from gi.repository import Gtk, GObject
see python-gtk-3-tutorial.readthedocs.io
If you have lost the “handler_id” for some reason (for example the handlers were installed using Gtk.Builder.connect_signals()), you can still disconnect a specific callback using the function disconnect_by_func():
widget.disconnect_by_func(callback)
Here is my sample code. How do I get the html source code of the current page. It only prints 'GString at 0x8875130' . How to convert it to real text contains html?
from gi.repository import WebKit
from gi.repository import Gtk, Gdk
def get_source(webobj, frame):
print "loading..."
x = web.get_main_frame().get_data_source().get_data()
print x
win = Gtk.Window()
web = WebKit.WebView()
web.open("http://google.com")
web.connect("load-finished", get_source)
win.add(web)
win.show_all()
Gtk.main()
print x.str
Data is available as .str member of GLib.String object. For further details try help(GLib.String) on python prompt after importing libraries.
#Before you can use the require_version() method from gi, you need to import the gi module.
import gi
#Specify versions to import from the repository.
gi.require_version('Gtk','3.0')
gi.require_version('WebKit','3.0')
#Import the modules that will give us a Graphical User Interface (GUI) and a WebKit Browser.
from gi.repository import Gtk,WebKit
#Define your function to handle the WebKit's "load-finished" event. The webobj is a reference to the WebKit that triggered the event. The frame is which frame triggered the event (useful if the loaded page has multiple frames like a frameset.
def ShowSource(webobj,frame):
#What you have printed is what results from this line. This line returns a reference to an object, so when you print it's return value, a description is all Python knows to print.
SourceCodeStringObject=frame.get_data_source().get_data()
#You can get the text the object is carrying from it's "str" member property like I do below.
SourceCodeStringText=SourceCodeStringObject.str
#Send the source code string text to the output stream.
print(SourceCodeStringText)
#Create Window object.
Window=Gtk.Window()
#Set the text to display in the window's caption.
Window.set_title("Test of Python GTK and WebKit")
#Set the starting window size in pixels.
Window.set_default_size(480,320)
#Create the WebView object.
WebBrowser=WebKit.WebView()
#Tell the WebView object to load a website.
WebBrowser.open("https://stackoverflow.com/questions/24119290/pygtk-webkit-get-source-html")
#Set the event handler for the WebView's "load-finished" event to the function we have above.
WebBrowser.connect("load-finished",ShowSource)
#Add the WebView to the window.
Window.add(WebBrowser)
#Set the handler of the window closing to cause GTK to exit. Without this, GTK will hang when it quits, because it's main loop that we start later will still be running. Gtk.main_quit will stop the main loop for GTK.
Window.connect("delete-event",Gtk.main_quit)
#Display the window.
Window.show_all()
#Start GTK's main loop.
Gtk.main()
This way works for me.
#!/usr/bin/env python
import webkit, gtk
def get_source(webobj, frame):
print "loading..."
x = web.get_main_frame().get_data_source().get_data()
print x
win = gtk.Window()
win.set_position(gtk.WIN_POS_CENTER_ALWAYS)
win.resize(1024,768)
win.connect('destroy', lambda w: gtk.main_quit())
win.set_title('Titulo')
vbox = gtk.VBox(spacing=5)
vbox.set_border_width(5)
web = webkit.WebView()
vbox.pack_start(web, fill=True, expand=True)
web = webkit.WebView()
web.open("http://www.google.co.ve")
web.connect("load-finished", get_source)
browser_settings = web.get_settings()
browser_settings.set_property('user-agent', 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0')
browser_settings.set_property('enable-default-context-menu', True)
browser_settings.set_property('enable-accelerated-compositing', True)
browser_settings.set_property('enable-file-access-from-file-uris', True)
web.set_settings(browser_settings)
win.add(web)
win.show_all()
gtk.main()
I want to dive in Python by building a simple browser-application. I've mad a minimalistic webkitbrowser with a tutorial and now want to extend the program, but I'm stuck at some tiny problems I cannot solve.
Python 3.3.3
using Glade for the UI
The first step is to simply add a second scrolledWindow in which the developer-tools should load, immediately.
Here is my .ui-file so far, and this is the python-code:
from gi.repository import Gtk, WebKit
UI_FILE = "browser.ui"
class Browser:
"""A simple Webkit-Browser in GTK+"""
def __init__(self):
self.builder = Gtk.Builder()
self.builder.add_from_file(UI_FILE)
self.builder.connect_signals(self)
self.back = self.builder.get_object("back")
self.forward = self.builder.get_object("forward")
self.adress = self.builder.get_object("adress")
self.webview = WebKit.WebView()
scrolled_window = self.builder.get_object("scrolledwindow")
scrolled_window.add(self.webview)
self.settings = WebKit.WebSettings()
self.settings.set_property('enable-developer-extras', True)
self.webview.set_settings(self.settings)
self.devtools = WebKit.WebInspector()
scrolled_window_dev = self.builder.get_object("scrolledwindowDev")
scrolled_window_dev.add(self.devtools)
^^^^^
self.webview.connect("title-changed", self.on_title_changed)
self.window = self.builder.get_object("window")
self.window.show_all()
def on_title_changed(self, webview, frame, title):
self.window.set_title(title)
def on_button_clicked(self, button):
if button.get_stock_id() == Gtk.STOCK_GO_FORWARD:
self.webview.go_forward()
elif button.get_stock_id() == Gtk.STOCK_GO_BACK:
self.webview.go_back()
def on_entry_activate(self, widget):
url = widget.get_text()
if not "http://" in url:
url = "http://"+url
self.webview.load_uri(url)
def destroy(self, window):
Gtk.main_quit()
def main():
app = Browser()
Gtk.main()
if __name__ == "__main__":
main()
I get the error
TypeError: argument widget: Expected Gtk.Widget, but got
gi.repository.WebKit.WebInspector
Okay, this is stated in the reference of Webkit, that WebInspector is a GObject and not a GtkWidget. But I don't know what to do now.
So, can I make a GtkWidget from a GObject (if yes - how) or should I attach the dev-tools in a complete different way?
The inspector, as you noted, isn't a widget. It's a web page, so you need to create another webview for it. You do this by getting self.window.props.web_inspector (don't create a new inspector) and connecting to its inspect-web-view signal. Inside that signal handler, you need to create a new webview, add that webview to a window or wherever you want to display it, and return it.
You'll probably also want to handle the show-window, attach-window, detach-window, and close-window signals.
More documentation here: inspect-web-view
Example of running Inspector in separate window. Webkit-gtk.
This gist without many signals connected.
https://gist.github.com/alex-eri/53518825b2a8a50dd1695c69ee5058cc
I have a problem
My application on close has to logout from web application. It's take some time. I want to inform user about it with " logging out" information
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ico, gtk.BUTTONS_NONE, txt)
md.showall()
self.send('users/logout.json', {}, False, False)
gtk.main_quit()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
if __name__ == "__main__":
app = Belt()
app.main()
When I try to show dialog in destroy method only window does appear, without icon and text.
I want to, that this dialog have no confirm button, just the information, and dialog have to be destroy with all app.
Any ideas?
Sorry for my poor English
Basically, GTK has to have the chance to work through the event queue all the time. If some other processing takes a long time and the event queue is not processed in the meantime, your application will become unresponsive. This is usually not what you want, because it may result in your windows not being updated, remaining grey, having strange artefacts, or other kinds of visible glitches. It may even cause your window system to grey the window out and offer to kill the presumably frozen application.
The solutution is to make sure the event queue is being processed. There are two primary ways to do this. If the part that takes long consists of many incremental steps, you can periodically process the queue yourself:
def this_takes_really_long():
for _ in range(10000):
do_some_more_work()
while gtk.events_pending():
gtk.main_iteration()
In the general case, you'll have to resort to some kind of asynchronous processing. The typical way is to put the blocking part into its own thread, and then signal back to the main thread (which sits in the main loop) via idle callbacks. In your code, it might look something like this:
from threading import Thread
import gtk, gobject
class Belt(gtk.Window):
def __init__(self):
super(Belt, self).__init__()
self.connect("destroy", self.destroy)
self.show_all()
self.isLogged = True
self.iniError = False
def destroy(self, widget, data=None):
if self.isLogged:
md = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, 0, gtk.BUTTONS_NONE, "Text")
md.show_all()
Thread(target=self._this_takes_very_long).start()
def main(self):
if self.iniError is False:
gtk.gdk.threads_init()
gtk.gdk.threads_enter()
gtk.main()
gtk.gdk.threads_leave()
def _this_takes_very_long(self):
self.send('users/logout.json', {}, False, False)
gobject.idle_add(gtk.main_quit)
if __name__ == "__main__":
app = Belt()
app.main()
I have written this simple script in python:
import gtk
window = gtk.Window()
window.set_size_request(800, 700)
window.show()
gtk.main()
now I want to load in this window an image from web ( and not from my PC ) like this:
http://www.dailygalaxy.com/photos/uncategorized/2007/05/05/planet_x.jpg
How can I do that ?
P.S. I don't want download the image ! I just want load the image from the URL.
This downloads the image from a url, but writes the data into a gtk.gdk.Pixbuf instead of to a file:
import pygtk
pygtk.require('2.0')
import gtk
import urllib2
class MainWin:
def destroy(self, widget, data=None):
print "destroy signal occurred"
gtk.main_quit()
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("destroy", self.destroy)
self.window.set_border_width(10)
self.image=gtk.Image()
response=urllib2.urlopen(
'http://www.dailygalaxy.com/photos/uncategorized/2007/05/05/planet_x.jpg')
loader=gtk.gdk.PixbufLoader()
loader.write(response.read())
loader.close()
self.image.set_from_pixbuf(loader.get_pixbuf())
# This does the same thing, but by saving to a file
# fname='/tmp/planet_x.jpg'
# with open(fname,'w') as f:
# f.write(response.read())
# self.image.set_from_file(fname)
self.window.add(self.image)
self.image.show()
self.window.show()
def main(self):
gtk.main()
if __name__ == "__main__":
MainWin().main()
Download the image. Google on how to download files with python, there are easy-to-use libraries for that.
Load the image into a widget. Look up how to display an image in GTK.
Sorry for the lack of detail, but the answer would get long and you'd still be better off reading on those subjects somewhere else.
Hope it helps!
Here's a simple script using WebKit:
#!/usr/bin/env python
import gtk
import webkit
window = gtk.Window()
window.set_size_request(800, 700)
webview = webkit.WebView()
window.add(webview)
window.show_all()
webview.load_uri('http://www.dailygalaxy.com/photos/uncategorized/2007/05/05/planet_x.jpg')
gtk.main()
Take note, though, that this does in fact download the image.