如何在辅助显示器上全屏显示图像?[英] How to display image on secondary monitor in full screen?

本文是小编为大家收集整理的关于如何在辅助显示器上全屏显示图像?的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

如何使用Pyqt5/pyside或任何其他Python库在全屏模式下在辅助显示器上显示所需的图像?过去,我使用了一个framebuffer映像查看器( fbi fbi改进了).但是,这种方法要求我使用Linux.我更喜欢在Windows上工作,最好使用Python找到解决方案.

动机/上下文

我正在研究基于DLP投影的3D打印过程.当我使用HDMI将DLP投影仪连接到Windows PC时,它显示为第二个显示器.我想专用此辅助显示器(DLP)仅显示我所需的图案图像(PNG,BMP或SVG)用于3D打印过程.我想使用Python进行编程控制,以显示哪些图像正在显示. 这是 https:https:https:https://3dprinting.stackexchange.com/questions/1217/how-to-display-images-images-on-dlp-using-for-for-for-3d-printing

部分解决方案和问题

以下代码是一种可能的解决方案,但是我不确定其是正确或最有效的方法.我使用pyqt5:1)使用飞溅屏幕发现了两种方法,以及2)使用Qlabel.我的代码面临以下问题:

  • 光标是按预期隐藏的,但是,如果我不小心在辅助屏幕上单击鼠标,请关闭Splash屏幕.
  • 如果我使用Qlabel方法,我会看到一个白屏幕出现,然后加载了图像.从显示实际图像时,白屏幕出现的时间与〜0.5-1s的明显延迟.
  • 如果图像以高频显示(例如:每1秒),则该代码的工作原理不佳.例如,在代码中,将 total_loops = 1 更改为 total_loops = 25 .当使用Splash屏幕方法时,我看到启动屏幕出现在主屏幕上,然后移动到辅助屏幕.当使用Qlabel方法时,我所看到的只是一个白屏幕,只显示图像的最后一次迭代.此外,Qlabel的窗口在主屏幕上变得活跃,在任务栏中可见.
  • 如果要显示视频而不是图像,该如何处理情况?

对于3D打印应用程序,解决方案需要满足以下要求:

  • 辅助屏幕是DLP投影仪,它不应包含任何相关的Windows/taskbars/etc ...
  • 没有光标/鼠标或其他应用程序应出现在辅助屏幕上
  • 图像/视频需要以全屏模式显示
  • 在辅助屏幕上显示或更新图像时,主屏幕上不应有干扰.例如,辅助屏幕中的图像窗口不应从主屏幕中的当前活动窗口
  • 中的当前活动窗口删除.
import time
start_time = time.time() 
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QSplashScreen
from PyQt5.QtGui import QPixmap, QCursor
from PyQt5.QtCore import Qt
import os 

app = QApplication(sys.argv)

total_loops = 1

for i in range(total_loops):    

    # https://doc.qt.io/qtforpython/index.html
    # https://www.riverbankcomputing.com/static/Docs/PyQt5/module_index.html

    s = app.screens()[1] # Get the secondary screen 
    # Display info about secondary screen 
    print('Screen Name: {} Size: {}x{} Available geometry {}x{} '.format(s.name(), s.size().width(), s.size().height(), s.availableGeometry().width(), s.availableGeometry().height()))

    # Hide cursor from appearing on screen 
    app.setOverrideCursor(QCursor(Qt.BlankCursor)) # https://forum.qt.io/topic/49877/hide-cursor 

    # Select desired image to be displayed 
    pixmap = QPixmap('test.png')

    # Splash screen approach 
    # https://doc.qt.io/qtforpython/PySide2/QtWidgets/QSplashScreen.html?highlight=windowflags 
    splash = QSplashScreen(pixmap)      # Set the splash screen to desired image
    splash.show()                       # Show the splash screen
    splash.windowHandle().setScreen(s)  # Set splash screen to secondary monitor https://stackoverflow.com/a/30597458/4988010
    splash.showFullScreen()             # Show in splash screen in full screen mode 

    # # Qlabel apporach 
    # l = QLabel()
    # l.setPixmap(pixmap)
    # l.move(1920,0)
    # l.show()
    # l.windowHandle().setScreen(s) # https://stackoverflow.com/a/30597458/4988010
    # l.showFullScreen()

    time.sleep(0.5) 
    end_time = time.time() 
    print('Execution  time: ', end_time-start_time )

sys.exit(app.exec_())

推荐答案

以下代码是我问题的一种可能解决方案.我的解决方案假设QT仅用于在全屏幕上显示图像,而不是用于其余逻辑.因此,我不得不以辅助线程运行QT应用.这是因为我运行函数app.exec_()的那一刻,QT将连续运行事件循环,从而阻止我的python逻辑的其余部分,该逻辑不依赖QT.不建议在主线程外运行QApplication我的理解,因此,我欢迎一个经验丰富的用户发布更好的方法.

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QObject, pyqtSignal
import sys
import time
import threading


def main():

    print('Step 1')
    print('     Some logic here without QT')

    print('Step 2')
    print('     Launch QT app to run in background')
    myapp = myImageDisplayApp()

    print('Step 3')
    print('     Continue some logic while QT running in background')
    time.sleep(2)

    print('Step 4')
    print('     Update the displayed image in the QT app running in background')
    myapp.emit_image_update('qt_test_static_1.png')
    time.sleep(2)

    print('Step 5')
    print('     Update displayed image again')
    myapp.emit_image_update('qt_test_static_2.png')
    time.sleep(2)

class myImageDisplayApp (QObject):

    # Define the custom signal
    # https://www.riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html#the-pyqtslot-decorator
    signal_update_image = pyqtSignal(str)

    def __init__ (self):

        super().__init__()

        # Setup the seperate thread 
        # https://stackoverflow.com/a/37694109/4988010
        self.thread = threading.Thread(target=self.run_app_widget_in_background) 
        self.thread.daemon = True
        self.thread.start()

    def run_app_widget_in_background(self):
        self.app = QApplication(sys.argv)
        self.my_bg_qt_app = qtAppWidget(main_thread_object=self)
        self.app.exec_()

    def emit_image_update(self, pattern_file=None):
        print('emit_image_update signal')
        self.signal_update_image.emit(pattern_file)


class qtAppWidget (QLabel):

    def __init__ (self, main_thread_object):

        super().__init__()

        # Connect the singal to slot
        main_thread_object.signal_update_image.connect(self.updateImage)

        self.setupGUI()

    def setupGUI(self):

        self.app = QApplication.instance()

        # Get avaliable screens/monitors
        # https://doc.qt.io/qt-5/qscreen.html
        # Get info on selected screen 
        self.selected_screen = 0            # Select the desired monitor/screen

        self.screens_available = self.app.screens()
        self.screen = self.screens_available[self.selected_screen]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        # Create a black image for init 
        self.pixmap = QPixmap(self.screen_width, self.screen_height)
        self.pixmap.fill(QColor('black'))

        # Create QLabel object
        self.app_widget = QLabel()

        # Varioius flags that can be applied to make displayed window frameless, fullscreen, etc...
        # https://doc.qt.io/qt-5/qt.html#WindowType-enum
        # https://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum
        self.app_widget.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowDoesNotAcceptFocus | Qt.WindowStaysOnTopHint)
        # Hide mouse cursor 
        self.app_widget.setCursor(Qt.BlankCursor)       

        self.app_widget.setGeometry(0, 0, self.screen_width, self.screen_height)            # Set the size of Qlabel to size of the screen
        self.app_widget.setWindowTitle('myImageDisplayApp')
        self.app_widget.setAlignment(Qt.AlignLeft | Qt.AlignTop) #https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum                         
        self.app_widget.setPixmap(self.pixmap)
        self.app_widget.show()

        # Set the screen on which widget is on
        self.app_widget.windowHandle().setScreen(self.screen)
        # Make full screen 
        self.app_widget.showFullScreen()


    def updateImage(self, pattern_file=None):
        print('Pattern file given: ', pattern_file)
        self.app_widget.clear()                     # Clear all existing content of the QLabel
        self.pixmap = QPixmap(pattern_file)         # Update pixmap with desired image  
        self.app_widget.setPixmap(self.pixmap)      # Show desired image on Qlabel

