OS X: Clicks on fully transparent parts of windows no longer passes through in Qt 5.6
-
Hi,
Shouldn't you also set the
Qt::WA_TransparentForMouseEvents
attribute for that ? -
Then I'd ask on the interest mailing list about that behavior change. You'll find there Qt's developers/maintainers (this forum is more user oriented)
-
@SGaist et al,
I tried the mailing list but haven't gotten any response so far. Can anyone recommend anything else to try?I'm finding myself stuck with Qt related issues in my app. So far, 5.3.x, 5.5.x, and 5.6 all have different, mutually exclusive bugs that are serious enough that I need to fix them. I suppose I could try 5.4.x and hope it works out. But it'd be much better if I could stay up to date with the latest stable Qt.
-
This is old but I'm bumping again, as I'm still stuck with this problem. I just tried Qt 5.7.0 and (not unexpectedly) it has the same behavior.
I'd really like to make use of the features of Qt 5.6 and later, especially since 5.6 is a version with long term support. But as long as I can't find a workaround for this issue, I'm stuck with 5.5.1.
-
If you can create a minimal compilable application that shows the behavior problem then you should take a look at the bug report system and see if something known. If not please consider opening a new report providing your sample application.
-
@SGaist Here is the bug report.
In case you or anyone else is interested, here is a minimal sample application that demonstrates the
// mainwindow.h #include <QMainWindow> class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = 0); protected: void paintEvent(QPaintEvent *); };
// main.cpp #include <QApplication> #include <QPainter> #include "mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setAttribute(Qt::WA_TranslucentBackground); setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); } void MainWindow::paintEvent(QPaintEvent *) { QPainter windowPainter(this); windowPainter.setBrush(Qt::red); windowPainter.drawRect(QRect(0,0,50,50)); windowPainter.setBrush(Qt::transparent); windowPainter.drawRect(rect().adjusted(0,0,-1,-1)); } int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow window; window.show(); return a.exec(); }
The app creates a transparent window that includes a frame outlining the window and a red box. When built with Qt 5.5.x, clicking on the frame or red box will activate the window, and clicking on the transparent area will click whatever element in under the window. When built with Qt 5.6 and later, clicking anywhere inside the frame of the window will activate the window and not pass through the click.
-
Thanks for sharing !
-
This is an old post but I am facint the same issue on PyQt6, TransluscentBackground set on MacOS, on Windows it works fine, I did not test on Linux yet, did you @Guy-Gizmo found a solution?
-
@bashtian said in OS X: Clicks on fully transparent parts of windows no longer passes through in Qt 5.6:
This is an old post but I am facint the same issue on PyQt6, TransluscentBackground set on MacOS, on Windows it works fine, I did not test on Linux yet, did you @Guy-Gizmo found a solution?
Yes, actually. You can check out the Shaped Clock example for a demonstration of how to do this: https://doc.qt.io/qt-6/qtwidgets-widgets-shapedclock-example.html
The key is to use
setMask()
so that Qt knows which regions are supposed to be transparent to clicks.That said, I did hit some issues with this. It's easy if the opaque region of your window is a simple shape like an ellipse or rectangle, since you can simply create a
QRegion
that covers that shape and set the mask to that, but that wasn't so in my case. There would be transparent pixels in any number of shapes that I wanted clicks to fall through. So I needed to render out the contents of my window as aQBitmap
with any fully transparent pixels being white and all others being black.BUT, there was another snag: on high-DPI displays it wouldn't quite work. At least on macOS the mask needed to have a pixel density of 1, even if the window itself has a pixel density of 2. So I wrote a method that generates a low density mask where it'll have a black pixel if any of the encompassing high-DPI pixels in the widget itself are the slightest bit non-transparent. I'll share that code here since others might find it useful:
void MyWindow::updateMask() { // In my case, the window contains a single top-level widget that does all of // the rendering named contentsWidget. // In order for this method to work, your widgets may need to be structured // like that too: QRect grabRect = rect().translated(-contentsWidget->geometry().topLeft()); QBitmap widgetMask = contentsWidget->grab(grabRect).mask(); if (devicePixelRatio() == 2) { // Unfortunately masks cannot be retina resolution, so if our widget is // on a retina display, we have to generate a mask at non-retina // resolution. But just rendering on a non-retina paint device and then // generating a mask unfortunately doesn't work, as there'll inevitably // be some pixels in the mask that should be black but are actually // white. The right way to do it is to render at retina resolution, and // then scale the bitmap mask down to the right size. But a normal // scale won't do -- each pixel on our mask needs to be black if any of // the four retina pixels that comprise it are black, i.e. a bitwise-OR // of the four pixels. So we need to do the scaling ourselves: QImage maskImage = widgetMask.toImage(); QImage scaledMaskImage(maskImage.size()/2, maskImage.format()); for(int y = 0; y < scaledMaskImage.height(); ++y) { uint16_t *line1 = (uint16_t *)maskImage.scanLine(y*2); uint16_t *line2 = (uint16_t *)maskImage.scanLine(y*2+1); uint8_t *line = scaledMaskImage.scanLine(y); for(int x = 0; x < scaledMaskImage.bytesPerLine(); ++x) { uint16_t a = line1[x]; uint16_t b = line2[x]; // This expression is the simplified version of the full expression // that will combine the mask's bits together as described above. line[x] = ~uint8_t((a & 0x0001) | (a>>1 & 0x0003) | (a>>2 & 0x0006) | (a>>3 & 0x000C) | (a>>4 & 0x0018) | (a>>5 & 0x0030) | (a>>6 & 0x0060) | (a>>7 & 0x00C0) | (a>>8 & 0x0080) | (b & 0x0001) | (b>>1 & 0x0003) | (b>>2 & 0x0006) | (b>>3 & 0x000C) | (b>>4 & 0x0018) | (b>>5 & 0x0030) | (b>>6 & 0x0060) | (b>>7 & 0x00C0) | (b>>8 & 0x0080)); // Not sure why it's necessary to negate this value given that // we're using the same format of image as the original mask } } setMask(QBitmap::fromImage(scaledMaskImage)); } else { setMask(widgetMask); } }
Note that this might not be necessary on Windows. My app is currently macOS only so I'm not sure what's necessary to get proper transparent rendering in Windows in high DPI displays just yet.
You mentioned your project is Python and this is C++ code, but if you need to do the same thing in Python this can at least show what it is you need to do. The syntax for writing out a mask image will be different in Python but the math should be the same.