简体   繁体   中英

xmlprc inside Qt's (PyQt) event loop?

I want to use an xmlprc server along with a Qt app, in that case a Qt web engine, so than an xmlrpc client can send commands to it. I must do with an existing client.

We have two event loops here. Running Qt's exec_() is blocking, running xmlrpc's serve_forever() is blocking.

I was first wondering if you knew of an xmlrpc library for Python that integrates nicely with Qt's event loop. So everything would work out of the box.


I didn't find one, so I tried a bit starting the rpc server inside a thread. It basically works, but it cannot interact with the Qt widget. How can we do so ?

below with pyqt5==5.12 and PyQtWebEngine.

from PyQt5.QtCore import QUrl from PyQt5.QtCore import QThread
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QVBoxLayout
from PyQt5.QtWidgets import QWidget

from xmlrpc.server import SimpleXMLRPCServer

# Qt
URL_START = "http://ddg.gg"

app = QApplication([])
window = QWidget()
layout = QVBoxLayout()

webview = QWebEngineView()
webview.setUrl(QUrl(URL_START))

minibuffer = QWebEngineView()
mb_prompt = """
<html>
<div> hello minibuffer </div>
</html>
"""
minibuffer.setHtml(mb_prompt)

layout.addWidget(webview)
layout.addWidget(minibuffer)

window.setWindowTitle("My browser")
window.setLayout(layout)
window.show()

# xmlrpc
def hello(name):
    return "hello " + name

def set_minibuffer(name):
    return minibuffer.setHtml(mb_prompt.replace("minibuffer", name))


class RPCThread(QThread):
    def run(self):
        # sleep a little bit to make sure QApplication is running.
        self.sleep(1)
        print("--- starting server…")
        self.rpcserver = SimpleXMLRPCServer(("localhost", 8282))
        self.rpcserver.register_function(hello)
        self.rpcserver.register_function(set_minibuffer)

        self.rpcserver.serve_forever()

class RPCWidget(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.thread = RPCThread(self)
        self.thread.start()

rpcwidget = RPCWidget()
rpcwidget.show()

# Qt main loop.
print("--- Qt loop")
app.exec_()

From the client:

from xmlrpc.client import ServerProxy
client = ServerProxy("http://localhost:8282"
print(client.hello("me"))  # OK
# Trying to set the web engine's html content:
print(client.set_minibuffer("me")  # fails

this fails with

--- starting server…
127.0.0.1 - - [27/Mar/2019 16:52:01] "POST /RPC2 HTTP/1.1" 200 -
Received signal 11 SEGV_MAPERR 000000000000
#0 0x7f9b5bf3e8bf <unknown>
#1 0x7f9b5bf3ecbb <unknown>
#2 0x7f9b5bf3f33e <unknown>
#3 0x7f9b6667ef20 <unknown>
#4 0x7f9b5b9f3515 <unknown>
#5 0x7f9b5b9f37eb <unknown>
#6 0x7f9b5b9f3b79 <unknown>
#7 0x7f9b5a4731c0 QtWebEngineCore::WebContentsAdapter::setContent()
#8 0x7f9b610c7bf5 QWebEnginePage::setHtml()
#9 0x7f9b612fb824 meth_QWebEngineView_setHtml
#10 0x0000005030d5 <unknown>
#11 0x000000506859 _PyEval_EvalFrameDefault
#12 0x000000504c28 <unknown>
#13 0x00000058644b <unknown>
#14 0x00000059ebbe PyObject_Call
#15 0x000000507c17 _PyEval_EvalFrameDefault
[…]

You have 2 errors:

  • You can not and must not directly modify the GUI from another thread, in your case set_minibuffer() is executed in the thread where SimpleXMLRPCServer lives. Instead you must do it indirectly through pyqtSignal , QMetaObject::invokeMethod , custom QEvents or QTimer.singleShot(0, ...) .

  • I'm not an xmlrpc expert but in Why can't xmlrpc client append item to list accessable via xmlrpc server procedure? , it says that you must pass the SimpleXMLRPCServer object to the constructor allow_none = True .

Considering the above, the solution is:

from functools import partial

# ...

from PyQt5.QtCore import QTimer

# ...

def set_minibuffer(name):
    html = mb_prompt.replace("minibuffer", name)
    wrapper = partial(minibuffer.setHtml, html)
    QTimer.singleShot(0, wrapper)
    return html


class RPCThread(QThread):
    def run(self):
        # sleep a little bit to make sure QApplication is running.
        self.sleep(1)
        print("--- starting server…")
        self.rpcserver = SimpleXMLRPCServer(("localhost", 8282), allow_none=True)
        self.rpcserver.register_function(hello)
        self.rpcserver.register_function(set_minibuffer)

        self.rpcserver.serve_forever()

# ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM