Simulate mouse hover in PyQt5

Question:

How do you simulate mouse hover for PyQt5 to a coordinate? Perhaps with QPoint.

import sys

from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings

class Screenshot(QWebEngineView):
    def capture(self, url, output_file):
        self.output_file = output_file
        self.load(QUrl(url))
        self.loadFinished.connect(self.on_loaded)
        # Create hidden view without scrollbars
        #self.setAttribute(Qt.WA_DontShowOnScreen)
        self.page().settings().setAttribute(
            QWebEngineSettings.ShowScrollBars, False)
        self.show()

    def on_loaded(self):
        size = self.page().contentsSize().toSize()
        self.resize(size)
        # Wait for resize
        print("Capturing")
        QTimer.singleShot(3000, self.take_screenshot)

    def take_screenshot(self):
        self.grab().save(self.output_file, b'PNG')
        self.app.quit()


app = QApplication(sys.argv)
screenshots = {}

screenshot = Screenshot()
screenshot.app = app
screenshot.capture('https://zoom.earth/maps/wind-speed/#view=13.955336,121.109689,11z', 'wind.png')

Such that it shows a UI overlay like in the image.
Zoom Wind map, Mouse hovered at center

Answers:

Faking a mouse event is not that difficult, it’s just a matter of creating a QMouseEvent with the correct arguments:

event = QMouseEvent(QEvent.MouseMove, 
    QPoint(someX, someY), 
    Qt.NoButton,
    Qt.MouseButtons(Qt.NoButton), 
    Qt.KeyboardModifiers(Qt.NoModifier))
  • the QPoint argument represents the point in which the cursor is supposed to be, in local coordinates: in the following example I’ll use self.rect().center(), which is the exact center in the rectangle of the viewport;
  • the next argument is the mouse button that generates the QMouseEvent; since a mouse move event is not generated by a mouse button press, it should always be NoButton;
    . the argument after represents the buttons that are pressed at the time of the event; since we suppose that this is just a "hover" movement, we still have a NoButton value (but using the MouseButton flag, since it represents a flag and multiple buttons could be pressed);
  • the last is obviously about the keyboard modifiers, and we again don’t need any of that, but still we must use a flag;

The important part is to send the event to the correct widget. QWebEngineView uses a private QWidget subclass to show the actual content and receive user interaction, so we need to find that widget.

    def on_loaded(self):
        size = self.page().contentsSize().toSize()
        self.resize(size)

        # Get the actual web content widget
        child = self.findChild(QWidget)
        # Create a fake mouse move event
        event = QMouseEvent(QEvent.MouseMove, 
            self.rect().center(), 
            Qt.NoButton, Qt.MouseButtons(Qt.NoButton), 
            Qt.KeyboardModifiers(Qt.NoModifier))
        # Send the event
        QApplication.postEvent(child, event)

        # Wait for resize
        print("Capturing")
        QTimer.singleShot(3000, self.take_screenshot)
Answered By: musicamante

Working Code:

import sys

from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings

class Screenshot(QWebEngineView):
    def capture(self, url, output_file):
        self.output_file = output_file
        self.load(QUrl(url))
        self.loadFinished.connect(self.on_loaded)
        # Create hidden view without scrollbars
        #self.setAttribute(Qt.WA_DontShowOnScreen)
        self.page().settings().setAttribute(
            QWebEngineSettings.ShowScrollBars, False)
        self.show()

    def on_loaded(self):
        size = self.page().contentsSize().toSize()
        self.resize(size)
        # Wait for resize
        print("Capturing")
        QTimer.singleShot(3000, self.take_screenshot)

    def take_screenshot(self):
        self.grab().save(self.output_file, b'PNG')
        self.app.quit()


app = QApplication(sys.argv)
screenshots = {}

screenshot = Screenshot()
screenshot.app = app
screenshot.capture('https://zoom.earth/maps/wind-speed/#view=13.955336,121.109689,11z', 'wind.png')
Answered By: Ian Kirk Villanueva
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.