wayland: Introduce internal popup event filter
The event filter allows dismissing the user actions menu by clicking anywhere outside of it. BUG: 428408
This commit is contained in:
parent
30464e5c8b
commit
f5925e2f17
6 changed files with 104 additions and 51 deletions
|
@ -9,6 +9,7 @@
|
|||
#include "kwin_wayland_test.h"
|
||||
#include "platform.h"
|
||||
#include "cursor.h"
|
||||
#include "deleted.h"
|
||||
#include "effects.h"
|
||||
#include "internal_client.h"
|
||||
#include "screens.h"
|
||||
|
@ -65,6 +66,7 @@ private Q_SLOTS:
|
|||
void testChangeWindowType();
|
||||
void testEffectWindow();
|
||||
void testReentrantSetFrameGeometry();
|
||||
void testDismissPopup();
|
||||
};
|
||||
|
||||
class HelperWindow : public QRasterWindow
|
||||
|
@ -173,6 +175,7 @@ void HelperWindow::keyReleaseEvent(QKeyEvent *event)
|
|||
void InternalWindowTest::initTestCase()
|
||||
{
|
||||
qRegisterMetaType<KWin::AbstractClient *>();
|
||||
qRegisterMetaType<KWin::Deleted *>();
|
||||
qRegisterMetaType<KWin::InternalClient *>();
|
||||
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
|
||||
QVERIFY(applicationStartedSpy.isValid());
|
||||
|
@ -809,6 +812,41 @@ void InternalWindowTest::testReentrantSetFrameGeometry()
|
|||
QCOMPARE(client->pos(), QPoint(100, 100));
|
||||
}
|
||||
|
||||
void InternalWindowTest::testDismissPopup()
|
||||
{
|
||||
// This test verifies that a popup window created by the compositor will be dismissed
|
||||
// when user clicks another window.
|
||||
|
||||
// Create a toplevel window.
|
||||
QSignalSpy clientAddedSpy(workspace(), &Workspace::internalClientAdded);
|
||||
QVERIFY(clientAddedSpy.isValid());
|
||||
HelperWindow clientToplevel;
|
||||
clientToplevel.setGeometry(0, 0, 100, 100);
|
||||
clientToplevel.show();
|
||||
QTRY_COMPARE(clientAddedSpy.count(), 1);
|
||||
auto serverToplevel = clientAddedSpy.last().first().value<InternalClient *>();
|
||||
QVERIFY(serverToplevel);
|
||||
|
||||
// Create a popup window.
|
||||
QRasterWindow clientPopup;
|
||||
clientPopup.setFlag(Qt::Popup);
|
||||
clientPopup.setTransientParent(&clientToplevel);
|
||||
clientPopup.setGeometry(0, 0, 50, 50);
|
||||
clientPopup.show();
|
||||
QTRY_COMPARE(clientAddedSpy.count(), 2);
|
||||
auto serverPopup = clientAddedSpy.last().first().value<InternalClient *>();
|
||||
QVERIFY(serverPopup);
|
||||
|
||||
// Click somewhere outside the popup window.
|
||||
QSignalSpy popupClosedSpy(serverPopup, &InternalClient::windowClosed);
|
||||
quint32 timestamp = 0;
|
||||
kwinApp()->platform()->pointerMotion(QPointF(serverPopup->x() + serverPopup->width() + 1,
|
||||
serverPopup->y() + serverPopup->height() + 1),
|
||||
timestamp++);
|
||||
kwinApp()->platform()->pointerButtonPressed(BTN_LEFT, timestamp++);
|
||||
QTRY_COMPARE(popupClosedSpy.count(), 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WAYLANDTEST_MAIN(KWin::InternalWindowTest)
|
||||
|
|
86
input.cpp
86
input.cpp
|
@ -2560,10 +2560,44 @@ Toplevel *InputRedirection::findToplevel(const QPoint &pos)
|
|||
return u;
|
||||
}
|
||||
}
|
||||
if (Toplevel *window = findInternal(pos)) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return findManagedToplevel(pos);
|
||||
}
|
||||
|
||||
Toplevel *InputRedirection::findInternal(const QPoint &pos) const
|
||||
{
|
||||
const QList<InternalClient *> &internalClients = workspace()->internalClients();
|
||||
if (internalClients.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = internalClients.end();
|
||||
do {
|
||||
--it;
|
||||
QWindow *w = (*it)->internalWindow();
|
||||
if (!w || !w->isVisible()) {
|
||||
continue;
|
||||
}
|
||||
if (!(*it)->frameGeometry().contains(pos)) {
|
||||
continue;
|
||||
}
|
||||
// check input mask
|
||||
const QRegion mask = w->mask().translated(w->geometry().topLeft());
|
||||
if (!mask.isEmpty() && !mask.contains(pos)) {
|
||||
continue;
|
||||
}
|
||||
if (w->property("outputOnly").toBool()) {
|
||||
continue;
|
||||
}
|
||||
return *it;
|
||||
} while (it != internalClients.begin());
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos)
|
||||
{
|
||||
if (!Workspace::self()) {
|
||||
|
@ -2785,16 +2819,8 @@ void InputDeviceHandler::update()
|
|||
}
|
||||
|
||||
Toplevel *toplevel = nullptr;
|
||||
QWindow *internalWindow = nullptr;
|
||||
|
||||
if (positionValid()) {
|
||||
const auto pos = position().toPoint();
|
||||
internalWindow = findInternalWindow(pos);
|
||||
if (internalWindow) {
|
||||
toplevel = workspace()->findInternal(internalWindow);
|
||||
} else {
|
||||
toplevel = input()->findToplevel(pos);
|
||||
}
|
||||
toplevel = input()->findToplevel(position().toPoint());
|
||||
}
|
||||
// Always set the toplevel at the position of the input device.
|
||||
setAt(toplevel);
|
||||
|
@ -2803,11 +2829,12 @@ void InputDeviceHandler::update()
|
|||
return;
|
||||
}
|
||||
|
||||
if (internalWindow) {
|
||||
if (m_focus.internalWindow != internalWindow) {
|
||||
if (auto client = qobject_cast<InternalClient *>(toplevel)) {
|
||||
QWindow *handle = client->internalWindow();
|
||||
if (m_focus.internalWindow != handle) {
|
||||
// changed internal window
|
||||
updateDecoration();
|
||||
updateInternalWindow(internalWindow);
|
||||
updateInternalWindow(handle);
|
||||
updateFocus();
|
||||
} else if (updateDecoration()) {
|
||||
// went onto or off from decoration, update focus
|
||||
|
@ -2850,39 +2877,4 @@ QWindow *InputDeviceHandler::internalWindow() const
|
|||
return m_focus.internalWindow;
|
||||
}
|
||||
|
||||
QWindow* InputDeviceHandler::findInternalWindow(const QPoint &pos) const
|
||||
{
|
||||
if (waylandServer()->isScreenLocked()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QList<InternalClient *> &internalClients = workspace()->internalClients();
|
||||
if (internalClients.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = internalClients.end();
|
||||
do {
|
||||
--it;
|
||||
QWindow *w = (*it)->internalWindow();
|
||||
if (!w || !w->isVisible()) {
|
||||
continue;
|
||||
}
|
||||
if (!(*it)->frameGeometry().contains(pos)) {
|
||||
continue;
|
||||
}
|
||||
// check input mask
|
||||
const QRegion mask = w->mask().translated(w->geometry().topLeft());
|
||||
if (!mask.isEmpty() && !mask.contains(pos)) {
|
||||
continue;
|
||||
}
|
||||
if (w->property("outputOnly").toBool()) {
|
||||
continue;
|
||||
}
|
||||
return w;
|
||||
} while (it != internalClients.begin());
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
3
input.h
3
input.h
|
@ -316,6 +316,7 @@ private:
|
|||
void reconfigure();
|
||||
void setupInputFilters();
|
||||
void installInputEventFilter(InputEventFilter *filter);
|
||||
Toplevel *findInternal(const QPoint &pos) const;
|
||||
KeyboardInputRedirection *m_keyboard;
|
||||
PointerInputRedirection *m_pointer;
|
||||
TabletInputRedirection *m_tablet;
|
||||
|
@ -497,8 +498,6 @@ private:
|
|||
bool updateDecoration();
|
||||
void updateInternalWindow(QWindow *window);
|
||||
|
||||
QWindow* findInternalWindow(const QPoint &pos) const;
|
||||
|
||||
struct {
|
||||
QPointer<Toplevel> at;
|
||||
QMetaObject::Connection surfaceCreatedConnection;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <KDecoration2/Decoration>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QOpenGLFramebufferObject>
|
||||
#include <QWindow>
|
||||
|
||||
|
@ -388,6 +389,18 @@ void InternalClient::destroyClient()
|
|||
delete this;
|
||||
}
|
||||
|
||||
bool InternalClient::hasPopupGrab() const
|
||||
{
|
||||
return !m_internalWindow->flags().testFlag(Qt::WindowTransparentForInput) &&
|
||||
m_internalWindow->flags().testFlag(Qt::Popup) &&
|
||||
!m_internalWindow->flags().testFlag(Qt::ToolTip);
|
||||
}
|
||||
|
||||
void InternalClient::popupDone()
|
||||
{
|
||||
m_internalWindow->hide();
|
||||
}
|
||||
|
||||
void InternalClient::present(const QSharedPointer<QOpenGLFramebufferObject> fbo)
|
||||
{
|
||||
Q_ASSERT(m_internalImage.isNull());
|
||||
|
@ -438,8 +451,15 @@ bool InternalClient::acceptsFocus() const
|
|||
bool InternalClient::belongsToSameApplication(const AbstractClient *other, SameApplicationChecks checks) const
|
||||
{
|
||||
Q_UNUSED(checks)
|
||||
|
||||
return qobject_cast<const InternalClient *>(other) != nullptr;
|
||||
const InternalClient *otherInternal = qobject_cast<const InternalClient *>(other);
|
||||
if (!otherInternal) {
|
||||
return false;
|
||||
}
|
||||
if (otherInternal == this) {
|
||||
return true;
|
||||
}
|
||||
return otherInternal->internalWindow()->isAncestorOf(internalWindow()) ||
|
||||
internalWindow()->isAncestorOf(otherInternal->internalWindow());
|
||||
}
|
||||
|
||||
void InternalClient::doMove(int x, int y)
|
||||
|
|
|
@ -63,6 +63,8 @@ public:
|
|||
void setNoBorder(bool set) override;
|
||||
void updateDecoration(bool check_workspace_pos, bool force = false) override;
|
||||
void destroyClient() override;
|
||||
bool hasPopupGrab() const override;
|
||||
void popupDone() override;
|
||||
|
||||
void present(const QSharedPointer<QOpenGLFramebufferObject> fbo);
|
||||
void present(const QImage &image, const QRegion &damage);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "popup_input_filter.h"
|
||||
#include "abstract_client.h"
|
||||
#include "deleted.h"
|
||||
#include "internal_client.h"
|
||||
#include "workspace.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
@ -18,6 +19,7 @@ PopupInputFilter::PopupInputFilter()
|
|||
: QObject()
|
||||
{
|
||||
connect(workspace(), &Workspace::clientAdded, this, &PopupInputFilter::handleClientAdded);
|
||||
connect(workspace(), &Workspace::internalClientAdded, this, &PopupInputFilter::handleClientAdded);
|
||||
}
|
||||
|
||||
void PopupInputFilter::handleClientAdded(Toplevel *client)
|
||||
|
|
Loading…
Reference in a new issue