[wayland] Use a dummy window for QtWayland's popup window handling

QtWayland only creates popup windows if they have a parent QWindow or
if there is any window which had input. It's not enough to fake an
enter, it needs to be either a pointer button press or key press.

As KWin's useraction menu doesn't have a parent and we most likely
never send a pointer press to any QWindow it doesn't get shown. To
circumvent this we create a dummy window and fake a button press/release
on the window. After that Qt is tricked into believing there's a parent
window and shows the popup.

Faking the input is only done with at least Qt 5.5 as QtWayland crashes
on pointer event without a keymap being installed. As KWin does not yet
send keymaps we better disable the dangerous code path. With Qt 5.5 the
crash condition is fixed.
This commit is contained in:
Martin Gräßlin 2015-05-18 11:23:45 +02:00
parent 3f94a2afc7
commit 29c2ae57e0
3 changed files with 52 additions and 0 deletions

View file

@ -221,6 +221,7 @@ void ApplicationWayland::continueStartupWithX()
Xcb::sync(); // Trigger possible errors, there's still a chance to abort Xcb::sync(); // Trigger possible errors, there's still a chance to abort
notifyKSplash(); notifyKSplash();
waylandServer()->createDummyQtWindow();
} }
); );
eglInitWatcher->setFuture(QtConcurrent::run([] { eglInitWatcher->setFuture(QtConcurrent::run([] {

View file

@ -27,6 +27,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// Client // Client
#include <KWayland/Client/connection_thread.h> #include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/registry.h> #include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
// Server // Server
#include <KWayland/Server/compositor_interface.h> #include <KWayland/Server/compositor_interface.h>
#include <KWayland/Server/datadevicemanager_interface.h> #include <KWayland/Server/datadevicemanager_interface.h>
@ -35,6 +36,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KWayland/Server/seat_interface.h> #include <KWayland/Server/seat_interface.h>
#include <KWayland/Server/shell_interface.h> #include <KWayland/Server/shell_interface.h>
// Qt
#include <QWindow>
// system // system
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
@ -94,6 +98,13 @@ void WaylandServer::init(const QByteArray &socketName)
// skip Xwayland clients, those are created using standard X11 way // skip Xwayland clients, those are created using standard X11 way
return; return;
} }
if (surface->client() == m_qtConnection) {
// one of Qt's windows
if (m_dummyWindowSurface && (m_dummyWindowSurface->id() == surface->surface()->id())) {
fakeDummyQtWindowInput();
return;
}
}
auto client = new ShellClient(surface); auto client = new ShellClient(surface);
if (auto c = Compositor::self()) { if (auto c = Compositor::self()) {
connect(client, &Toplevel::needsRepaint, c, &Compositor::scheduleRepaint); connect(client, &Toplevel::needsRepaint, c, &Compositor::scheduleRepaint);
@ -194,4 +205,37 @@ void WaylandServer::removeClient(ShellClient *c)
emit shellClientRemoved(c); emit shellClientRemoved(c);
} }
void WaylandServer::createDummyQtWindow()
{
if (m_dummyWindow) {
return;
}
m_dummyWindow.reset(new QWindow());
m_dummyWindow->setSurfaceType(QSurface::RasterSurface);
m_dummyWindow->show();
m_dummyWindowSurface = KWayland::Client::Surface::fromWindow(m_dummyWindow.data());
}
void WaylandServer::fakeDummyQtWindowInput()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
// we need to fake Qt into believing it has got any seat events
// this is done only when receiving either a key press or button.
// we simulate by sending a button press and release
auto surface = KWayland::Server::SurfaceInterface::get(m_dummyWindowSurface->id(), m_qtConnection);
if (!surface) {
return;
}
const auto oldSeatSurface = m_seat->focusedPointerSurface();
const auto oldPos = m_seat->focusedPointerSurfacePosition();
m_seat->setFocusedPointerSurface(surface, QPoint(0, 0));
m_seat->setPointerPos(QPointF(0, 0));
m_seat->pointerButtonPressed(Qt::LeftButton);
m_seat->pointerButtonReleased(Qt::LeftButton);
m_qtConnection->flush();
m_dummyWindow->hide();
m_seat->setFocusedPointerSurface(oldSeatSurface, oldPos);
#endif
}
} }

View file

@ -24,12 +24,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <QObject> #include <QObject>
class QWindow;
namespace KWayland namespace KWayland
{ {
namespace Client namespace Client
{ {
class ConnectionThread; class ConnectionThread;
class ShmPool; class ShmPool;
class Surface;
} }
namespace Server namespace Server
{ {
@ -89,6 +92,7 @@ public:
**/ **/
int createQtConnection(); int createQtConnection();
void createInternalConnection(); void createInternalConnection();
void createDummyQtWindow();
KWayland::Server::ClientConnection *xWaylandConnection() const { KWayland::Server::ClientConnection *xWaylandConnection() const {
return m_xwaylandConnection; return m_xwaylandConnection;
@ -108,6 +112,7 @@ Q_SIGNALS:
void shellClientRemoved(ShellClient*); void shellClientRemoved(ShellClient*);
private: private:
void fakeDummyQtWindowInput();
KWayland::Server::Display *m_display = nullptr; KWayland::Server::Display *m_display = nullptr;
KWayland::Server::CompositorInterface *m_compositor = nullptr; KWayland::Server::CompositorInterface *m_compositor = nullptr;
KWayland::Server::SeatInterface *m_seat = nullptr; KWayland::Server::SeatInterface *m_seat = nullptr;
@ -122,6 +127,8 @@ private:
} m_internalConnection; } m_internalConnection;
AbstractBackend *m_backend = nullptr; AbstractBackend *m_backend = nullptr;
QList<ShellClient*> m_clients; QList<ShellClient*> m_clients;
QScopedPointer<QWindow> m_dummyWindow;
KWayland::Client::Surface *m_dummyWindowSurface = nullptr;
KWIN_SINGLETON(WaylandServer) KWIN_SINGLETON(WaylandServer)
}; };