if __name__ == "__main__":

    main() 

我还要感谢@ekhumoro将我指向qwidget属性/旗帜.

其他推荐答案

您不应在主线程以外的其他内部运行GUI,因为QT不能保证它正如 docs .您必须在另一个线程中执行其他繁重任务,而不是在另一个线程中执行GUI.

您必须更改经典顺序逻辑的方法,但必须使用面向事件的编程,在事件之前采取动作,在QT中通过信号进行操作.

考虑到上述问题,解决方案是:

import sys
import time


from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QThread, QTimer
from PyQt5.QtGui import QColor, QPixmap
from PyQt5.QtWidgets import QApplication, QLabel, QWidget


class TaskManager(QObject):
    task3Finished = pyqtSignal()
    task4Finished = pyqtSignal()

    @pyqtSlot()
    def task3(self):
        print("Step 3")
        print("     Continue some logic while QT running in background")
        time.sleep(2)
        self.task3Finished.emit()

    @pyqtSlot()
    def task4(self):
        print("Step 4")
        print("     Update the displayed image in the QT app running in background")
        time.sleep(2)
        self.task4Finished.emit()


class qtAppWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupGUI()

    def setupGUI(self):
        self.app = QApplication.instance()

        # Get avaliable screens/monitors
        # https://doc.qt.io/qt-5/qscreen.html
        # Get info on selected screen
        self.selected_screen = 0  # Select the desired monitor/screen

        self.screens_available = self.app.screens()
        self.screen = self.screens_available[self.selected_screen]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        # Create a black image for init
        self.pixmap = QPixmap(self.screen_width, self.screen_height)
        self.pixmap.fill(QColor("black"))

        # Create QLabel object
        self.app_widget = QLabel()

        # Varioius flags that can be applied to make displayed window frameless, fullscreen, etc...
        # https://doc.qt.io/qt-5/qt.html#WindowType-enum
        # https://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum
        self.app_widget.setWindowFlags(
            Qt.FramelessWindowHint
            | Qt.WindowDoesNotAcceptFocus
            | Qt.WindowStaysOnTopHint
        )
        # Hide mouse cursor
        self.app_widget.setCursor(Qt.BlankCursor)

        self.app_widget.setGeometry(
            0, 0, self.screen_width, self.screen_height
        )  # Set the size of Qlabel to size of the screen
        self.app_widget.setWindowTitle("myImageDisplayApp")
        self.app_widget.setAlignment(
            Qt.AlignLeft | Qt.AlignTop
        )  # https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum
        self.app_widget.setPixmap(self.pixmap)
        self.app_widget.show()

        # Set the screen on which widget is on
        self.app_widget.windowHandle().setScreen(self.screen)
        # Make full screen
        self.app_widget.show()

    @pyqtSlot()
    def on_task3_finished(self):
        pixmap = QPixmap("qt_test_static_1.png")
        self.app_widget.setPixmap(pixmap)

    @pyqtSlot()
    def on_task4_finished(self):
        pixmap = QPixmap("qt_test_static_2.png")
        self.app_widget.setPixmap(pixmap)

        # quit application after to 2 secons
        QTimer.singleShot(2 * 1000, QApplication.quit)


def main(args):
    print("Step 1")
    print("     Some logic here without QT")

    print("Step 2")
    print("     Launch QT app to run")
    app = QApplication(args)
    myapp = qtAppWidget()

    thread = QThread()
    thread.start()

    manager = TaskManager()
    # move the QObject to the other thread
    manager.moveToThread(thread)

    manager.task3Finished.connect(myapp.on_task3_finished)
    manager.task3Finished.connect(manager.task4)
    manager.task4Finished.connect(myapp.on_task4_finished)

    # start task
    QTimer.singleShot(0, manager.task3)

    ret = app.exec_()

    thread.quit()
    thread.wait()

    del thread, app

    return ret


