Search code examples
pythonpyqt5qtwebengine

How To Close Application Using HTML Button


In my PyQt5 browser window I've loaded HTML and JavaScript. I've Included a button in the HTML which should close the Python-Application using vanilla JavaScript. Here is my Python code which makes the window and loads the HTML-File:

import sys
import os
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.browser = QWebEngineView()
        config_name = 'data_files/init.html'
        if getattr(sys, 'frozen', False):
            application_path = os.path.dirname(sys.executable)
        elif __file__:
            application_path = os.path.dirname(__file__)
        config_path = os.path.join(application_path, config_name)
        self.browser.load(QUrl().fromLocalFile(config_path))
        self.setCentralWidget(self.browser)
        self.showMaximized()


app = QApplication(sys.argv)
QApplication.setApplicationName('v0.1')
window = MainWindow()
app.exec_()

And In my HTML I have this:

<button onclick="exitApplication()">Exit</button>

<script>
    function exitApplication() {
        // How can I tell Python to call self.close() ??
    }
</script>

As I said I want to close the Application using the button inside the HTML. I've looked at various sources but I could never find anything that helped me out or was just too confusing.

How can I close the Application when calling the exitApplication()-Function?


Solution

  • First of all, it must be known that closing the window is not the same as exiting the application, if you have only one window and the property quitOnLastWindowClosed in True then they are equivalent but in general it is not the same.

    QtWebChannel:

    import sys
    import os
    from PyQt5.QtCore import pyqtSignal, pyqtSlot, QCoreApplication, QObject, QUrl
    from PyQt5.QtWidgets import QApplication, QMainWindow
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    from PyQt5.QtWebChannel import QWebChannel
    
    application_path = (
        os.path.dirname(sys.executable)
        if getattr(sys, "frozen", False)
        else os.path.dirname(__file__)
    )
    
    
    class Bridge(QObject):
        request_closed = pyqtSignal()
    
        @pyqtSlot()
        def quit(self):
            QCoreApplication.quit()
    
        @pyqtSlot()
        def close(self):
            self.request_closed.emit()
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.browser = QWebEngineView()
    
            self.bridge = Bridge()
    
            self.bridge.request_closed.connect(self.close)
    
            self.channel = QWebChannel()
            self.channel.registerObject("bridge", self.bridge)
            self.browser.page().setWebChannel(self.channel)
    
            config_name = "data_files/init.html"
            config_path = os.path.join(application_path, config_name)
            self.browser.load(QUrl.fromLocalFile(config_path))
            self.setCentralWidget(self.browser)
            self.showMaximized()
    
    
    if __name__ == "__main__":
    
        app = QApplication(sys.argv)
        QApplication.setApplicationName("v0.1")
        window = MainWindow()
        app.exec_()
    
    <!DOCTYPE html>
    <html>
      <head>
        <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
      </head>
      <body>
        <button onclick="exitApplication()">Exit</button>
    
        <script>
          var bridge = null;
          function exitApplication() {
            bridge.quit();
            // bridge.close();
          }
          if (typeof QWebChannel !== "undefined") {
            new QWebChannel(qt.webChannelTransport, function (channel) {
              bridge = channel.objects.bridge;
            });
          }
        </script>
      </body>
    </html>
    

    QWebEngineUrlSchemeHandler:

    import sys
    import os
    from PyQt5.QtCore import QCoreApplication, QUrl
    from PyQt5.QtWidgets import QApplication, QMainWindow
    from PyQt5.QtWebEngineCore import (
        QWebEngineUrlScheme,
        QWebEngineUrlSchemeHandler,
        QWebEngineUrlRequestJob,
    )
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    
    
    application_path = (
        os.path.dirname(sys.executable)
        if getattr(sys, "frozen", False)
        else os.path.dirname(__file__)
    )
    
    
    class QtSchemeHandler(QWebEngineUrlSchemeHandler):
        def requestStarted(self, job):
            if job.requestMethod() == b"POST" and job.requestUrl() == QUrl(
                "qt://commands/quit"
            ):
                QCoreApplication.quit()
                return
            job.fail(QWebEngineUrlRequestJob.RequestFailed)
    
    
    class MainWindow(QMainWindow):
        def __init__(self):
            super(MainWindow, self).__init__()
            self.browser = QWebEngineView()
            self.scheme_handler = QtSchemeHandler()
            self.browser.page().profile().installUrlSchemeHandler(
                b"qt", self.scheme_handler
            )
    
            config_name = "data_files/init.html"
            config_path = os.path.join(application_path, config_name)
            self.browser.load(QUrl.fromLocalFile(config_path))
            self.setCentralWidget(self.browser)
            self.showMaximized()
    
    
    if __name__ == "__main__":
    
        scheme = QWebEngineUrlScheme(b"qt")
        scheme.setFlags(QWebEngineUrlScheme.CorsEnabled)
        QWebEngineUrlScheme.registerScheme(scheme)
    
        app = QApplication(sys.argv)
        QApplication.setApplicationName("v0.1")
        window = MainWindow()
        app.exec_()
    
    <!DOCTYPE html>
    <html>
      <body>
        <button onclick="exitApplication()">Exit</button>
    
        <script>
          function exitApplication() {
            const xhr = new XMLHttpRequest();
            xhr.open("POST", "qt://commands/quit", true);
            xhr.send();
          }
        </script>
      </body>
    </html>