PyGTK with threading, gobject, send signal from another thread? - python

I'm new to Python, I want open a web page(here is google.com) using pywebkitgtk
then countdown with another thread,
when time's up, send a signal to webview, download the html as file
Is there a way to open a web-page in gtk.main and countdown in background thread, then send a signal to GUI, make GUI do something..
reference material:
Downloading a page’s content with python and WebKit
using a separate thread to run code, two approaches for threads in PyGTK.
here is my code, it cannot run, I guess I do not understand Python's Class...
#!/usr/bin/env python
import sys, threading
import gtk, webkit
import time
import gobject
gobject.threads_init()
google = "http://www.google.com"
class WebView(webkit.WebView):
#return page's content
def get_html(self):
self.execute_script('oldtitle=document.title;document.title=document.documentElement.innerHTML;')
html = self.get_main_frame().get_title()
self.execute_script('document.title=oldtitle;')
return html
#wait 5 senconds and send a signal
class TimeSender(gobject.GObject, threading.Thread):
def __init__(self):
self.__gobject_init__()
threading.Thread.__init__(self)
def run(self):
print "sleep 5 seconds"
time.sleep(5)
self.emit("Sender_signal")
gobject.type_register(TimeSender)
gobject.signal_new("Sender_signal", TimeSender, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
#PywebkitGTK, open google.com, receive signal
class Window(gtk.Window, gobject.GObject):
def __init__(self, time_sender, url):
self.__gobject_init__()
gtk.Window.__init__(self)
time_sender.connect('Sender_signal', self._finished_loading)
self._url = url
def open_page(self):
view = WebView()
view.get_html()
view.open(self._url)
self.add(view)
gtk.main()
#write html to file
def _finished_loading(self, view):
with open("pagehtml.html", 'w') as f:
f.write(view.get_html())
gtk.main_quit()
'''
def user_callback(object):
with open("pagehtml2.html", 'w') as f:
f.write(view.get_html())
gtk.main_quit()
'''
if __name__ == '__main__':
time_sender = TimeSender()
window = Window(time_sender, google)
#time_sender.connect("Sender_signal", user_callback)
time_sender.start()
window.open_page()
I got an error:
AttributeError: 'TimeSender' object has no attribute 'get_html'
I've been confused for a few days... thanks

Looks like you are confused about singals/objects and threads. _finished_loading method does not get view as a parameter as yo are not passing it. If you make it global it should work. Following piece of code works as expected.
#!/usr/bin/env python
import sys, threading
import gtk, webkit
import time
import gobject
gobject.threads_init()
google = "http://www.google.com"
class WebView(webkit.WebView):
#return page's content
def get_html(self):
self.execute_script('oldtitle=document.title;document.title=document.documentElement.innerHTML;')
html = self.get_main_frame().get_title()
self.execute_script('document.title=oldtitle;')
return html
#wait 5 senconds and send a signal
class TimeSender(gobject.GObject, threading.Thread):
def __init__(self):
self.__gobject_init__()
threading.Thread.__init__(self)
def myEmit(self):
window.emit("Sender_signal")
def run(self):
print "sleep 5 seconds"
time.sleep(5)
gobject.idle_add(self.myEmit)
gobject.type_register(TimeSender)
#PywebkitGTK, open google.com, receive signal
class Window(gtk.Window, gobject.GObject):
def __init__(self, time_sender, url):
self.__gobject_init__()
gtk.Window.__init__(self)
self.connect('Sender_signal', self._finished_loading)
self._url = url
def open_page(self):
self.view = WebView()
self.view.get_html()
self.view.open(self._url)
self.add(self.view)
gtk.main()
#write html to file
def _finished_loading(self, view1):
with open("pagehtml.html", 'w') as f:
f.write(self.view.get_html())
gtk.main_quit()
'''
def user_callback(object):
with open("pagehtml2.html", 'w') as f:
f.write(view.get_html())
gtk.main_quit()
'''
if __name__ == '__main__':
gobject.signal_new("Sender_signal", Window, gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
time_sender = TimeSender()
window = Window(time_sender, google)
#time_sender.connect("Sender_signal", user_callback)
time_sender.start()
window.open_page()

Related

DBus python unwanted blocked threads

I write a simple dbus service which create several threads. But when a service starts all threads became blocked. More or less this is my service:
import time
import threading
import gobject
import dbus
import dbus.service
import dbus.mainloop.glib
dbus.mainloop.glib.threads_init()
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
class DummyThread(threading.Thread):
def __init__(self):
super(DummyThread, self).__init__()
self.__running = True
def run(self):
while self.__running:
print "I'm a running thread..."
time.sleep(1.0)
def stop(self):
self.__running = False
class Service(dbus.service.Object):
def __init__(self):
super(Service, self).__init__(
dbus.service.BusName('example.service', bus.SessionBus()),
'/example/service')
self.__loop = gobject.MainLoop(is_running=True)
#dbus.service.method('example.service.Interface')
def echo(self, data):
print 'Received: {}'.format(str(data))
return data
def run(self):
self.__loop.run()
def quit(self):
if self.__loop.is_running():
self.__loop.stop()
if __name__ == '__main__':
service = Service()
print 'Starting dummy thread...'
dummy = DummyThread()
dummy.start()
print 'Starting echo service...'
try:
service.run()
except KeyboardInterrupt:
print '\nUser want to quit!'
print 'Stopping services and threads...'
dummy.stop()
dummy.join()
service.quit()
And also this is a little client:
import sys
import dbus
class Client(object):
def __init__(self):
service = dbus.SessionBus().get_object('example.service',
'/example/service')
self.echo = service.get_dbus_method('echo', 'example.service.Interface')
if __name__ == '__main__':
client = Client()
recv = client.echo(' '.join(sys.argv[1:]))
print 'Received: {}'.format(recv)
If you run the server, no messages of the thread are showed until any client make requests to service. So my question is: is it possible to make threads independent of the gMainLoop() used by DBus?
Thank you in advance!

cannot pass data between thread and Qt object in Python

I have created a GUI, on which i have to pass string data coming from serial COM port. One individual thread is handling the Serial data whereas Qt object needs to take that data and display it via 'setPlainText' method. It gives an error (on line i've marked in comments) is
"QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTextDocument(0x3ebaa68), parent's thread is QThread(0x3dd5c58), current thread is QThread(0x3fbd6a8)"
Heres my code;
import sys
from PyQt4 import QtGui
from My_GUI_code import Ui_Dialog
import serial # import Serial Library
import threading
import time
test=""
arduinoData = serial.Serial('COM2', 9600) #
index=0
incoming_data=""
device_0_V=""
class Serial_read(threading.Thread):
"""
Thread to read data coming from Arduino
"""
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global incoming_data
global device_0_V
global test
while 1:
while (arduinoData.inWaiting()==0): #Wait here until there is data
pass #do nothing
incoming_data = arduinoData.readline() #read the line of text from the serial port
if "V0" in incoming_data:
index = incoming_data.index("V0=")
device_0_V=incoming_data[index+3:index+6]
print device_0_V
#print incoming_data,
class Editor(QtGui.QMainWindow, threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
global device_0_V
super(Editor, self).__init__()
self.ui=Ui_Dialog()
#test=self.ui.Dev_1_V
self.ui.setupUi(self)
self.setWindowIcon(QtGui.QIcon('ICON.png'))
self.ui.Dev_1_V.setPlainText("anum")
self.show()
self.ui.Dev_1_ON.clicked.connect(self.handleButton)
def run(self):
global device_0_V
while 1:
self.ui.Dev_1_V.setPlainText(device_0_V) #<<here it gives ERROR
time.sleep(1)
def handleButton(self):
time = self.ui.time_dev_1.value()
self.ui.Dev_1_V.setPlainText(device_0_V)
print time
#print ('Hello World')
def main():
tx_socket_thread2 = Serial_read()
tx_socket_thread2.start()
app = QtGui.QApplication(sys.argv)
ex = Editor()
ex.start()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I've seen some relevant questions asked in Stackoverflow, but I am not able to understand the concept still, as I am new in Classes, Qt and OOP things. I know i am doing some basic mistake here... Any help will be highly appreciated.
So after some readings on related asked questions in Stack overflow, I've managed to achieve what I want, Heres the code;
import sys
from PyQt4 import QtGui, QtCore
from My_GUI_code import Ui_Dialog
import serial # import Serial Library
import threading
import time
test=""
arduinoData = serial.Serial('COM2', 9600) #
index=0
incoming_data=""
device_0_V=""
class Serial_read(threading.Thread):
"""
Thread to read data coming from Arduino
"""
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global incoming_data
global device_0_V
global test
while 1:
while (arduinoData.inWaiting()==0): #Wait here until there is data
pass #do nothing
incoming_data = arduinoData.readline() #read the line of text from the serial port
if "V0" in incoming_data:
index = incoming_data.index("V0=")
device_0_V=incoming_data[index+3:index+6]
print device_0_V
#print incoming_data,
class Editor(QtGui.QMainWindow):
def __init__(self):
#threading.Thread.__init__(self)
global device_0_V
super(Editor, self).__init__()
self.ui=Ui_Dialog()
#test=self.ui.Dev_1_V
self.ui.setupUi(self)
self.setWindowIcon(QtGui.QIcon('ICON.png'))
self.ui.Dev_1_V.setPlainText("anum")
self.show()
self.ui.Dev_1_ON.clicked.connect(self.handleButton)
self.worker = Worker(self) # an independent thread that will listen to a signal 'beep' and trigger a function self.update
self.connect(self.worker, QtCore.SIGNAL('beep'), self.update)
self.worker.start() # start the thread
def update(self, Serial_data):
# here, I am getting the Serial data via signaling
if "V0" in incoming_data:
index = incoming_data.index("V0=")
device_0_V=incoming_data[index+3:index+7]
self.ui.Dev_1_V.setPlainText(device_0_V)
def handleButton(self):
time = self.ui.time_dev_1.value()
self.ui.Dev_1_V.setPlainText(device_0_V)
print time
#print ('Hello World')
class Worker(QtCore.QThread):
def __init__(self, host_window):
super(Worker, self).__init__()
self.running = False
def run(self):
self.running = True
global incoming_data #kept the Serial data global
global device_0_V
while self.running:
#sending 'beep' signal to the main Qt object, with string data 'incoming_data'
self.emit(QtCore.SIGNAL('beep'), incoming_data)
time.sleep(0.1)
def stop(self):
self.running = False
def main():
tx_socket_thread2 = Serial_read()
tx_socket_thread2.start()
app = QtGui.QApplication(sys.argv)
ex = Editor()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Inside the main Qt object, I've created a QThread "Worker" that sends signals to main Qt object with the Serial data. The update function is triggered every time a signal arrives from worker thread, and then further reads the data coming from worker thread.
Got help from this question
Thank you #Andy and #SiHa for your participation
Here's a page describing what Qt objects are and are not thread-safe -- http://doc.qt.io/qt-4.8/threads-reentrancy.html
For the most part, GUI objects are not thread safe and you should avoid modifying them from other threads.
One way of affecting the GUI from other threads is to use the signal and slot system, which is safe to use between threads so long as any objects passed are thread-safe. This usually means creating a thread-safe data structure in the secondary thread and passing it along with a signal to the main thread, which then reads the data structure and updates the GUI.
A more advanced version of that design pattern is to use a 2-way queue. One queue is populated by the main thread, which creates worker threads that process the items in the queue. When finished, the worker threads populate the other queue with thread-safe return values that the main thread then processes. Signals and events are still used to notify the main and worker threads when there are items in the queue to process.
Also, unless absolutely want to directly manage the threads, you can use QRunnable and QThreadPool to kick off threads without the need to directly manage them.

Python: How to detect function completion and re-enable a button?

I'm new to python and in my project I have completed implementing a function which has implementation to get data from a micro-controller (sampling data over UART to my PC).
Which takes several seconds, it depends on how many samples I wish to collect. This works fine with straightforward python script.
However, I wish to implement a GUI and I chose PyQt to do it. All I want to do is call the function when a button is pressed.
I will try to explain what I want to achieve in sequential steps below:
Click the button.
Button is disabled.
Call the function collectDataFromUART().
Wait for/detect the data collection to complete (Several Seconds)
Re-enable the button.
I have a button clicked handler, as shown below:
self.ui.pushButton1.clicked.connect(self.handlepushButton1)
def handlepushButton1(self):
self.ui.textEdit1.append("Started")
collectDataFromUART()
What I'm not able to understand is how to detect the completion of the function collectDataFromUART() and only then re-enable the button.
Can anyone throw light on this? Examples will be really very helpful.
Help! Thank you.
I suggest put collectDataFromUART() in another thread and emit message when it done. Something like this:
mysignal = QtCore.pyqtSignal()
class NamedThread(QtCore.QThread):
def __init__(self, parent=None):
super(self.__class__, self).__init__(parent)
def run(self):
<some code from collectDataUART>
self.mysignal.emit()
class NamedWidget(QtGui.QWidget):
def __init__(self, parent=None):
<some code>
self.thread = NamedThread()
self.pushButton1.clicked.connect(self.handlepushButton1)
self.thread.mysignal.connect(lambda: self.pushButton1.setEnabled(True), QtCore.Qt.QueuedConnection)
def handlepushButton1(self):
self.pushButton1.setDisabled(True)
self.thread.start()
You also can add some information about status of execution in signal. To do so you need pyqtSiglnal([type of data you want to send]) after that just call emit with some data self.mysignal[type of data].emit(<data>)
For my opinion, sound like your should handle it by create QThread to receive your UART data. First, your have initiate thread for receive your UART data and close button. Next, wait for this thread. If success or fail, Create signal send back to main widget. Last, Handle signal data it and do want your want;
Little example;
import sys
import time
from PyQt4 import QtGui
from PyQt4 import QtCore
def collectDataFromUART ():
# Your collect data from UART
time.sleep(1)
data = 'UART'
return data
class QCustomThread (QtCore.QThread):
status = QtCore.pyqtSignal(object, object)
def __init__ (self, parentQWidget = None):
super(QCustomThread, self).__init__(parentQWidget)
def run (self):
try:
data = collectDataFromUART()
errorCode = None
except Exception, error:
data = None
errorCode = str(error)
self.status.emit(data, errorCode)
self.exit(0)
class QCustomMainWindow (QtGui.QMainWindow):
def __init__ (self):
super(QCustomMainWindow, self).__init__()
self.startQPushButton = QtGui.QPushButton('START')
self.startQPushButton.released.connect(self.requestWork)
self.setCentralWidget(self.startQPushButton)
def requestWork (self):
self.startQPushButton.setEnabled(False)
myQCustomThread = QCustomThread(self)
myQCustomThread.status.connect(self.relpyWork)
myQCustomThread.start()
def relpyWork (self, data, errorCode):
self.startQPushButton.setEnabled(True)
if errorCode == None:
QtGui.QMessageBox.information(self, 'Information', data)
else:
QtGui.QMessageBox.critical(self, 'Critical', errorCode)
myQApplication = QtGui.QApplication(sys.argv)
myQCustomMainWindow = QCustomMainWindow()
myQCustomMainWindow.show()
sys.exit(myQApplication.exec_())

PySide browser in a separate process

I trying to write a simple parser, which checks some web pages and if content on these pages is changed, then script sends url to the headless webkit browser which runs using PySide binding to Qt and makes a screenshot. I want this browser always running in the background separate process waiting for url to appear in queue. As soon as url comes, it makes screenshot, saves it and then returns to waiting.
I try to implement this behavior with this code(i cut the parser part):
import multiprocessing
import sys
from datetime import datetime
from PySide import QtGui, QtWebKit, QtCore
class Browser(QtWebKit.QWebPage):
def __init__(self, queue_in, queue_out):
self.app = QtGui.QApplication(sys.argv)
QtWebKit.QWebPage.__init__(self)
self.queue_out = queue_out
self.queue_in = queue_in
self.setViewportSize(QtCore.QSize(900, 900))
self.mainFrame().setScrollBarPolicy(QtCore.Qt.Horizontal, QtCore.Qt.ScrollBarAlwaysOff)
self.mainFrame().setScrollBarPolicy(QtCore.Qt.Vertical, QtCore.Qt.ScrollBarAlwaysOff)
self.mainFrame().loadFinished.connect(self._makeScreenshot)
self.makeScreenshotOf()
def makeScreenshotOf(self):
self.mainFrame().setUrl(QtCore.QUrl.fromEncoded(self.queue_in.get()))
def _makeScreenshot(self):
image = QtGui.QImage(self.viewportSize(), QtGui.QImage.Format_ARGB32)
painter = QtGui.QPainter(image)
self.mainFrame().render(painter)
painter.end()
file_name = datetime.now().strftime("%Y-%m-%d %H-%M-%S-%f") + ".png"
image.save(file_name)
self.queue_out.put(file_name)
self.makeScreenshotOf()
if __name__ == "__main__":
multiprocessing.set_start_method('spawn')
queue_in = multiprocessing.Queue()
queue_out = multiprocessing.Queue()
t = threading.Thread(target = Browser, args = (queue_in, queue_out))
t.start()
queue_in.put(url)
The problem is that on the first run process successfully stays on hold, awaiting url to appear in queue, but as soon as it gets url, process just stops, ignoring Qt connection
self.mainFrame().loadFinished.connect(self._makeScreenshot)
The thing is that if i directly inherit from Process
class Browser(multiprocessing.Process):
def __init__(self, queue_in, queue_out):
multiprocessing.Process.__init__(self)
self.queue_out = queue_out
self.queue_in = queue_in
self.app = QtGui.QApplication(sys.argv)
self.browser = QtWebKit.QWebPage()
...
if __name__ == "__main__":
queue_in = multiprocessing.Queue()
queue_out = multiprocessing.Queue()
b = Browser(queue_in, queue_out)
Then connection is not ignored and all works perfectly, but as a side effect self.queue_in.get() invoked in a Browser process also blocks main process (if queue is empty).
Questions:
Why the Qt connection is not working in the first case and working in the other?
Why in the second case queue.get() blocks the main process? How to prevent this?
Queue.get() blocks if the queue is empty. Use get_nowait(), which will raise an exception if there's nothing there.
Well it seems that call to app.exec_() was essential. Everything works now. Except that i get
warning qapplication was not created in the main()
Despite everything works anyway, i decided to move it into the main
app = QtGui.QApplication(sys.argv)
browser = Browser(queue_in, queue_out)
app.exec_()
and run parser part in a separate process.
UPD
Figured how to run QApplication in a process
class QtApp(QtGui.QApplication):
"""
docstring
"""
def __init__(self, args, url_queue, filename_queue):
QtGui.QApplication.__init__(self, args)
browser = Browser(url_queue, filename_queue)
self.exec_()
browser_process = multiprocessing.Process(target=QtApp,
args=(sys.argv, url_queue, filename_queue))

Introduce a text in a lineEdit of PyQt from a thread

How can I introduce a text in a lineEdit from a thread that are getting the data whithout colapse the program? The important line is in the class "fil" where it shows Principal.self.aplicacio.actual_lineEdit.setText(self.temp)
# !/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import serial
import threading
from time import sleep
from PyQt4 import QtCore, QtGui
from temperaturaUI import Ui_Form
class Principal(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.aplicacio = Ui_Form()
self.aplicacio.setupUi(self)
self.aplicacio.sortir_Button.clicked.connect(exit)
self.aplicacio.connectar_Button.clicked.connect(self.connectar)
def connectar(self):
try:
arduino = serial.Serial('/dev/ttyACM0', 9600)
print "Connectat amb èxit"
temperatura = fil(0, arduino, self.aplicacio.actual_lineEdit)
temperatura.start()
except:
print "Impossible connectar a l'Arduino"
class fil(threading.Thread):
def __init__(self, temp, serie, line):
threading.Thread.__init__(self)
self.temp = temp
self.serie = serie
self.line = line
def run(self):
try:
while 1:
self.temp = self.serie.readline()
if self.temp != 0:
**Principal.self.aplicacio.actual_lineEdit.setText(self.temp)**
sleep(0.2)
except:
print "Error al llegir de l'Arduino"
def main():
app = QtGui.QApplication(sys.argv)
aplicacio = Principal()
aplicacio.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You can use signals. You would add a signal to the fil class that emits the new text:
class fil(threading.Thread):
update_line_edit = pyqtSignal(str)
def __init__(self, temp, serie, line):
...
def run(self):
try:
while True:
self.temp = self.serie.readline()
if not self.temp:
update_line_edit.emit(self.temp)
...
Then, simply connect that signal to a slot function in your Principal class:
class Principal(QtGui.QWidget):
def __init__(self):
...
def connectar(self):
try:
arduino = serial.Serial('/dev/ttyACM0', 9600)
print "Connectat amb èxit"
temperatura = fil(0, arduino, self.aplicacio.actual_lineEdit)
temperatura.change_line_edit.connect(self.update_line_edit)
...
def update_line_edit(self, text):
self.aplicacio.actual_lineEdit.setText(text)
There are a few ways to do this correctly.
The first is to use a QThread instead of a python thread. You can then use Qt signals to pass a message back from the fil thread to the Qt MainThread and append the message to the QLineEdit there. Another similar approach is to continue using a Python thread, but place your message in a Python Queue.Queue() object. This Queue is then read by a secondary QThread, whose sole purpose is to read messages out of the Queue and emit a signal back to the MainThread.
The common feature of these two methods is that you only access Qt GUI objects from the MainThread and use signals/slots to communicate between threads. Here are some other questions where I've answered similar questions (you should be able to adapt them to your program):
Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread
Syncing activity in PyQt QThreads
However, since answering those questions, my colleagues and I have created a project that helps simplify writing multi-threaded Qt applications. The project is called qtutils and is on PyPi so it can be installed with pip or easy_install (just run pip install qtutils or easy_install qtutils from a commandline/terminal window).
This library has (among others) some functions inmain and inmain_later which will run a specified method in the Qt MainThread (regardless of the thread the call is made from) synchronously or asynchronously. Documentation on how to use these methods is here. I've modified your example code to use my inmain method and put the code here: http://pastebin.com/QM1Y6zBx -- obviously you need to install qtutils for it to work!

Categories