how to create new window with flask-desktop - python

Searching on GUI-based http server (as I need to do some GUI notification from my program, when rest GET would be catched). Found this solution. How to do something like this properly (variant below isn't work):
#app.route('/')
def main():
# Create declarative and use it how I want
view = QDeclarativeView()
# Create an URL to the QML file
url = QUrl('view.qml')
# Set the QML file and show
view.setSource(url)
view.show()

The GUI creates an infinite loop, in case qt (pyqt4, pyqt5 and pyside) do it through the function exec_(), Flask also needs the same reason why both can not coexist in the same thread, therefore We created a new thread for Flask.
In this thread we will send the data through signals to the main thread and this will be responsible for displaying the data.
The following code implements the above.
*.py
from flask import Flask
from PySide import QtCore, QtGui, QtDeclarative
import sys
app = Flask(__name__)
#app.route('/')
def main():
w = FlaskThread._single
date = QtCore.QDateTime.currentDateTime()
w.signal.emit("date: {} function: {}".format(date.toString("dd.MM.yyyy hh:mm:ss.zzz"), "main"))
return "Hello world!"
class FlaskThread(QtCore.QThread):
signal = QtCore.Signal(str)
_single = None
def __init__(self, application):
QtCore.QThread.__init__(self)
if FlaskThread._single:
raise FlaskThread._single
FlaskThread._single = self
self.application = application
def __del__(self):
self.wait()
def run(self):
self.application.run()
def provide_GUI_for(application):
qtapp = QtGui.QApplication(sys.argv)
webapp = FlaskThread(application)
view = QtDeclarative.QDeclarativeView()
url = QtCore.QUrl('view.qml')
view.setSource(url)
root = view.rootObject()
webapp.signal.connect(lambda text: root.setProperty("text", text))
view.show()
qtapp.aboutToQuit.connect(webapp.terminate)
QtGui.QApplication.setQuitOnLastWindowClosed(False)
webapp.start()
return qtapp.exec_()
if __name__ == '__main__':
sys.exit(provide_GUI_for(app))
view.qml
import QtQuick 1.0
Text {
width: 320
height: 240
text: "nothing"
color: "red"
horizontalAlignment: Text.AlignHCenter
}

Related

PyQt app with API server. Unable to QTextEdit.setText()

I am trying to run PyQt (PyQt6.4.0) server with fastapi (0.89.1) and uvicorn (0.20.0). The code is as following:
# PyQt with server code
from fastapi import FastAPI
import threading
import uvicorn
from PyQt6.QtWidgets import (QMainWindow, QApplication, QTextEdit)
import sys
from PyQt6.QtCore import (QRect)
# main window app in main thread
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setGeometry(QRect(0, 0, 400, 400))
self._initial_widgets()
self._create_server()
def _initial_widgets(self):
self.textedit = QTextEdit(self)
self.textedit.setGeometry(QRect(100, 0, 100, 100))
self.textedit.setReadOnly(True)
self.setCentralWidget(self.textedit)
def _create_server(self):
self.app = FastAPI()
#self.app.post("/")
async def change_textEdit_data():
self.textedit.setText("hello") # By removing this important line there is no issue
return {"detail": "OK"}
# The server is going to be run in a child thread
# I believe that this is the source of the issue
# Child thread does not have access to self in parent thread
thread = threading.Thread(target=uvicorn.run, kwargs={"app": self.app, "port": 80})
thread.start() # runs server in separate thread
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
And to send a request to it
# Code to send test signal
from time import sleep
import requests as req
if __name__ == "__main__":
sleep(1)
r = req.post(url="http://127.0.0.1:80")
print(r.text, r.status_code)
Running order
PyQt with server code
Code to send test signal
The server has to be run in a thread to avoid UI freezing. (The freezing is not the actual issue)
The issue is that after I start the 2nd code in another process the following is appearing in console of the first one:
INFO: Started server process [14400]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:80 (Press CTRL+C to quit)
Process finished with exit code -1073741819 (0xC0000005)
It crashes.
Just by removing line self.textedit.setText("hello"), the issue is fixed, but the line is important.
Could someone suggest on what should I use to be able to change text in the textbox by using data received from API in PyQt app.
It is important that I keep PyQt and for the other libraries some other options can be suggested, i.e. sth instead of uvicorn.
Edit:
After I remove self.textedit.setText("hello") I am getting an expected reply
{"detail":"OK"} 200
So, the issue is definitely something with that line.
As suggested by #musicamante, the solution is simple:
Create a signal and create an instance of that object as below:
class Signals(QObject):
text_signal = pyqtSignal(str)
signals = Signals()
Create a function in the MainWindow class to change the text as follows:
class MainWindow(QMainWindow):
...
def set_textEdit(self, data):
self.textedit.setText(data)
Connect the signal and the function:
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
...
signals.text_signal.connect(self.set_textEdit)
Create implementation of emit in the API
class MainWindow(QMainWindow):
...
def _create_server(self):
...
#self.app.post("/")
async def change_textEdit_data(data):
signals.text_signal.emit(data)
return {"detail": "OK"}
...
Everything else remains the same and the final code is
# PyQt with server code
from fastapi import FastAPI
import threading
import uvicorn
from PyQt6.QtWidgets import (QMainWindow, QApplication, QTextEdit)
import sys
from PyQt6.QtCore import (QRect, pyqtSignal, QObject)
class Signals(QObject):
text_signal = pyqtSignal(str)
signals = Signals()
# main window app in main thread
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setGeometry(QRect(0, 0, 400, 400))
self._initial_widgets()
self._create_server()
signals.text_signal.connect(self.set_textEdit)
def _initial_widgets(self):
self.textedit = QTextEdit(self)
self.textedit.setGeometry(QRect(100, 0, 100, 100))
self.textedit.setReadOnly(True)
self.setCentralWidget(self.textedit)
def _create_server(self):
self.app = FastAPI()
#self.app.post("/")
async def change_textEdit_data(data):
signals.text_signal.emit(data)
return {"detail": "OK"}
thread = threading.Thread(target=uvicorn.run, kwargs={"app": self.app, "port": 81})
thread.start()
def set_textEdit(self, data):
self.textedit.setText(data)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

closeEvent for Window Python

I am creating an application on Qt Creator with python as my backend and I have a problem that i can't seem to pin point.
So what i want to do is have a function called when the user exits the application but the function
closeEvent(self, event:QCloseEvent) is never called.
The Constructor that the class inheriting the QMainWindow has is called but the over ridden methods aren't called.
Main.qml
import QtQuick
import QtQuick.Window
Window {
id: mainwindow
width: 640
height: 480
visible: true
color: "#000000"
flags: Qt.Window
title: qsTr("Hello World")
Connections{
target: backend
}
}
Main.py
# This Python file uses the following encoding: utf-8
import os
from pathlib import Path
import sys
from PySide6.QtGui import QCloseEvent
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtWidgets import QApplication,QMainWindow
class DetectQuit(QMainWindow):
def __init__(self):
print("From Constructor")
super().__init__()
print("End Constructor")
def hideEvent(self, event):
print("Minimizing Application")
def closeEvent(self, event:QCloseEvent) :
return super().closeEvent(event)
def closeEvent(self, event):
print("Exiting Application")
if __name__ == "__main__":
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
mainwindow=DetectQuit()
engine.rootContext().setContextProperty("backend",mainwindow)
engine.load(os.fspath(Path(__file__).resolve().parent / "main.qml"))
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec())
So what happens is that the DetectQuit class' constructor is called but the over-ridden functions are not called. If I add a self.show() in the constructor, there appears a second window titled "python" that calls the events accordingly.
Images for Visual assistance :
Running the Application Prints the Constructor part but not the Closing and hiding Events
This is with the Show method on the class which causes two windows to appear
Closing the second window that popped up causes the expected behaviour
Any help is greatly appreciated.
Well the thing is that the closeEvent() does not work so a workaround is that you can add a property to the Window Component of the qml that is onClosing like:
onClosing: {
print("Exiting Application (1)")
backend.closeFunction()
}
/*
// close.accepted = false -- if you need to do something else before the window can be closed
onClosing: function(close) {
print("Exiting Application (1)")
close.accepted = false
backend.closeFunction()
}
*/
This was Answered by #ndias on the Qt Forum
Hope this helps.

Splitting Python classes in PyQt5

I want to split one big class (UI) into several smaller ones and I have a problem with accessing the other fields in the rest of GUI.
I created a simple GUI using QT Creator and now I want to program it using PyQt5.
When I split the functions into separate classes and use the verifyBtn button I get an error:
self.hostname = self.IPInp.text()
AttributeError: 'bool' object has no attribute 'IPInp'
If verify_button function is in main UI class then program works fine. Do you have any idea how the code should look like so I can split the function into another class?
from PyQt5.QtWidgets import *
from PyQt5 import uic
import os, sys
class ButtonHandler(QMainWindow):
def __init__(self) -> None:
super().__init__()
def verify_button(self):
self.hostname = self.IPInp.text()
response = os.system("ping -n 1 " + self.hostname)
if response == 0:
self.verifyBtn.setStyleSheet('QPushButton { color: green;}')
self.verifyBtn.setText("OK")
else:
self.verifyBtn.setStyleSheet('QPushButton { color: red;}')
self.verifyBtn.setText("NO")
class UI(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("Test_gui.ui", self)
self.vBtn = ButtonHandler()
self.verifyBtn.clicked.connect(self.vBtn.verify_button)
if __name__ == '__main__':
app = QApplication([])
win = UI()
win.show()
sys.exit(app.exec_())
In the __init__-method of ButtonHandler you can pass a reference to UI in the following way (if you use toplevel_ui for the reference to UI)
class ButtonHandler(QMainWindow):
def __init__(self, toplevel) -> None:
super().__init__()
self.toplevel_ui = toplevel_ui
so when you create an instance of ButtonHandler from UI, you now write
self.vBtn = ButtonHandler(self)
and then when you want to access properties of the class UI from the class ButtonHandler you write self.toplevel_ui.<attribute_name> where you replace <attribute_name> with whatever you want. For example verifyBtn or verify_button.

Using QWebEngine to login to a SAML authorization page, wait for a cookie, and then cleanup / exit

I'm trying to write a PyQT QWebEngineView that opens a website, does a SAML login to AAD, returns, and once it sees a specific cookie (openconnect webvpn cookie), grabs the value and returns it to the "console" script which can continue processing and/or return to the command prompt.
I've glued together enough code that I can pop a browser window, step through my SAML authorization and see the cookie and cookie value. I don't know how to auto-close / exit the WebView window and "return" that cookie value and/or just the array to Python itself so I can keep processing it and/or exit. Not quite sure how to "clean up" my objects either.
I did probably fudge up my classes, initiators, and object variables. It's a kludge.
Thoughts? Ideas?
This is Arch Linux with latest Python and pyqt via package repo.
The code:
#!/usr/bin/python
#core python
import sys
#PyQT libraries
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtNetwork import *
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *
#functions / classes
class OpenconnectSamlAuth(QMainWindow):
#init self object
def __init__(self):
#inherit parents functions, classes, etc....
super(OpenconnectSamlAuth, self).__init__()
#create webview object
self.webview = QWebEngineView()
#grab profile
self.profile = QWebEngineProfile("storage", self.webview)
self.cookie_store = self.profile.cookieStore()
self.cookie_store.cookieAdded.connect(self.onCookieAdded)
#empty array of cookies
self.samlcookies = []
#set some window options
#window width x height
self.resize(1024, 768);
#default settings
self.mySettings = QWebEngineSettings.defaultSettings()
self.mySettings.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
#load URL / process login
def samlLogin(self,url):
#create page and load URL
webpage = QWebEnginePage(self.profile, self.webview)
self.webview.setPage(webpage)
self.webview.load(QUrl(url))
#windows options
self.setCentralWidget(self.webview)
#window title
self.webview.setWindowTitle('Loading...')
self.webview.titleChanged.connect(self.updateTitle)
#update title window
def updateTitle(self):
self.webview.setWindowTitle(self.webview.title())
#handle cookies being added
def onCookieAdded(self, cookie):
#check if cookies exists
#for c in self.cookies:
# if c.hasSameIdentifier(cookie):
# return
#self.cookies.append(QNetworkCookie(cookie)) return;
#bytearray(c.name()).decode()
print(bytearray( QNetworkCookie(cookie).name() ).decode() )
print(bytearray( QNetworkCookie(cookie).value() ).decode() )
return
#main loop
def main():
#initialize QT application object
App = QApplication(sys.argv)
#setup webkit window / browser session
OpenconnectWebObj = OpenconnectSamlAuth()
#load URL
OpenconnectWebObj.samlLogin("https://vpnserverurl/groupname")
#show connection window
OpenconnectWebObj.show()
#execute the app and grab the returned cookie
cookie = App.exec_()
print(cookie)
#exit
sys.exit()
#if called via command line; run this
if __name__ == '__main__':
main()
If you want to close the window then you must call the close() method, but in this case it seems that it requires terminating the Qt eventloop so the QCoreApplication.quit() method should be used. On the other hand, the cookie can be stored as an attribute and then used:
import sys
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtNetwork import QNetworkCookie
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtWebEngineWidgets import (
QWebEnginePage,
QWebEngineProfile,
QWebEngineSettings,
QWebEngineView,
)
class OpenconnectSamlAuth(QMainWindow):
def __init__(self, parent=None):
super(OpenconnectSamlAuth, self).__init__(parent)
self._cookie = None
self.webview = QWebEngineView()
self.profile = QWebEngineProfile("storage", self.webview)
self.cookie_store = self.profile.cookieStore()
self.cookie_store.cookieAdded.connect(self.handle_cookie_added)
self.profile.settings().setAttribute(QWebEngineSettings.JavascriptEnabled, True)
webpage = QWebEnginePage(self.profile, self)
self.webview.setPage(webpage)
self.webview.titleChanged.connect(self.update_title)
self.setCentralWidget(self.webview)
self.resize(1024, 768)
#property
def cookie(self):
return self._cookie
def login(self, url):
self.webview.load(QUrl.fromUserInput(url))
self.webview.setWindowTitle("Loading...")
def update_title(self):
self.webview.setWindowTitle(self.webview.title())
def handle_cookie_added(self, cookie):
print("added {name} : {value}".format(name=cookie.name(), value=cookie.value()))
if cookie.name() == b"name_of_cookie":
self._cookie = QNetworkCookie(cookie)
QCoreApplication.quit()
# main loop
def main():
app = QApplication(sys.argv)
openconnect_webobj = OpenconnectSamlAuth()
openconnect_webobj.login("https://vpnserverurl/groupname")
openconnect_webobj.show()
ret = app.exec_()
cookie = openconnect_webobj.cookie
if cookie is not None:
print("results:", cookie.name(), cookie.value(), cookie.toRawForm())
sys.exit(ret)
if __name__ == "__main__":
main()

PyQt4 & flask : Cannot create children for a parent that is in a different thread

I am trying to save image of a page on a http request to a flask server.
This is the message that I get on running the thing
QObject: Cannot create children for a parent that is in a different thread.
here is my code
import sys
import time
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtWebKit import *
import Image
from flask import Flask, Response, jsonify,request
app=Flask(__name__)
class Screenshot(QWebView):
def __init__(self):
self.app = QApplication(sys.argv)
QWebView.__init__(self)
self._loaded = False
self.loadFinished.connect(self._loadFinished)
def capture(self,url,width,output_file):
self.resize(width,300)
self.load(QUrl(url))
self.wait_load()
# set to webpage size
frame = self.page().mainFrame()
self.page().setViewportSize(frame.contentsSize())
# render image
image = QImage(self.page().viewportSize(), QImage.Format_ARGB32)
painter = QPainter(image)
frame.render(painter)
painter.end()
print 'saving', output_file
image.save(output_file)
def wait_load(self, delay=0):
# process app events until page loaded
while not self._loaded:
self.app.processEvents()
time.sleep(delay)
self._loaded = False
def _loadFinished(self, result):
self._loaded = True
if __name__=='__main__':
s = Screenshot()
#app.route('/image', methods=['GET','POST'])
def getPicture():
#reading args
url=request.args.get('url')
screenWidth=int(request.args.get('sw'))
top=int(request.args.get('top'))
left=int(request.args.get('left'))
width=int(request.args.get('width'))
height=int(request.args.get('height'))
#cropping image
s.capture(url,screenWidth,"temp.png")
img=Image.open("temp.png")
box=(left, top, left+width, top+height)
area=img.crop(box)
area.save("output","png")
return "output.png"
#app.route('/')
def api_root():
return 'Welcome'
app.run(host='0.0.0.0',port=3000,debug=True)
whenever I hit this using
curl http://0.0.0.0:3000/image?url=googlecom&sw=1920&top=100&left=100&width=200&height=200
I get the following error message,
QObject: Cannot create children for a parent that is in a different thread.
(Parent is Screenshot(0x7f9dbf121b90), parent's thread is QThread(0x7f9dbdb92240), current thread is QThread(0x7f9dbf11ed50)
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
I'm not sure that is the reason, but I don't see a self.app.exec_() call in Screenshot.__init__(). Without it, the QApplication instance never enters the main loop. BTW, I would instantiate QApplication outside of Screenshot but somewhere in your 'main' section. Not sure if it matters in this particular case, but it may.

Categories

Resources