if __name__ == "__main__":

    sys.exit(main(sys.argv))

本文地址:https://www.itbaoku.cn/post/2091202.html

问题描述

How can I use PyQt5/PySide or any other Python library to display a desired image on a secondary monitor in full-screen mode? In the past, I used a framebuffer image viewer (Fbi and Fbi improved). However, this approach requires me to use Linux. I prefer to work in Windows and preferably find a solution using Python.

Motivation/Context

I am working on a DLP projection based 3D printing process. When I connect a DLP projector to my Windows PC using HDMI, it shows up as a second monitor. I want to dedicate this secondary monitor (DLP) only to display my desired patterns images (png, bmp, or svg) for the 3D printing process. I would like to programmatically control using Python which image is being displayed. This is a followup question to https://3dprinting.stackexchange.com/questions/1217/how-to-display-images-on-dlp-using-hdmi-for-3d-printing

Partial solution and issues

Below code is one possible solution, however I am unsure if its the correct or the most efficient approach. I found two approaches using PyQt5: 1) using splash screen, and 2) using QLabel. I am facing the following issues with my code:

  • Cursor is hidden as expected, however if I accidentally click mouse on secondary screen, the splash screen closes.
  • If I use the QLabel approach, I see a white screen appear and then my image gets loaded. There is a distinct delay of ~ 0.5-1s from the time white screen appears to when the actual image is displayed.
  • If the images are displayed in high frequency (ex: every 1 sec), this code doesn't work well. For example, in the code change the total_loops=1 to total_loops=25. When using splash screen method, I see the splash screen appear on the main screen then it moves to the secondary screen. When using the QLabel method, all I see is a white screen appear, and only the last iteration the image is displayed. In addition, the window of the QLabel becomes active on the main screen and is visible in the Task bar.
  • How do I handle a situation if I want to display a video instead of an image?

For 3D printing application, the solution needs to meet the following requirement:

  • Secondary screen is the DLP projector, and it should NOT contain any OS related windows/taskbars/etc...
  • No cursor/mouse or other applications should appear on the the secondary screen
  • Images/videos need to be displayed in fullscreen mode
  • When displaying or updating images on the secondary screen, there should be no disturbance on the primary screen. For example, the image window in secondary screen shouldn't take focus away from currently active window in the primary screen
import time
start_time = time.time() 
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QSplashScreen
from PyQt5.QtGui import QPixmap, QCursor
from PyQt5.QtCore import Qt
import os 

app = QApplication(sys.argv)

total_loops = 1

for i in range(total_loops):    

    # https://doc.qt.io/qtforpython/index.html
    # https://www.riverbankcomputing.com/static/Docs/PyQt5/module_index.html

    s = app.screens()[1] # Get the secondary screen 
    # Display info about secondary screen 
    print('Screen Name: {} Size: {}x{} Available geometry {}x{} '.format(s.name(), s.size().width(), s.size().height(), s.availableGeometry().width(), s.availableGeometry().height()))

    # Hide cursor from appearing on screen 
    app.setOverrideCursor(QCursor(Qt.BlankCursor)) # https://forum.qt.io/topic/49877/hide-cursor 

    # Select desired image to be displayed 
    pixmap = QPixmap('test.png')

    # Splash screen approach 
    # https://doc.qt.io/qtforpython/PySide2/QtWidgets/QSplashScreen.html?highlight=windowflags 
    splash = QSplashScreen(pixmap)      # Set the splash screen to desired image
    splash.show()                       # Show the splash screen
    splash.windowHandle().setScreen(s)  # Set splash screen to secondary monitor https://stackoverflow.com/a/30597458/4988010
    splash.showFullScreen()             # Show in splash screen in full screen mode 

    # # Qlabel apporach 
    # l = QLabel()
    # l.setPixmap(pixmap)
    # l.move(1920,0)
    # l.show()
    # l.windowHandle().setScreen(s) # https://stackoverflow.com/a/30597458/4988010
    # l.showFullScreen()

    time.sleep(0.5) 
    end_time = time.time() 
    print('Execution  time: ', end_time-start_time )

