File indexing completed on 2024-02-25 17:23:55

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2013, 2016 Martin Gräßlin <mgraesslin@kde.org>
0006     SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 #include "touch_input.h"
0011 
0012 #include <config-kwin.h>
0013 
0014 #include "decorations/decoratedclient.h"
0015 #include "input_event_spy.h"
0016 #include "pointer_input.h"
0017 #include "wayland/seat_interface.h"
0018 #include "wayland_server.h"
0019 #include "window.h"
0020 #include "workspace.h"
0021 // KDecoration
0022 #include <KDecoration2/Decoration>
0023 // screenlocker
0024 #if KWIN_BUILD_SCREENLOCKER
0025 #include <KScreenLocker/KsldApp>
0026 #endif
0027 // Qt
0028 #include <QHoverEvent>
0029 #include <QWindow>
0030 
0031 namespace KWin
0032 {
0033 
0034 TouchInputRedirection::TouchInputRedirection(InputRedirection *parent)
0035     : InputDeviceHandler(parent)
0036 {
0037 }
0038 
0039 TouchInputRedirection::~TouchInputRedirection() = default;
0040 
0041 void TouchInputRedirection::init()
0042 {
0043     Q_ASSERT(!inited());
0044     waylandServer()->seat()->setHasTouch(input()->hasTouch());
0045     connect(input(), &InputRedirection::hasTouchChanged,
0046             waylandServer()->seat(), &KWaylandServer::SeatInterface::setHasTouch);
0047 
0048     setInited(true);
0049     InputDeviceHandler::init();
0050 
0051 #if KWIN_BUILD_SCREENLOCKER
0052     if (waylandServer()->hasScreenLockerIntegration()) {
0053         connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, [this]() {
0054             cancel();
0055             // position doesn't matter
0056             update();
0057         });
0058     }
0059 #endif
0060     connect(workspace(), &QObject::destroyed, this, [this] {
0061         setInited(false);
0062     });
0063     connect(waylandServer(), &QObject::destroyed, this, [this] {
0064         setInited(false);
0065     });
0066 }
0067 
0068 bool TouchInputRedirection::focusUpdatesBlocked()
0069 {
0070     if (m_windowUpdatedInCycle) {
0071         return true;
0072     }
0073     m_windowUpdatedInCycle = true;
0074     if (waylandServer()->seat()->isDragTouch()) {
0075         return true;
0076     }
0077     if (m_activeTouchPoints.count() > 1) {
0078         // first touch defines focus
0079         return true;
0080     }
0081     return false;
0082 }
0083 
0084 bool TouchInputRedirection::positionValid() const
0085 {
0086     // we can only determine a position with at least one touch point
0087     return !m_activeTouchPoints.isEmpty();
0088 }
0089 
0090 void TouchInputRedirection::focusUpdate(Window *focusOld, Window *focusNow)
0091 {
0092     // TODO: handle pointer grab aka popups
0093 
0094     if (focusOld && focusOld->isClient()) {
0095         focusOld->pointerLeaveEvent();
0096     }
0097     disconnect(m_focusGeometryConnection);
0098     m_focusGeometryConnection = QMetaObject::Connection();
0099 
0100     if (focusNow && focusNow->isClient()) {
0101         focusNow->pointerEnterEvent(m_lastPosition);
0102     }
0103 
0104     auto seat = waylandServer()->seat();
0105     if (!focusNow || !focusNow->surface()) {
0106         seat->setFocusedTouchSurface(nullptr);
0107         return;
0108     }
0109 
0110     // TODO: invalidate pointer focus?
0111 
0112     // FIXME: add input transformation API to KWaylandServer::SeatInterface for touch input
0113     seat->setFocusedTouchSurface(focusNow->surface(), -1 * focusNow->inputTransformation().map(focusNow->pos()) + focusNow->pos());
0114     m_focusGeometryConnection = connect(focusNow, &Window::frameGeometryChanged, this, [this]() {
0115         if (!focus()) {
0116             return;
0117         }
0118         auto seat = waylandServer()->seat();
0119         if (focus()->surface() != seat->focusedTouchSurface()) {
0120             return;
0121         }
0122         seat->setFocusedTouchSurfacePosition(-1 * focus()->inputTransformation().map(focus()->pos()) + focus()->pos());
0123     });
0124 }
0125 
0126 void TouchInputRedirection::cleanupDecoration(Decoration::DecoratedClientImpl *old, Decoration::DecoratedClientImpl *now)
0127 {
0128     // nothing to do
0129 }
0130 
0131 void TouchInputRedirection::processDown(qint32 id, const QPointF &pos, std::chrono::microseconds time, InputDevice *device)
0132 {
0133     if (!inited()) {
0134         return;
0135     }
0136     m_lastPosition = pos;
0137     m_windowUpdatedInCycle = false;
0138     m_activeTouchPoints.insert(id);
0139     if (m_activeTouchPoints.count() == 1) {
0140         update();
0141         workspace()->setActiveCursorOutput(pos);
0142     }
0143     input()->setLastInputHandler(this);
0144     input()->processSpies(std::bind(&InputEventSpy::touchDown, std::placeholders::_1, id, pos, time));
0145     input()->processFilters(std::bind(&InputEventFilter::touchDown, std::placeholders::_1, id, pos, time));
0146     m_windowUpdatedInCycle = false;
0147 }
0148 
0149 void TouchInputRedirection::processUp(qint32 id, std::chrono::microseconds time, InputDevice *device)
0150 {
0151     if (!inited()) {
0152         return;
0153     }
0154     if (!m_activeTouchPoints.remove(id)) {
0155         return;
0156     }
0157     input()->setLastInputHandler(this);
0158     m_windowUpdatedInCycle = false;
0159     input()->processSpies(std::bind(&InputEventSpy::touchUp, std::placeholders::_1, id, time));
0160     input()->processFilters(std::bind(&InputEventFilter::touchUp, std::placeholders::_1, id, time));
0161     m_windowUpdatedInCycle = false;
0162     if (m_activeTouchPoints.count() == 0) {
0163         update();
0164     }
0165 }
0166 
0167 void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, std::chrono::microseconds time, InputDevice *device)
0168 {
0169     if (!inited()) {
0170         return;
0171     }
0172     if (!m_activeTouchPoints.contains(id)) {
0173         return;
0174     }
0175     input()->setLastInputHandler(this);
0176     m_lastPosition = pos;
0177     m_windowUpdatedInCycle = false;
0178     input()->processSpies(std::bind(&InputEventSpy::touchMotion, std::placeholders::_1, id, pos, time));
0179     input()->processFilters(std::bind(&InputEventFilter::touchMotion, std::placeholders::_1, id, pos, time));
0180     m_windowUpdatedInCycle = false;
0181 }
0182 
0183 void TouchInputRedirection::cancel()
0184 {
0185     if (!inited()) {
0186         return;
0187     }
0188     // If the touch sequence is artificially cancelled by the compositor, touch motion and touch
0189     // up events will be silently ignored and won't be passed down through the event filter chain.
0190     // If the touch sequence is cancelled because we received a TOUCH_CANCEL event from libinput,
0191     // the compositor will not receive any TOUCH_MOTION or TOUCH_UP events for that slot.
0192     if (!m_activeTouchPoints.isEmpty()) {
0193         m_activeTouchPoints.clear();
0194         input()->processFilters(std::bind(&InputEventFilter::touchCancel, std::placeholders::_1));
0195     }
0196 }
0197 
0198 void TouchInputRedirection::frame()
0199 {
0200     if (!inited() || !waylandServer()->seat()->hasTouch()) {
0201         return;
0202     }
0203     input()->processFilters(std::bind(&InputEventFilter::touchFrame, std::placeholders::_1));
0204 }
0205 
0206 }