Howto: Make a HTML desktop app with PySide

Given a project structure like:

my_project
 main.py
 www/
    index.html
    scripts/ 
        jquery-1.9.1.min.js

main.py looks like:

import sys
from os.path import dirname, join
 
#from PySide.QtCore import QApplication
from PySide.QtCore import QObject, Slot, Signal
from PySide.QtGui import QApplication
from PySide.QtWebKit import QWebView, QWebSettings
from PySide.QtNetwork import QNetworkRequest
 
 
 
web     = None
myPage  = None
myFrame = None
 
class Hub(QObject):
 
    def __init__(self):
        super(Hub, self).__init__()
 
 
    @Slot(str)
    def connect(self, config):
        print config
        self.on_client_event.emit("Howdy!")
 
    @Slot(str)
    def disconnect(self, config):
        print config
 
    on_client_event = Signal(str)
    on_actor_event = Signal(str)
    on_connect = Signal(str)
    on_disconnect = Signal(str)
 
 
myHub = Hub()
 
class HTMLApplication(object):
 
    def show(self):
        #It is IMPERATIVE that all forward slashes are scrubbed out, otherwise QTWebKit seems to be
        # easily confused
        kickOffHTML = join(dirname(__file__).replace('\\', '/'), "www/index.html").replace('\\', '/')
 
        #This is basically a browser instance
        self.web = QWebView()
 
        #Unlikely to matter but prefer to be waiting for callback then try to catch
        # it in time.
        self.web.loadFinished.connect(self.onLoad)
        self.web.load(kickOffHTML)
 
        self.web.show()
 
    def onLoad(self):
        if getattr(self, "myHub", False) == False:
            self.myHub = Hub()
 
        #This is the body of a web browser tab
        self.myPage = self.web.page()
        self.myPage.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
        #This is the actual context/frame a webpage is running in.  
        # Other frames could include iframes or such.
        self.myFrame = self.myPage.mainFrame()
        # ATTENTION here's the magic that sets a bridge between Python to HTML
        self.myFrame.addToJavaScriptWindowObject("my_hub", myHub)
 
        #Tell the HTML side, we are open for business
        self.myFrame.evaluateJavaScript("ApplicationIsReady()")
 
 
#Kickoff the QT environment
app = QApplication(sys.argv)
 
myWebApp = HTMLApplication()
myWebApp.show()
 
sys.exit(app.exec_())

and index.html looks like:

<html>
    <head>
        <!-- Oh hell no, I can access anything on my computer via this -->
        <script src="./scripts/jquery-1.9.1.min.js"></script>
 
        <script language="javascript" type="text/javascript">
 
            function callback(){
                console.log("callback!");
                alert("called!");
            }
 
 
            function sendStuff() {
                console.log("sendStuff!");
                my_hub.connect("hello?");
            }
 
            function ApplicationIsReady(){
                console.log("ApplicationIsReady!");
                my_hub.on_client_event.connect(callback);
            }
 
        </script>
    </head>
    <body>
        Tell the hub to connect
        <button onclick=sendStuff()>No, click me!</button>
    </body>
    <script>
        alert(typeof jQuery);
    </script>
</html>

Basically class Hub is a bridge/interface pattern between Python and the HTML Javascript engines. It’s probably best to pass things between the two as JSON as that’s a language both sides are readily prepared to deal with.

Stuff I didn’t deal with yet: The Main window title ( self.web.setWindowTitle), setting a window Icon ( no clue on this one ).

Otherwise this gives me an OS agnostic UI, I can leverage my HTML skillset without having to learn QML or QT Designer’s UI interface, and I can hopefully recycle some logic. Additionally I can go full fledged with the bridge logic, split it between the two, or shove all the complexity into JS and have basic Python endpoints exposed.