sys.exit(app.exec_())

推荐答案

The code below is one possible solution to my question. My solution assumes that Qt is only used to display the images in full-screen and not for the remaining logic. Therefore, I had to run the QT app in a secondary thread. This is because the moment I run the function app.exec_(), Qt will continuously run an event loop thus blocking the rest of my Python logic which does NOT rely on Qt. It is my understanding running QApplication outside of a main thread is not recommended, therefore I would welcome a more experienced user to post a better approach.

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QObject, pyqtSignal
import sys
import time
import threading


def main():

    print('Step 1')
    print('     Some logic here without QT')

    print('Step 2')
    print('     Launch QT app to run in background')
    myapp = myImageDisplayApp()

    print('Step 3')
    print('     Continue some logic while QT running in background')
    time.sleep(2)

    print('Step 4')
    print('     Update the displayed image in the QT app running in background')
    myapp.emit_image_update('qt_test_static_1.png')
    time.sleep(2)

    print('Step 5')
    print('     Update displayed image again')
    myapp.emit_image_update('qt_test_static_2.png')
    time.sleep(2)

class myImageDisplayApp (QObject):

    # Define the custom signal
    # https://www.riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html#the-pyqtslot-decorator
    signal_update_image = pyqtSignal(str)

    def __init__ (self):

        super().__init__()

        # Setup the seperate thread 
        # https://stackoverflow.com/a/37694109/4988010
        self.thread = threading.Thread(target=self.run_app_widget_in_background) 
        self.thread.daemon = True
        self.thread.start()

    def run_app_widget_in_background(self):
        self.app = QApplication(sys.argv)
        self.my_bg_qt_app = qtAppWidget(main_thread_object=self)
        self.app.exec_()

    def emit_image_update(self, pattern_file=None):
        print('emit_image_update signal')
        self.signal_update_image.emit(pattern_file)


class qtAppWidget (QLabel):

    def __init__ (self, main_thread_object):

        super().__init__()

        # Connect the singal to slot
        main_thread_object.signal_update_image.connect(self.updateImage)

        self.setupGUI()

    def setupGUI(self):

        self.app = QApplication.instance()

        # Get avaliable screens/monitors
        # https://doc.qt.io/qt-5/qscreen.html
        # Get info on selected screen 
        self.selected_screen = 0            # Select the desired monitor/screen

        self.screens_available = self.app.screens()
        self.screen = self.screens_available[self.selected_screen]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        # Create a black image for init 
        self.pixmap = QPixmap(self.screen_width, self.screen_height)
        self.pixmap.fill(QColor('black'))

        # Create QLabel object
        self.app_widget = QLabel()

        # Varioius flags that can be applied to make displayed window frameless, fullscreen, etc...
        # https://doc.qt.io/qt-5/qt.html#WindowType-enum
        # https://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum
        self.app_widget.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowDoesNotAcceptFocus | Qt.WindowStaysOnTopHint)
        # Hide mouse cursor 
        self.app_widget.setCursor(Qt.BlankCursor)       

        self.app_widget.setGeometry(0, 0, self.screen_width, self.screen_height)            # Set the size of Qlabel to size of the screen
        self.app_widget.setWindowTitle('myImageDisplayApp')
        self.app_widget.setAlignment(Qt.AlignLeft | Qt.AlignTop) #https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum                         
        self.app_widget.setPixmap(self.pixmap)
        self.app_widget.show()

        # Set the screen on which widget is on
        self.app_widget.windowHandle().setScreen(self.screen)
        # Make full screen 
        self.app_widget.showFullScreen()


    def updateImage(self, pattern_file=None):
        print('Pattern file given: ', pattern_file)
        self.app_widget.clear()                     # Clear all existing content of the QLabel
        self.pixmap = QPixmap(pattern_file)         # Update pixmap with desired image  
        self.app_widget.setPixmap(self.pixmap)      # Show desired image on Qlabel

