The invisible QApplication
Hello Qt community,
I am currently developing in project which involves rendering a Qt Application (or more specifically one widget) into a VR context. To that end I am spawning the Application in a contained thread and use everything Qt from there. When updates come in, I QWidget::render() into a QPixbuf and consume the buffer from there to be used as a texture in an rather extensive OpenGL application. This works surprisingly well so far.
However, my current challenge is, that the QApplication itself, main Window and all, still spawn to be visible on the desktop. I understand show() is necessary for the event loop to fire up and the actual rendering to happen.
What I need to do is to suppress this. I want a show() that doesn't show. ;-) I want the application to live and process signals and render the widgets all normally but all this without actually displaying anything to the user. All I need is the rendered pixbuf content, which I already have. I will then multiplex necessary input signals into the application to mimic user interaction.
Is there a way to do this?
I have the following preconditions:
- Windows and ideally Linux is OK. Other platforms are not required
- It would be alright to tell Qt to display into some kind of buffer or virtual screen or whatever as long as I can discard the content and only show what I have now. Possible performance penalty by this are acceptable
- A solution should not be too invasive
- It would be OK to use the Windows API or some other platform specific solution (I thought about unused OpenGL contexts)?
If anyone can give me a hint as to how I can proceed with that it would help a lot.
I would like to add current state of investigations. During the last couple of days I have experimented with the approach of trying to create a native Win32 Window, have Qt use it and try to turn this invisble.
Similar to this:
HWND native_window = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "Static", "InvisbleQtWrap", WS_VISIBLE, 100, 100, 800, 1200, 0, 0, 0, nullptr); ... QWindow *win = QWindow::fromWinId((WId)native_window); QWidget *container = QWidget::createWindowContainer(win); // create my widget using Container as parent. container->show();
This doesn't seem to work. I can get the native window to spawn and the QtContainer appears to take it over but there's nothing rendered and my widget seems to be dormant.
I will investigate in other directions now.
Maybe someone has an idea.
We support a command line option for hiding the GUI of our Qt based app. This works on Windows, Linux and Solaris.
Hiding a window can't be done in the constructor so you need to use a single shot timer.
In the main window constructor,
qApp->setQuitOnLastWindowClosed( false ); QTimer::singleShot( 0, this, SLOT( hide() ) );
The main app creates the window but does not call show or anything on it. We simply call the exec method for our application object.
@rturrentine Thanks a lot for your response. I just gave that a test. In my scenario the window remains hidden but the widgets are nor receiving paint events either.
I suppose this is due to the nature of Qt's event loop. In my research everything I read pointed towards "Your widgets will not be painted as long as they are hidden.". But I do need them painted so I can intercept the events and render() into my own buffer.
Or is there another way? Maybe I can send those events myself in a QTimer triggered frequency? Is that a viable approach?
If a Widget is hidden it gets no paint events as far as i know.
One thing you could test is the render() function
It lets you draw a widget directly to pixmap so I think it can work with hidden
widget but never tested it.
I know u already use render() but I wonder if u tried calling it directly, not using paintevent at all?
One thing that could also be issue is that while hidden , it do not receive resize events
so might be of wrong size even if Render() does work.
Maybe you already tested all this. Just asking :)
I want a show() that doesn't show.
I'm quite new to Qt. Please explain why setting the widget geometry (X, Y) to negative values doesn't take the widget off screen .. effectively hiding the widget?
For some platforms that might work.
But some platforms/windows managers do not allow it or
it can have side effects in multiple monitor setup.
Some windows managers might even help you and move it into view :)
@mrjj Yes, I tried calling this directly. It only produces an empty buffer though. I suppose this is due to the widget in question being quite complex.
It is a QWebEngineWidget.
It creates a lot of underlying child widgets and they pass their sizes and status back and forth. It wasn't even straight forward hooking up to it's paint event at all.
So when I trigger render on it, say from a QTimer without having the widget visible it simply paints a white screen into the buffer. At this point I can only assume that the size information are not correctly passed to the child widgets if it is invisible.
So far all my tests with manually generating events were inconclusive or negative. Personally I believe messing with the event loop too much will only bring complexity and no solution. My gut tells me a solution will work best if I can redirect the graphical output to the now legendary /dev/null without Qt knowing about it.
Thank you very much for the hint though!
Well Qt will queue resize events if widget is hidden. ( as far as i know)
so if your widget does stuff in these events then it will not have happened.
I was wondering if u played around with
In theory , you could construct the same events and send to it.
But yes, it suddenly becomes complex and I agree with your gut feeling that a
simple solution exists once found :)
I have tested the resize events. Contrary to what I thought hide() or any other means of hiding it don't cause any such events. At least not that I see them.
About the postEvent, that does sound interesting. I will need those things anyway, once I start creating user interaction on the by then hopefully invisible widget.
But to hide it in the first place (or making it work without show()ing it) I wouldn't know which events to send.
What I tried before was QShowEvent. After creating the widget and before I go into QApplication::exec() I tried sending one, hoping to fool the widget. It didn't work though.
The widget never went into paint(). And when I sent the paint myself it crashed somewhere in QtOpenGLs innards. Apparently some initial contexts have not been created. I guess show() is necessary for it to work.
I'm currently trying to move the widget into its own process to live remotely on its own machine where I'm not bothered by the output. I was so hoping to avoid the network bridge for such a seemingly simple thing...
So later the users will also click on the texture of the widgets
and it needs to react as the real widget would?
That is pretty advanced. :)
what types of widget do u need to support ? i if may ask.
Well, that's the plan. :-)
At the moment I will be OK with basic interaction such as mouse wheel and perhaps clicks. No keyboard or other stuff yet.
In the long run I am hoping to adapt more widgets as long as they are content with my basic means of interaction.
For now, QWebEngine is primary target as it gives me a modern web browser in-a-box with all capabilities I need. My research ha turned up no other implementation but Qt's here that can do this. Also, due to its complexity and overall weirdness (OpenGL, child widgets) I suppose if it works with that chances are it works with everything.
At the moment I am also looking into Windows 10's new virtual desktops. If I can open an application on one of those I wouldn't have to bridge the network and could grab the buffer via IPC or shared memory or something. This desktop would be my /dev/null. As long as Qt doesn't notice it's not actually visible that is.
Thats a fine plan :)
To boldly go .. ;)
Yes, if you can get QWebEngine to work as hidden then most other
widgets will just work / you will have the code to do it.
Oh, virtual desktops is a pretty good idea if not considered hidden by Qt when other desktop is active.
Far more fun than running on a other pc and need to handle that traffic too.
Thanks! To boldly go indeed. As a final little update in the matter I would like to add that my approach with the Windows 10 virtual desktops seems to work. I have created a shared memory server which runs the QApplication and renders into a shared memory buffer. This buffer is now consumed where the app-in-a-box used to live. And to my great relief Qt, being oblivious to new Windows features, seems not to notice that the application is actually invisible, so events are processed fine. Let's just pray this stays that way.
As a welcome side effect my actual application now suffers a lot less performance impact from creating and running the Qt app since it has only to access the buffer. Next step are the user events.
Thanks for your input!
Ok. super :)
Thank you for the feedback. it's good to know that being on other desktop is not the
same as being hidden.
Good luck with user interaction :)