0

I am trying to write a simple application in PyQt5 with one QWidget that has the following two behaviors:

  1. Overrides the mouse cursor
  2. Transparent for input; meaning that it can ignore mouse inputs and send it to the background widget or OS UI (i.e. like an overlaying behavior)

I can achieve each behavior but individually i.e. When I combine them together the cursor returns to it's default state (i.e. I lose the ability to override it)!

I wonder if this is achievable in QT?

from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QWidget

if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)

    # Goal no. 1: override mouse cursor
    QApplication.setOverrideCursor(QtCore.Qt.WaitCursor)
    w = QWidget()
    w.setWindowOpacity(0.1)
    
    # Goal no.2: Make widget transparent for input
    w.setWindowFlags(w.windowFlags() 
    | QtCore.Qt.WindowTransparentForInput | QtCore.Qt.WindowStaysOnTopHint)

    w.resize(900, 900)
    w.show()
    sys.exit(app.exec_())
Rida Shamasneh
  • 767
  • 1
  • 6
  • 16
  • 1
    Surely if the Qt window is transparent for mouse input, the *underlying* window must determine the mouse cursor? Otherwise, there would be no true transparency... – ekhumoro Dec 18 '21 at 14:40
  • Is there a way to combine these two features into the same application? Do I need to change my way of thinking? – Rida Shamasneh Dec 18 '21 at 14:42
  • You appear to be asking: "can a window be transparent and not transparent at the same time?". What are you actually trying to achieve? – ekhumoro Dec 18 '21 at 14:45
  • I have a QT application that will control the mouse cursor of the system using a sepcial hardware ... And I need to animate the default mouse cursor to show some status/feedback ... So I thought that I could utilize QT to do that by creating a transparent widget and still utilize those QT features which help me to override my mouse cursor – Rida Shamasneh Dec 18 '21 at 14:49
  • Why do you need the mouse transparency at the same time? – ekhumoro Dec 18 '21 at 14:55
  • I need the q_widget itself to be transparent for input so my clicks pass through it and gets delivered to default system UI as if the q_widget does not exist; and at the same time override the q_widget cursor ... So basically the widget will be transparent and the mouse will be animated (using a gif maybe) – Rida Shamasneh Dec 18 '21 at 14:57
  • I don't really understand why you need to change the cursor *at the same time* - that seems to directly contradict the need for mouse transparency. Surely it would be much simpler to have a separate animation painted on the Qt window (which could perhaps follow mouse movements), and leave the system cursor at it is? (I'm assuming you have an independent way to track mouse movements). – ekhumoro Dec 18 '21 at 15:06
  • The QT application can currently drive & control the mouse in the four directions (up/down/left/right) based on some data that I receive from a special hardware on my side; later on I wanted to animate mouse cursor to display more information about the internal application status. I know that the best approach is to animate the windows mouse cursor programmatically but that approach is dangerous and could ruin the default mouse cursor if application quits abnormally due to internal app error; so i thought that I can animate the mouse cursor on the QT application level – Rida Shamasneh Dec 18 '21 at 15:09
  • that's why I am now trying to combine these two features together: I want widget to be transparent to control system UI as before; and also need to override mouse cursor to show some data on it. – Rida Shamasneh Dec 18 '21 at 15:14
  • When the mouse transparency is in effect, the Qt application does not have a cursor, so there's nothing to override. To override an *external cursor*, you'd need to use the relevant system APIs. – ekhumoro Dec 18 '21 at 15:24
  • 1
    I was able to override the external cursor using the relevant system APIs. But that's risky and not recommended ... that's why I am trying now to accomplish that in QT .. but i think you gave me a good idea which is painting some animation in the widget itself next to the system cursor position ... i am not sure if that works but i will investigate it – Rida Shamasneh Dec 18 '21 at 15:35
  • @ekhumoro I was able to accomplish smth using your great advise. please check the answer below – Rida Shamasneh Jan 03 '22 at 19:38

1 Answers1

1

Based on the great advice of @ekhumoro:

I found a solution to use a transparent widget and draw an animated shape on the screen that will follow the mouse cursor

The steps are:

  • Create a fully transparent widget (i.e. completely hidden)
  • This widget shall cover the whole screen; Similar to screenshot applications
  • This widget is then configured to ignore all mouse inputs ⇒ It should allow mouse cursor to interact with all UI behind it exactly as if this widget does not exist as all (e.g. user can click through this widget)
  • Then shapes/texts/animations can be drawn and moved on that transparent widget
  • The coordinates of these shapes can be determined based on the mouse cursor coordinates
  • The only limitation of this approach is that we not override the system cursor but we will be shown animations next to it which I believe satisfies the original use case
from PySide6 import QtCore, QtWidgets, QtGui
from PySide6.QtCore import QTimer
from PySide6.QtGui import QPainter, QFont
from PySide6.QtWidgets import QApplication


class TransparentWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(TransparentWidget, self).__init__(parent)

        self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

        # https://stackoverflow.com/questions/52827296/pyside2-pass-mouse-events-to-system
        self.setWindowFlags(self.windowFlags()
                            # | QtCore.Qt.WindowTransparentForInput
                            | QtCore.Qt.X11BypassWindowManagerHint
                            | QtCore.Qt.WindowStaysOnTopHint
                            | QtCore.Qt.WA_MouseNoMask
                            | QtCore.Qt.WA_TranslucentBackground
                            | QtCore.Qt.FramelessWindowHint)

        self.x = 0
        self.y = 0
        self.number = 4

    def paintEvent(self, event):
        print(f"paintEvent {self.x}  {self.y}")

        if not self.number:
            return

        painter = QPainter()
        painter.begin(self)

        font = QFont()
        font.setBold(True)
        font.setPixelSize(15)
        painter.setFont(font)

        pen = QtGui.QPen()
        pen.setWidth(3)
        pen.setColor(QtCore.Qt.red)
        painter.setPen(pen)

        painter.setBrush(QtCore.Qt.white)
        painter.drawEllipse(self.x + 15, self.y + 15, 30, 30)
        pen = QtGui.QPen()
        pen.setColor(QtCore.Qt.black)
        painter.setPen(pen)

        painter.drawText(self.x + 26, self.y + 35, str(self.number))


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    w = TransparentWidget()
    w.showFullScreen()


    def func():
        w.x += 1
        w.y += 1
        w.update()


    timer = QTimer()
    timer.timeout.connect(func)
    timer.start(10)

    sys.exit(app.exec())
Rida Shamasneh
  • 767
  • 1
  • 6
  • 16