Pygame not returning events while embedded in PyQt

Question:

I’m testing out an application and the UI uses PyQt4 with Pygame embedded into it. It uses a timer to “update” itself so to speak and in the timerEvent function Pygame attempts to retrieve all detected events. Issue is, Pygame isn’t detecting any events.

Here’s a minimalist version of my code

#!/etc/python2.7
from PyQt4 import QtGui
from PyQt4 import QtCore
import pygame
import sys

class ImageWidget(QtGui.QWidget):
    def __init__(self,surface,parent=None):
        super(ImageWidget,self).__init__(parent)
        w=surface.get_width()
        h=surface.get_height()
        self.data=surface.get_buffer().raw
        self.image=QtGui.QImage(self.data,w,h,QtGui.QImage.Format_RGB32)

        self.surface = surface

        self.timer = QtCore.QBasicTimer()
        self.timer.start(500, self)

    def timerEvent(self, event):
        w=self.surface.get_width()
        h=self.surface.get_height()
        self.data=self.surface.get_buffer().raw
        self.image=QtGui.QImage(self.data,w,h,QtGui.QImage.Format_RGB32)
        self.update()

        for ev in pygame.event.get():
            if ev.type == pygame.MOUSEBUTTONDOWN:
                print "Mouse down"

    def paintEvent(self,event):
        qp=QtGui.QPainter()
        qp.begin(self)
        qp.drawImage(0,0,self.image)
        qp.end()


class MainWindow(QtGui.QMainWindow):
    def __init__(self,surface,parent=None):
        super(MainWindow,self).__init__(parent)
        self.setCentralWidget(ImageWidget(surface))



pygame.init()
s=pygame.Surface((640,480))
s.fill((64,128,192,224))
pygame.draw.circle(s,(255,255,255,255),(100,100),50)

app=QtGui.QApplication(sys.argv)
w=MainWindow(s)
w.show()
app.exec_()

How can I get Pygame events while the Pygame window is embedded in a PyQt application?

Asked By: Edgecase

||

Answers:

You can not.


TL; DR;

You cannot and should not combine 2 libraries that have their own event loop, for example now the Qt eventloop is blocking the pygame event loop.

Answered By: eyllanesc

Fist of all do not mix frameworks. The frameworks may interact poorly or completely conflict with one another.
Getting it to work on your system doesn’t mean it will work on another system or with a different version of any of the frameworks.
Mixing frameworks always means some kind of undefined behavior.

In your example your create an image (pygame.Surface) with the Pygame library and display it in QWidget.
You never create a Pygame window. Therefore the Pygame event handling cannot work. You need to use Qts event handling.

Anyway, if you just want to do some image processing or draw some pictures and display them in a Qt application, I suggest using OpenCV (cv2). This library is designed for powerful image manipulation and the images can be viewed nicely using a Qt user interface.

Answered By: Rabbid76

A functional code implementation:

import time
import contextlib
from PySide6.QtCore import QObject, Signal, Qt, QThread
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import QMainWindow, QLabel, QWidget, QVBoxLayout, QApplication

with contextlib.redirect_stdout(None):
    import pygame

DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 600


class PygameWorker(QObject):
    send_image = Signal(QImage)

    def run(self):
        # Initialise screen
        pygame.init()

        screen = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT),
                                            pygame.OPENGL)

        # Fill background
        background = pygame.Surface(screen.get_size())
        background = background.convert()
        background.fill((250, 250, 250))

        # Display some text
        font = pygame.font.Font(None, 36)
        text = font.render("Hello There", 1, (10, 10, 10))
        textpos = text.get_rect()
        textpos.centerx = background.get_rect().centerx
        background.blit(text, textpos)

        # Blit everything to the screen
        screen.blit(background, (0, 0))

        while not QThread.currentThread().isInterruptionRequested():
            screen.blit(background, (0, 0))

            # Get the display image
            image_str = pygame.image.tostring(pygame.display.get_surface(), "RGB", False)
            buffer = QImage(image_str, DISPLAY_WIDTH, DISPLAY_HEIGHT, QImage.Format_RGB888)
            if buffer.isNull():
                print("Buffer is null")
                continue

            self.send_image.emit(buffer)
            time.sleep(0.1)
        print("Thread finished")


class PygameReceiver(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)

        self.main_widget = QWidget()
        self.setCentralWidget(self.main_widget)

        self.main_layout = QVBoxLayout()
        self.main_widget.setLayout(self.main_layout)

        self.pygame_worker = PygameWorker()
        self.pygame_worker.send_image.connect(self.update_image)
        self.pygame_worker_thread = QThread()
        self.pygame_worker.moveToThread(self.pygame_worker_thread)
        self.pygame_worker_thread.started.connect(self.pygame_worker.run)
        self.pygame_worker_thread.start()

        self.image_label = QLabel()
        self.image_label.setAlignment(Qt.AlignCenter)
        self.main_layout.addWidget(self.image_label)

    def update_image(self, image):
        qpixmap = QPixmap.fromImage(image)
        self.image_label.setPixmap(qpixmap)

    def closeEvent(self, event):
        self.pygame_worker_thread.requestInterruption()
        self.pygame_worker_thread.quit()
        self.pygame_worker_thread.wait()
        QMainWindow.closeEvent(self, event)


if __name__ == "__main__":
    app = QApplication()
    window = PygameReceiver()
    window.show()
    app.exec()
Answered By: Jaime02
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.