Qt 5.15.0 on MacOS - fully transparent widget with clickable image
-
I'm using Qt to develop an application that displays a video with transparent background on top of existing apps. The video is decoded frame by frame and rendered in a QWidget as an image with an alpha layer.
The way this is implemented, in order to fully display the image no matter the movement or change in bounding rect, is that the encompassing widget is rather large, say 800 width by 800 height, while the contents of the image is smaller, say 200 width by 200 height. This allows for the image to move frame by frame while staying within the bounding rect of the widget and therefore always be fully displayed without getting clipped "outside" the widget. The widget is transparent and declared as a frameless window in Qt. This is working fine, on Windows and MacOS, on Qt 5.14.2 and on Qt 5.15.0.
There is a second feature in this app which allows the user to click on the currently displayed frame which triggers an action. The click is only triggered if the user clicks on a painted pixel within the widget, and isn't consumed if the user clicks on a transparent pixel - this would have the expected behaviour of clicking and possibly focusing whatever window/application was behind the clicked pixel. This feature has been working fine on Qt 5.14.2 on both Windows and MacOS, however since jumping to Qt 5.15.0, still works on Windows but doesn't work on MacOS anymore: the behaviour since Qt 5.15.0 on MacOS is that the click is triggered whenever the user clicks on the widget, regardless of whether that's in a transparent part of the widget or a painted one. I also notice that the mouse cursor, which used to change only when hovering over the displayed image, now changes as soon as the mouse enters the full widget.
I've tried a lot of different small changes, such as setting/removing window flags (adding WA_TransparentForMouseEvents, WA_NoSystemBackground, removing WA_OpaquePaintEvent) or widget attributes, without success, and am currently at quite a loss understanding what could be the root cause of the problem and how to go about fixing it. The only noticeable change I've managed to get is to completely disable mouse events by setting the WindowTransparentForInput flag to true.
Here is a code snippet of the important parts of the widget that renders the video:
MovieRasterWidget::MovieRasterWidget(QWidget* parent) : #ifdef Q_OS_MAC QWidget(parent, Qt::Window | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus | Qt::NoDropShadowWindowHint | Qt::WindowStaysOnTopHint) #else QWidget(parent, Qt::Tool | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus | Qt::NoDropShadowWindowHint) #endif { setFocusPolicy(Qt::NoFocus); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_TranslucentBackground); setCursor(Qt::PointingHandCursor); ... // Other member initialisation here } void MovieRasterWidget::paintEvent(QPaintEvent* e) { QSize size = movie()->frameSize(); if (size.isValid()) { QPainter p(this); p.setRenderHint(QPainter::SmoothPixmapTransform); #ifdef Q_OS_MAC // Effacement de la fenetre (A cause du bug de Qt 5) QPainter::CompositionMode old_mode = p.compositionMode(); p.setCompositionMode(QPainter::CompositionMode_Clear); p.fillRect(p.viewport(), Qt::transparent); p.setCompositionMode(old_mode); #endif QImage image = movie()->currentImage(); m_MovieCurrentRect = movie()->currentRect(); WidgetContext context; context.geometry = geometry(); context.clipping = m_Clipping; context.scale = m_WidgetScale; context.opacity = m_WidgetOpacity; context.color = m_Color; context.desktop = QApplication::desktop()->geometry(); context.hotSpot = movie()->hotSpot(); context.hasInOut = movie()->hasInOut(); context.hasPole = movie()->hasPole(); paintMovie(&p, &context, image, m_MovieCurrentRect); } } static void paintMovie(QPainter* painter, WidgetContext* context, const QImage& image, const QRect& rect) { qreal widget_scale = context->scale; QRect widget_geometry = context->geometry; QRect widget_clipping = context->clipping; QColor widget_color = context->color; QRect desktop = widget_clipping.isNull() ? context->desktop : widget_clipping; // Rectangle destination QRect scaled_rect(rect.topLeft() * widget_scale, rect.size() * widget_scale); // Clipping QRect clip = scaled_rect.adjusted(-1, -1, +1, +1); painter->setClipRect(clip); if (!widget_clipping.isNull()) { QRect r = widget_clipping; r.translate(-widget_geometry.topLeft()); painter->setClipRect(r, Qt::IntersectClip); } // Mode pour melanger la video avec le fond if (widget_color.isValid()) { painter->fillRect(painter->viewport(), widget_color); painter->setCompositionMode(QPainter::CompositionMode_DestinationIn); } else painter->setCompositionMode(QPainter::CompositionMode_Source); painter->drawImage(painter->viewport(), image); }
The legacy code also mentions a MacOS-specific "fix" for Qt 5, which I cannot explain and does not solve the problem if I try to remove it. I've also checked the values for all the rectangles used in the different methods, which are the same between Qt 5.14.2 and Qt 5.15.0.
And this is the code for the updateGeometry, which gives the same values as well for the geometry between Qt 5.14.2 and Qt 5.15.0 :
void MovieRasterWidget::updateGeometry() { if (!m_Threaded) { movie()->lock(); QSize size = movie()->frameSize(); QPoint hotspot = movie()->hotSpot(); movie()->unlock(); if (size.isValid()) setGeometry(QRect(m_WidgetAdjustedPos - hotspot * m_WidgetScale, size * m_WidgetScale)); } updateImage(); }
I'm wondering what could be the root cause of this problem? What could have changed in Qt 5.15.0 that would explain such a behaviour with the mouse detection, and how would I go about tackling/fixing this problem? Any help or hints would be much appreciated.