if __name__ == "__main__":

    main() 

I would also like to thank @ekhumoro for pointing me to QWidget attributes/flags.

其他推荐答案

You should not run the GUI in other than the main thread since Qt does not guarantee that it works correctly as indicated by the docs. Instead of executing the GUI in another thread, you must execute the other heavy tasks in another thread.

You have to change your approach to classical sequential logic but you must use event-oriented programming where actions are taken before an event, in the case of Qt through signals.

Considering the above, the solution is:

import sys
import time


from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QThread, QTimer
from PyQt5.QtGui import QColor, QPixmap
from PyQt5.QtWidgets import QApplication, QLabel, QWidget


class TaskManager(QObject):
    task3Finished = pyqtSignal()
    task4Finished = pyqtSignal()

    @pyqtSlot()
    def task3(self):
        print("Step 3")
        print("     Continue some logic while QT running in background")
        time.sleep(2)
        self.task3Finished.emit()

    @pyqtSlot()
    def task4(self):
        print("Step 4")
        print("     Update the displayed image in the QT app running in background")
        time.sleep(2)
        self.task4Finished.emit()


class qtAppWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupGUI()

    def setupGUI(self):
        self.app = QApplication.instance()

        # Get avaliable screens/monitors
        # https://doc.qt.io/qt-5/qscreen.html
        # Get info on selected screen
        self.selected_screen = 0  # Select the desired monitor/screen

        self.screens_available = self.app.screens()
        self.screen = self.screens_available[self.selected_screen]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        # Create a black image for init
        self.pixmap = QPixmap(self.screen_width, self.screen_height)
        self.pixmap.fill(QColor("black"))

        # Create QLabel object
        self.app_widget = QLabel()

        # Varioius flags that can be applied to make displayed window frameless, fullscreen, etc...
        # https://doc.qt.io/qt-5/qt.html#WindowType-enum
        # https://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum
        self.app_widget.setWindowFlags(
            Qt.FramelessWindowHint
            | Qt.WindowDoesNotAcceptFocus
            | Qt.WindowStaysOnTopHint
        )
        # Hide mouse cursor
        self.app_widget.setCursor(Qt.BlankCursor)

        self.app_widget.setGeometry(
            0, 0, self.screen_width, self.screen_height
        )  # Set the size of Qlabel to size of the screen
        self.app_widget.setWindowTitle("myImageDisplayApp")
        self.app_widget.setAlignment(
            Qt.AlignLeft | Qt.AlignTop
        )  # https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum
        self.app_widget.setPixmap(self.pixmap)
        self.app_widget.show()

        # Set the screen on which widget is on
        self.app_widget.windowHandle().setScreen(self.screen)
        # Make full screen
        self.app_widget.show()

    @pyqtSlot()
    def on_task3_finished(self):
        pixmap = QPixmap("qt_test_static_1.png")
        self.app_widget.setPixmap(pixmap)

    @pyqtSlot()
    def on_task4_finished(self):
        pixmap = QPixmap("qt_test_static_2.png")
        self.app_widget.setPixmap(pixmap)

        # quit application after to 2 secons
        QTimer.singleShot(2 * 1000, QApplication.quit)


def main(args):
    print("Step 1")
    print("     Some logic here without QT")

    print("Step 2")
    print("     Launch QT app to run")
    app = QApplication(args)
    myapp = qtAppWidget()

    thread = QThread()
    thread.start()

    manager = TaskManager()
    # move the QObject to the other thread
    manager.moveToThread(thread)

    manager.task3Finished.connect(myapp.on_task3_finished)
    manager.task3Finished.connect(manager.task4)
    manager.task4Finished.connect(myapp.on_task4_finished)

    # start task
    QTimer.singleShot(0, manager.task3)

    ret = app.exec_()

    thread.quit()
    thread.wait()

    del thread, app

    return ret


if __name__ == "__main__":

    sys.exit(main(sys.argv))