Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. OS X: Clicks on fully transparent parts of windows no longer passes through in Qt 5.6

OS X: Clicks on fully transparent parts of windows no longer passes through in Qt 5.6

Scheduled Pinned Locked Moved Unsolved General and Desktop
qt5.6framelesstransparencyqwidget
12 Posts 3 Posters 3.9k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Guy GizmoG Offline
    Guy GizmoG Offline
    Guy Gizmo
    wrote on last edited by
    #1

    I'm developing an OS X app that utilizes frameless windows which have areas that are fully transparent. This is done by setting the flag Qt::FramelessWindowHint and setting the attribute Qt::WA_TranslucentBackground on my top level QWidget.

    When built using Qt 5.5, clicking on any portion of the window that was transparent would have the click pass through the window to whatever screen element was underneath it. This is an important feature of the application: only portions of the window that are opaque should receive clicks, while clicking transparent portions should behave like the window is not there.

    I recently updated to the beta of Qt 5.6 since it has some important bug fixes I need. However, now clicks on transparent portions of my windows do not pass through.

    Is there anything I can do to get the behavior I want in Qt 5.6? Is this possibly a new bug?

    1 Reply Last reply
    1
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      Shouldn't you also set the Qt::WA_TransparentForMouseEvents attribute for that ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      Guy GizmoG 1 Reply Last reply
      0
      • SGaistS SGaist

        Hi,

        Shouldn't you also set the Qt::WA_TransparentForMouseEvents attribute for that ?

        Guy GizmoG Offline
        Guy GizmoG Offline
        Guy Gizmo
        wrote on last edited by
        #3

        @SGaist That would make clicks pass through for the entire window. I just want the transparent parts of the window to have clicks pass through.

        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          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)

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          Guy GizmoG 1 Reply Last reply
          0
          • SGaistS SGaist

            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)

            Guy GizmoG Offline
            Guy GizmoG Offline
            Guy Gizmo
            wrote on last edited by
            #5

            @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.

            1 Reply Last reply
            0
            • Guy GizmoG Offline
              Guy GizmoG Offline
              Guy Gizmo
              wrote on last edited by
              #6

              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.

              1 Reply Last reply
              0
              • SGaistS Offline
                SGaistS Offline
                SGaist
                Lifetime Qt Champion
                wrote on last edited by
                #7

                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.

                Interested in AI ? www.idiap.ch
                Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                Guy GizmoG 1 Reply Last reply
                0
                • SGaistS SGaist

                  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.

                  Guy GizmoG Offline
                  Guy GizmoG Offline
                  Guy Gizmo
                  wrote on last edited by
                  #8

                  @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.

                  1 Reply Last reply
                  0
                  • SGaistS Offline
                    SGaistS Offline
                    SGaist
                    Lifetime Qt Champion
                    wrote on last edited by
                    #9

                    Thanks for sharing !

                    Interested in AI ? www.idiap.ch
                    Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                    1 Reply Last reply
                    0
                    • bashtianB Offline
                      bashtianB Offline
                      bashtian
                      wrote on last edited by
                      #10

                      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?

                      Guy GizmoG 1 Reply Last reply
                      0
                      • bashtianB bashtian

                        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?

                        Guy GizmoG Offline
                        Guy GizmoG Offline
                        Guy Gizmo
                        wrote on last edited by
                        #11

                        @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 a QBitmap 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.

                        bashtianB 1 Reply Last reply
                        1
                        • Guy GizmoG Guy Gizmo

                          @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 a QBitmap 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.

                          bashtianB Offline
                          bashtianB Offline
                          bashtian
                          wrote on last edited by
                          #12

                          @Guy-Gizmo thank you very much for the answer, I will check it out, the click throu doesnt work at all on my mac, I will ttry to sort that out and see if I can combine both to achieve that effect.

                          1 Reply Last reply
                          0

                          • Login

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved