Support touch events on KWin internal windows
Summary: Qt's touch event API is rather difficult and complex to implement. As none of KWin's internal windows supports multi-touch gestures yet, this is going the easy route and just simulates a left mouse button press. If in future need arises for touch gesture support on KWin's internal windows, this can be added. Test Plan: Tested on exopc with DebugConsole and auto test Reviewers: #plasma Subscribers: plasma-devel Tags: #plasma Differential Revision: https://phabricator.kde.org/D1661icc-effect-5.14.5
parent
d758eae6c5
commit
ff88f93852
|
@ -46,6 +46,7 @@ private Q_SLOTS:
|
|||
void testPointerAxis();
|
||||
void testKeyboard_data();
|
||||
void testKeyboard();
|
||||
void testTouch();
|
||||
};
|
||||
|
||||
class HelperWindow : public QRasterWindow
|
||||
|
@ -55,6 +56,13 @@ public:
|
|||
HelperWindow();
|
||||
~HelperWindow();
|
||||
|
||||
QPoint latestGlobalMousePos() const {
|
||||
return m_latestGlobalMousePos;
|
||||
}
|
||||
Qt::MouseButtons pressedButtons() const {
|
||||
return m_pressedButtons;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void entered();
|
||||
void left();
|
||||
|
@ -74,6 +82,10 @@ protected:
|
|||
void wheelEvent(QWheelEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
void keyReleaseEvent(QKeyEvent *event) override;
|
||||
|
||||
private:
|
||||
QPoint m_latestGlobalMousePos;
|
||||
Qt::MouseButtons m_pressedButtons = Qt::MouseButtons();
|
||||
};
|
||||
|
||||
HelperWindow::HelperWindow()
|
||||
|
@ -103,18 +115,21 @@ bool HelperWindow::event(QEvent *event)
|
|||
|
||||
void HelperWindow::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
m_latestGlobalMousePos = event->globalPos();
|
||||
emit mouseMoved(event->globalPos());
|
||||
}
|
||||
|
||||
void HelperWindow::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
m_latestGlobalMousePos = event->globalPos();
|
||||
m_pressedButtons = event->buttons();
|
||||
emit mousePressed();
|
||||
}
|
||||
|
||||
void HelperWindow::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
m_latestGlobalMousePos = event->globalPos();
|
||||
m_pressedButtons = event->buttons();
|
||||
emit mouseReleased();
|
||||
}
|
||||
|
||||
|
@ -282,6 +297,72 @@ void InternalWindowTest::testKeyboard()
|
|||
QCOMPARE(pressSpy.count(), 1);
|
||||
}
|
||||
|
||||
void InternalWindowTest::testTouch()
|
||||
{
|
||||
// touch events for internal windows are emulated through mouse events
|
||||
QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
|
||||
QVERIFY(clientAddedSpy.isValid());
|
||||
HelperWindow win;
|
||||
win.setGeometry(0, 0, 100, 100);
|
||||
win.show();
|
||||
QVERIFY(clientAddedSpy.wait());
|
||||
QCOMPARE(clientAddedSpy.count(), 1);
|
||||
|
||||
QSignalSpy pressSpy(&win, &HelperWindow::mousePressed);
|
||||
QVERIFY(pressSpy.isValid());
|
||||
QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased);
|
||||
QVERIFY(releaseSpy.isValid());
|
||||
QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved);
|
||||
QVERIFY(moveSpy.isValid());
|
||||
|
||||
quint32 timestamp = 1;
|
||||
QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
|
||||
kwinApp()->platform()->touchDown(0, QPointF(50, 50), timestamp++);
|
||||
QCOMPARE(pressSpy.count(), 1);
|
||||
QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
|
||||
QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
|
||||
|
||||
// further touch down should not trigger
|
||||
kwinApp()->platform()->touchDown(1, QPointF(75, 75), timestamp++);
|
||||
QCOMPARE(pressSpy.count(), 1);
|
||||
kwinApp()->platform()->touchUp(1, timestamp++);
|
||||
QCOMPARE(releaseSpy.count(), 0);
|
||||
QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
|
||||
QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
|
||||
|
||||
// another press
|
||||
kwinApp()->platform()->touchDown(1, QPointF(10, 10), timestamp++);
|
||||
QCOMPARE(pressSpy.count(), 1);
|
||||
QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50));
|
||||
QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
|
||||
|
||||
// simulate the move
|
||||
QCOMPARE(moveSpy.count(), 0);
|
||||
kwinApp()->platform()->touchMotion(0, QPointF(80, 90), timestamp++);
|
||||
QCOMPARE(moveSpy.count(), 1);
|
||||
QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
|
||||
QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
|
||||
|
||||
// move on other ID should not do anything
|
||||
kwinApp()->platform()->touchMotion(1, QPointF(20, 30), timestamp++);
|
||||
QCOMPARE(moveSpy.count(), 1);
|
||||
QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
|
||||
QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton));
|
||||
|
||||
// now up our main point
|
||||
kwinApp()->platform()->touchUp(0, timestamp++);
|
||||
QCOMPARE(releaseSpy.count(), 1);
|
||||
QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
|
||||
QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
|
||||
|
||||
// and up the additional point
|
||||
kwinApp()->platform()->touchUp(1, timestamp++);
|
||||
QCOMPARE(releaseSpy.count(), 1);
|
||||
QCOMPARE(moveSpy.count(), 1);
|
||||
QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90));
|
||||
QCOMPARE(win.pressedButtons(), Qt::MouseButtons());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
WAYLANDTEST_MAIN(KWin::InternalWindowTest)
|
||||
|
|
126
input.cpp
126
input.cpp
|
@ -427,6 +427,81 @@ class InternalWindowEventFilter : public InputEventFilter {
|
|||
event->setAccepted(false);
|
||||
return QCoreApplication::sendEvent(found, event);
|
||||
}
|
||||
|
||||
bool touchDown(quint32 id, const QPointF &pos, quint32 time) override {
|
||||
auto seat = waylandServer()->seat();
|
||||
if (seat->isTouchSequence()) {
|
||||
// something else is getting the events
|
||||
return false;
|
||||
}
|
||||
auto touch = input()->touch();
|
||||
if (touch->internalPressId() != -1) {
|
||||
// already on a decoration, ignore further touch points, but filter out
|
||||
return true;
|
||||
}
|
||||
// a new touch point
|
||||
seat->setTimestamp(time);
|
||||
touch->update(pos);
|
||||
auto internal = touch->internalWindow();
|
||||
if (!internal) {
|
||||
return false;
|
||||
}
|
||||
touch->setInternalPressId(id);
|
||||
// Qt's touch event API is rather complex, let's do fake mouse events instead
|
||||
m_lastGlobalTouchPos = pos;
|
||||
m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y());
|
||||
QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers());
|
||||
e.setAccepted(false);
|
||||
QCoreApplication::sendEvent(internal.data(), &e);
|
||||
return true;
|
||||
}
|
||||
bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override {
|
||||
auto touch = input()->touch();
|
||||
auto internal = touch->internalWindow();
|
||||
if (!internal) {
|
||||
return false;
|
||||
}
|
||||
if (touch->internalPressId() == -1) {
|
||||
return false;
|
||||
}
|
||||
waylandServer()->seat()->setTimestamp(time);
|
||||
if (touch->internalPressId() != qint32(id)) {
|
||||
// ignore, but filter out
|
||||
return true;
|
||||
}
|
||||
m_lastGlobalTouchPos = pos;
|
||||
m_lastLocalTouchPos = pos - QPointF(internal->x(), internal->y());
|
||||
QMouseEvent e(QEvent::MouseMove, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers());
|
||||
QCoreApplication::instance()->sendEvent(internal.data(), &e);
|
||||
return true;
|
||||
}
|
||||
bool touchUp(quint32 id, quint32 time) override {
|
||||
auto touch = input()->touch();
|
||||
auto internal = touch->internalWindow();
|
||||
if (!internal) {
|
||||
return false;
|
||||
}
|
||||
if (touch->internalPressId() == -1) {
|
||||
return false;
|
||||
}
|
||||
waylandServer()->seat()->setTimestamp(time);
|
||||
if (touch->internalPressId() != qint32(id)) {
|
||||
// ignore, but filter out
|
||||
return true;
|
||||
}
|
||||
// send mouse up
|
||||
QMouseEvent e(QEvent::MouseButtonRelease, m_lastLocalTouchPos, m_lastGlobalTouchPos, Qt::LeftButton, Qt::MouseButtons(), input()->keyboardModifiers());
|
||||
e.setAccepted(false);
|
||||
QCoreApplication::sendEvent(internal.data(), &e);
|
||||
|
||||
m_lastGlobalTouchPos = QPointF();
|
||||
m_lastLocalTouchPos = QPointF();
|
||||
input()->touch()->setInternalPressId(-1);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
QPointF m_lastGlobalTouchPos;
|
||||
QPointF m_lastLocalTouchPos;
|
||||
};
|
||||
|
||||
class DecorationEventFilter : public InputEventFilter {
|
||||
|
@ -1338,4 +1413,55 @@ void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos)
|
|||
}
|
||||
}
|
||||
|
||||
void InputDeviceHandler::updateInternalWindow(const QPointF &pos)
|
||||
{
|
||||
const auto oldInternalWindow = m_internalWindow;
|
||||
bool found = false;
|
||||
// TODO: screen locked check without going through wayland server
|
||||
bool needsReset = waylandServer()->isScreenLocked();
|
||||
const auto &internalClients = waylandServer()->internalClients();
|
||||
const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible());
|
||||
if (!internalClients.isEmpty() && change) {
|
||||
auto it = internalClients.end();
|
||||
do {
|
||||
it--;
|
||||
if (QWindow *w = (*it)->internalWindow()) {
|
||||
if (!w->isVisible()) {
|
||||
continue;
|
||||
}
|
||||
if (w->geometry().contains(pos.toPoint())) {
|
||||
// check input mask
|
||||
const QRegion mask = w->mask().translated(w->geometry().topLeft());
|
||||
if (!mask.isEmpty() && !mask.contains(pos.toPoint())) {
|
||||
continue;
|
||||
}
|
||||
m_internalWindow = QPointer<QWindow>(w);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (it != internalClients.begin());
|
||||
if (!found) {
|
||||
needsReset = true;
|
||||
}
|
||||
}
|
||||
if (needsReset) {
|
||||
m_internalWindow.clear();
|
||||
}
|
||||
if (oldInternalWindow != m_internalWindow) {
|
||||
// changed
|
||||
if (oldInternalWindow) {
|
||||
QEvent event(QEvent::Leave);
|
||||
QCoreApplication::sendEvent(oldInternalWindow.data(), &event);
|
||||
}
|
||||
if (m_internalWindow) {
|
||||
QEnterEvent event(pos - m_internalWindow->position(),
|
||||
pos - m_internalWindow->position(),
|
||||
pos);
|
||||
QCoreApplication::sendEvent(m_internalWindow.data(), &event);
|
||||
}
|
||||
emit internalWindowChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
6
input.h
6
input.h
|
@ -298,13 +298,18 @@ public:
|
|||
QPointer<Decoration::DecoratedClientImpl> decoration() const {
|
||||
return m_decoration;
|
||||
}
|
||||
QPointer<QWindow> internalWindow() const {
|
||||
return m_internalWindow;
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
void decorationChanged();
|
||||
void internalWindowChanged();
|
||||
|
||||
protected:
|
||||
explicit InputDeviceHandler(InputRedirection *parent);
|
||||
void updateDecoration(Toplevel *t, const QPointF &pos);
|
||||
void updateInternalWindow(const QPointF &pos);
|
||||
InputRedirection *m_input;
|
||||
/**
|
||||
* @brief The Toplevel which currently receives events
|
||||
|
@ -314,6 +319,7 @@ protected:
|
|||
* @brief The Decoration which currently receives events.
|
||||
**/
|
||||
QPointer<Decoration::DecoratedClientImpl> m_decoration;
|
||||
QPointer<QWindow> m_internalWindow;
|
||||
};
|
||||
|
||||
inline
|
||||
|
|
|
@ -138,6 +138,21 @@ void PointerInputRedirection::init()
|
|||
update();
|
||||
}
|
||||
);
|
||||
connect(this, &PointerInputRedirection::internalWindowChanged, this,
|
||||
[this] {
|
||||
disconnect(m_internalWindowConnection);
|
||||
m_internalWindowConnection = QMetaObject::Connection();
|
||||
if (m_internalWindow) {
|
||||
m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this,
|
||||
[this] (bool visible) {
|
||||
if (!visible) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// warp the cursor to center of screen
|
||||
warp(screens()->geometry().center());
|
||||
|
@ -233,7 +248,7 @@ void PointerInputRedirection::update()
|
|||
// TODO: handle pointer grab aka popups
|
||||
Toplevel *t = m_input->findToplevel(m_pos.toPoint());
|
||||
const auto oldDeco = m_decoration;
|
||||
updateInternalWindow();
|
||||
updateInternalWindow(m_pos);
|
||||
if (!m_internalWindow) {
|
||||
updateDecoration(t, m_pos);
|
||||
} else {
|
||||
|
@ -296,65 +311,6 @@ void PointerInputRedirection::update()
|
|||
}
|
||||
}
|
||||
|
||||
void PointerInputRedirection::updateInternalWindow()
|
||||
{
|
||||
const auto oldInternalWindow = m_internalWindow;
|
||||
bool found = false;
|
||||
// TODO: screen locked check without going through wayland server
|
||||
bool needsReset = waylandServer()->isScreenLocked();
|
||||
const auto &internalClients = waylandServer()->internalClients();
|
||||
const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible());
|
||||
if (!internalClients.isEmpty() && change) {
|
||||
auto it = internalClients.end();
|
||||
do {
|
||||
it--;
|
||||
if (QWindow *w = (*it)->internalWindow()) {
|
||||
if (!w->isVisible()) {
|
||||
continue;
|
||||
}
|
||||
if (w->geometry().contains(m_pos.toPoint())) {
|
||||
// check input mask
|
||||
const QRegion mask = w->mask().translated(w->geometry().topLeft());
|
||||
if (!mask.isEmpty() && !mask.contains(m_pos.toPoint())) {
|
||||
continue;
|
||||
}
|
||||
m_internalWindow = QPointer<QWindow>(w);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (it != internalClients.begin());
|
||||
if (!found) {
|
||||
needsReset = true;
|
||||
}
|
||||
}
|
||||
if (needsReset) {
|
||||
m_internalWindow.clear();
|
||||
}
|
||||
if (oldInternalWindow != m_internalWindow) {
|
||||
// changed
|
||||
if (oldInternalWindow) {
|
||||
disconnect(m_internalWindowConnection);
|
||||
m_internalWindowConnection = QMetaObject::Connection();
|
||||
QEvent event(QEvent::Leave);
|
||||
QCoreApplication::sendEvent(oldInternalWindow.data(), &event);
|
||||
}
|
||||
if (m_internalWindow) {
|
||||
m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this,
|
||||
[this] (bool visible) {
|
||||
if (!visible) {
|
||||
update();
|
||||
}
|
||||
});
|
||||
QEnterEvent event(m_pos - m_internalWindow->position(),
|
||||
m_pos - m_internalWindow->position(),
|
||||
m_pos);
|
||||
QCoreApplication::sendEvent(m_internalWindow.data(), &event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PointerInputRedirection::updatePosition(const QPointF &pos)
|
||||
{
|
||||
// verify that at least one screen contains the pointer position
|
||||
|
|
|
@ -62,9 +62,6 @@ public:
|
|||
Qt::MouseButtons buttons() const {
|
||||
return m_qtButtons;
|
||||
}
|
||||
QPointer<QWindow> internalWindow() const {
|
||||
return m_internalWindow;
|
||||
}
|
||||
|
||||
QImage cursorImage() const;
|
||||
QPoint cursorHotSpot() const;
|
||||
|
@ -88,14 +85,12 @@ public:
|
|||
private:
|
||||
void updatePosition(const QPointF &pos);
|
||||
void updateButton(uint32_t button, InputRedirection::PointerButtonState state);
|
||||
void updateInternalWindow();
|
||||
CursorImage *m_cursor;
|
||||
bool m_inited = false;
|
||||
bool m_supportsWarping;
|
||||
QPointF m_pos;
|
||||
QHash<uint32_t, InputRedirection::PointerButtonState> m_buttons;
|
||||
Qt::MouseButtons m_qtButtons;
|
||||
QPointer<QWindow> m_internalWindow;
|
||||
QMetaObject::Connection m_windowGeometryConnection;
|
||||
QMetaObject::Connection m_internalWindowConnection;
|
||||
};
|
||||
|
|
|
@ -32,6 +32,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include <KScreenLocker/KsldApp>
|
||||
// Qt
|
||||
#include <QHoverEvent>
|
||||
#include <QWindow>
|
||||
|
||||
namespace KWin
|
||||
{
|
||||
|
@ -66,14 +67,29 @@ void TouchInputRedirection::update(const QPointF &pos)
|
|||
if (!m_inited) {
|
||||
return;
|
||||
}
|
||||
if (m_windowUpdatedInCycle) {
|
||||
return;
|
||||
}
|
||||
m_windowUpdatedInCycle = true;
|
||||
// TODO: handle pointer grab aka popups
|
||||
Toplevel *t = m_input->findToplevel(pos.toPoint());
|
||||
auto oldWindow = m_window;
|
||||
updateDecoration(t, pos);
|
||||
if (m_decoration) {
|
||||
t = nullptr;
|
||||
updateInternalWindow(pos);
|
||||
if (!m_internalWindow) {
|
||||
updateDecoration(t, pos);
|
||||
} else {
|
||||
// TODO: send hover leave to decoration
|
||||
if (m_decoration) {
|
||||
m_decoration->client()->leaveEvent();
|
||||
}
|
||||
m_decoration.clear();
|
||||
}
|
||||
if (m_decoration || m_internalWindow) {
|
||||
t = nullptr;
|
||||
} else if (!m_decoration) {
|
||||
m_decorationId = -1;
|
||||
} else if (!m_internalWindow) {
|
||||
m_internalId = -1;
|
||||
}
|
||||
if (!oldWindow.isNull() && t == oldWindow.data()) {
|
||||
return;
|
||||
|
@ -133,12 +149,14 @@ void TouchInputRedirection::processDown(qint32 id, const QPointF &pos, quint32 t
|
|||
if (!m_inited) {
|
||||
return;
|
||||
}
|
||||
m_windowUpdatedInCycle = false;
|
||||
const auto &filters = m_input->filters();
|
||||
for (auto it = filters.begin(), end = filters.end(); it != end; it++) {
|
||||
if ((*it)->touchDown(id, pos, time)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_windowUpdatedInCycle = false;
|
||||
}
|
||||
|
||||
void TouchInputRedirection::processUp(qint32 id, quint32 time)
|
||||
|
@ -146,12 +164,14 @@ void TouchInputRedirection::processUp(qint32 id, quint32 time)
|
|||
if (!m_inited) {
|
||||
return;
|
||||
}
|
||||
m_windowUpdatedInCycle = false;
|
||||
const auto &filters = m_input->filters();
|
||||
for (auto it = filters.begin(), end = filters.end(); it != end; it++) {
|
||||
if ((*it)->touchUp(id, time)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_windowUpdatedInCycle = false;
|
||||
}
|
||||
|
||||
void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32 time)
|
||||
|
@ -159,12 +179,14 @@ void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32
|
|||
if (!m_inited) {
|
||||
return;
|
||||
}
|
||||
m_windowUpdatedInCycle = false;
|
||||
const auto &filters = m_input->filters();
|
||||
for (auto it = filters.begin(), end = filters.end(); it != end; it++) {
|
||||
if ((*it)->touchMotion(id, pos, time)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_windowUpdatedInCycle = false;
|
||||
}
|
||||
|
||||
void TouchInputRedirection::cancel()
|
||||
|
|
|
@ -63,15 +63,23 @@ public:
|
|||
qint32 decorationPressId() const {
|
||||
return m_decorationId;
|
||||
}
|
||||
void setInternalPressId(qint32 id) {
|
||||
m_internalId = id;
|
||||
}
|
||||
qint32 internalPressId() const {
|
||||
return m_internalId;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_inited = false;
|
||||
qint32 m_decorationId = -1;
|
||||
qint32 m_internalId = -1;
|
||||
/**
|
||||
* external/kwayland
|
||||
**/
|
||||
QHash<qint32, qint32> m_idMapper;
|
||||
QMetaObject::Connection m_windowGeometryConnection;
|
||||
bool m_windowUpdatedInCycle = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue