diff --git a/input.cpp b/input.cpp index 69deb308c..17312c595 100644 --- a/input.cpp +++ b/input.cpp @@ -1,1498 +1,1510 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "input.h" #include "keyboard_input.h" #include "pointer_input.h" #include "touch_input.h" #include "client.h" #include "effects.h" #include "globalshortcuts.h" #include "logind.h" #include "main.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox/tabbox.h" #endif #include "unmanaged.h" #include "screenedge.h" #include "screens.h" #include "workspace.h" #if HAVE_INPUT #include "libinput/connection.h" #include "libinput/device.h" #endif #include "platform.h" #include "shell_client.h" #include "wayland_server.h" #include #include #include #include #include //screenlocker #include // Qt #include #include namespace KWin { InputEventFilter::InputEventFilter() = default; InputEventFilter::~InputEventFilter() { if (input()) { input()->uninstallInputEventFilter(this); } } bool InputEventFilter::pointerEvent(QMouseEvent *event, quint32 nativeButton) { Q_UNUSED(event) Q_UNUSED(nativeButton) return false; } bool InputEventFilter::wheelEvent(QWheelEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::keyEvent(QKeyEvent *event) { Q_UNUSED(event) return false; } bool InputEventFilter::touchDown(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchMotion(quint32 id, const QPointF &point, quint32 time) { Q_UNUSED(id) Q_UNUSED(point) Q_UNUSED(time) return false; } bool InputEventFilter::touchUp(quint32 id, quint32 time) { Q_UNUSED(id) Q_UNUSED(time) return false; } #if HAVE_INPUT class VirtualTerminalFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { // really on press and not on release? X11 switches on press. if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { const xkb_keysym_t keysym = event->nativeVirtualKey(); if (keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { LogindIntegration::self()->switchVirtualTerminal(keysym - XKB_KEY_XF86Switch_VT_1 + 1); return true; } } return false; } }; #endif class TerminateServerFilter : public InputEventFilter { public: bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { if (event->nativeVirtualKey() == XKB_KEY_Terminate_Server) { qCWarning(KWIN_CORE) << "Request to terminate server"; QMetaObject::invokeMethod(qApp, "quit", Qt::QueuedConnection); return true; } } return false; } }; class LockScreenFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (event->type() == QEvent::MouseMove) { if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } if (pointerSurfaceAllowed()) { seat->setPointerPos(event->screenPos().toPoint()); } } else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { if (pointerSurfaceAllowed()) { event->type() == QEvent::MouseButtonPress ? seat->pointerButtonPressed(nativeButton) : seat->pointerButtonReleased(nativeButton); } } return true; } bool wheelEvent(QWheelEvent *event) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); if (pointerSurfaceAllowed()) { seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); } return true; } bool keyEvent(QKeyEvent * event) override { if (!waylandServer()->isScreenLocked()) { return false; } if (event->isAutoRepeat()) { // wayland client takes care of it return true; } // send event to KSldApp for global accel // if event is set to accepted it means a whitelisted shortcut was triggered // in that case we filter it out and don't process it further event->setAccepted(false); QCoreApplication::sendEvent(ScreenLocker::KSldApp::self(), event); if (event->isAccepted()) { return true; } // continue normal processing input()->keyboard()->update(); auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); if (!keyboardSurfaceAllowed()) { // don't pass event to seat return true; } switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } if (touchSurfaceAllowed()) { input()->touch()->insertId(id, seat->touchDown(pos)); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } } return true; } bool touchUp(quint32 id, quint32 time) override { if (!waylandServer()->isScreenLocked()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (touchSurfaceAllowed()) { const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } } return true; } private: bool surfaceAllowed(KWayland::Server::SurfaceInterface *(KWayland::Server::SeatInterface::*method)() const) const { if (KWayland::Server::SurfaceInterface *s = (waylandServer()->seat()->*method)()) { if (Toplevel *t = waylandServer()->findClient(s)) { return t->isLockScreen() || t->isInputMethod(); } return false; } return true; } bool pointerSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedPointerSurface); } bool keyboardSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedKeyboardSurface); } bool touchSurfaceAllowed() const { return surfaceAllowed(&KWayland::Server::SeatInterface::focusedTouchSurface); } }; class EffectsFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (!effects) { return false; } return static_cast(effects)->checkInputWindowEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!effects || !static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { return false; } static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(event); return true; } }; class MoveResizeFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } switch (event->type()) { case QEvent::MouseMove: c->updateMoveResize(event->screenPos().toPoint()); break; case QEvent::MouseButtonRelease: if (event->buttons() == Qt::NoButton) { c->endMoveResize(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { Q_UNUSED(event) // filter out while moving a window return workspace()->getMovingClient() != nullptr; } bool keyEvent(QKeyEvent *event) override { AbstractClient *c = workspace()->getMovingClient(); if (!c) { return false; } if (event->type() == QEvent::KeyPress) { c->keyPressEvent(event->key() | event->modifiers()); if (c->isMove() || c->isResize()) { // only update if mode didn't end c->updateMoveResize(input()->globalPointer()); } } return true; } }; class GlobalShortcutFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton); if (event->type() == QEvent::MouseButtonPress) { if (input()->shortcuts()->processPointerPressed(event->modifiers(), event->buttons())) { return true; } } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->modifiers() == Qt::NoModifier) { return false; } PointerAxisDirection direction = PointerAxisUp; if (event->angleDelta().x() < 0) { direction = PointerAxisRight; } else if (event->angleDelta().x() > 0) { direction = PointerAxisLeft; } else if (event->angleDelta().y() < 0) { direction = PointerAxisDown; } else if (event->angleDelta().y() > 0) { direction = PointerAxisUp; } return input()->shortcuts()->processAxis(event->modifiers(), direction); } bool keyEvent(QKeyEvent *event) override { if (event->type() == QEvent::KeyPress && !event->isAutoRepeat()) { return input()->shortcuts()->processKey(event->modifiers(), event->nativeVirtualKey()); } return false; } }; class InternalWindowEventFilter : public InputEventFilter { bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } QMouseEvent e(event->type(), event->pos() - internal->position(), event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool wheelEvent(QWheelEvent *event) override { auto internal = input()->pointer()->internalWindow(); if (!internal) { return false; } const QPointF localPos = event->globalPosF() - QPointF(internal->x(), internal->y()); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(internal.data(), &e); return e.isAccepted(); } bool keyEvent(QKeyEvent *event) override { const auto &internalClients = waylandServer()->internalClients(); if (internalClients.isEmpty()) { return false; } QWindow *found = nullptr; auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (!screens()->geometry().contains(w->geometry())) { continue; } found = w; break; } } while (it != internalClients.begin()); if (!found) { return false; } 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 { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF p = event->globalPos() - decoration->client()->pos(); switch (event->type()) { case QEvent::MouseMove: { if (event->buttons() == Qt::NoButton) { return false; } QHoverEvent e(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(p.toPoint(), event->globalPos()); return true; } case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { QMouseEvent e(event->type(), p, event->globalPos(), event->button(), event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted() && event->type() == QEvent::MouseButtonPress) { decoration->client()->processDecorationButtonPress(&e); } if (event->type() == QEvent::MouseButtonRelease) { decoration->client()->processDecorationButtonRelease(&e); } return true; } default: break; } return false; } bool wheelEvent(QWheelEvent *event) override { auto decoration = input()->pointer()->decoration(); if (!decoration) { return false; } const QPointF localPos = event->globalPosF() - decoration->client()->pos(); const Qt::Orientation orientation = (event->angleDelta().x() != 0) ? Qt::Horizontal : Qt::Vertical; const int delta = event->angleDelta().x() != 0 ? event->angleDelta().x() : event->angleDelta().y(); QWheelEvent e(localPos, event->globalPosF(), QPoint(), event->angleDelta(), delta, orientation, event->buttons(), event->modifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration.data(), &e); if (e.isAccepted()) { return true; } if ((orientation == Qt::Vertical) && decoration->client()->titlebarPositionUnderMouse()) { decoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1), event->globalPosF().toPoint()); } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } if (input()->touch()->decorationPressId() != -1) { // already on a decoration, ignore further touch points, but filter out return true; } seat->setTimestamp(time); input()->touch()->update(pos); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } input()->touch()->setDecorationPressId(id); m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QMouseEvent e(QEvent::MouseButtonPress, m_lastLocalTouchPos, pos, Qt::LeftButton, Qt::LeftButton, input()->keyboardModifiers()); e.setAccepted(false); QCoreApplication::sendEvent(decoration->decoration(), &e); if (!e.isAccepted()) { decoration->client()->processDecorationButtonPress(&e); } return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(time) auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != qint32(id)) { // ignore, but filter out return true; } m_lastGlobalTouchPos = pos; m_lastLocalTouchPos = pos - decoration->client()->pos(); QHoverEvent e(QEvent::HoverMove, m_lastLocalTouchPos, m_lastLocalTouchPos); QCoreApplication::instance()->sendEvent(decoration->decoration(), &e); decoration->client()->processDecorationMove(m_lastLocalTouchPos.toPoint(), pos.toPoint()); return true; } bool touchUp(quint32 id, quint32 time) override { Q_UNUSED(time); auto decoration = input()->touch()->decoration(); if (!decoration) { return false; } if (input()->touch()->decorationPressId() == -1) { return false; } if (input()->touch()->decorationPressId() != 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(decoration->decoration(), &e); decoration->client()->processDecorationButtonRelease(&e); m_lastGlobalTouchPos = QPointF(); m_lastLocalTouchPos = QPointF(); input()->touch()->setDecorationPressId(-1); return true; } private: QPointF m_lastGlobalTouchPos; QPointF m_lastLocalTouchPos; }; #ifdef KWIN_BUILD_TABBOX class TabBoxInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 button) override { Q_UNUSED(button) if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleMouseEvent(event); } bool keyEvent(QKeyEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } if (event->type() == QEvent::KeyPress) TabBox::TabBox::self()->keyPress(event->modifiers() | event->key()); return true; } bool wheelEvent(QWheelEvent *event) override { if (!TabBox::TabBox::self() || !TabBox::TabBox::self()->isGrabbed()) { return false; } return TabBox::TabBox::self()->handleWheelEvent(event); } }; #endif class ScreenEdgeInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) ScreenEdges::self()->isEntered(event); // always forward return false; } }; /** * This filter implements window actions. If the event should not be passed to the * current pointer window it will filter out the event **/ class WindowActionInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { Q_UNUSED(nativeButton) if (event->type() != QEvent::MouseButtonPress) { return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (event->modifiers() == options->commandAllModifier()) { wasAction = true; switch (event->button()) { case Qt::LeftButton: command = options->commandAll1(); break; case Qt::MiddleButton: command = options->commandAll2(); break; case Qt::RightButton: command = options->commandAll3(); break; default: // nothing break; } } else { command = c->getMouseCommand(event->button(), &wasAction); } if (wasAction) { return !c->performMouseCommand(command, event->globalPos()); } return false; } bool wheelEvent(QWheelEvent *event) override { if (event->angleDelta().y() == 0) { // only actions on vertical scroll return false; } AbstractClient *c = dynamic_cast(input()->pointer()->window().data()); if (!c) { return false; } bool wasAction = false; Options::MouseCommand command = Options::MouseNothing; if (event->modifiers() == options->commandAllModifier()) { wasAction = true; command = options->operationWindowMouseWheel(-1 * event->angleDelta().y()); } else { command = c->getWheelCommand(Qt::Vertical, &wasAction); } if (wasAction) { return !c->performMouseCommand(command, event->globalPos()); } return false; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { Q_UNUSED(id) Q_UNUSED(time) auto seat = waylandServer()->seat(); if (seat->isTouchSequence()) { return false; } input()->touch()->update(pos); AbstractClient *c = dynamic_cast(input()->touch()->window().data()); if (!c) { return false; } bool wasAction = false; const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction); if (wasAction) { return !c->performMouseCommand(command, pos.toPoint()); } return false; } }; /** * The remaining default input filter which forwards events to other windows **/ class ForwardInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: if (event->buttons() == Qt::NoButton) { // update pointer window only if no button is pressed input()->pointer()->update(); } seat->setPointerPos(event->globalPos()); break; case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); if (event->buttons() == Qt::NoButton) { input()->pointer()->update(); } break; default: break; } return true; } bool wheelEvent(QWheelEvent *event) override { auto seat = waylandServer()->seat(); seat->setTimestamp(event->timestamp()); const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal; seat->pointerAxis(orientation, orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()); return true; } bool keyEvent(QKeyEvent *event) override { if (!workspace()) { return false; } if (event->isAutoRepeat()) { // handled by Wayland client return false; } auto seat = waylandServer()->seat(); input()->keyboard()->update(); seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::KeyPress: seat->keyPressed(event->nativeScanCode()); break; case QEvent::KeyRelease: seat->keyReleased(event->nativeScanCode()); break; default: break; } return true; } bool touchDown(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); if (!seat->isTouchSequence()) { input()->touch()->update(pos); } input()->touch()->insertId(id, seat->touchDown(pos)); return true; } bool touchMotion(quint32 id, const QPointF &pos, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchMove(kwaylandId, pos); } return true; } bool touchUp(quint32 id, quint32 time) override { if (!workspace()) { return false; } auto seat = waylandServer()->seat(); seat->setTimestamp(time); const qint32 kwaylandId = input()->touch()->mappedId(id); if (kwaylandId != -1) { seat->touchUp(kwaylandId); input()->touch()->removeId(id); } return true; } }; class DragAndDropInputFilter : public InputEventFilter { public: bool pointerEvent(QMouseEvent *event, quint32 nativeButton) override { auto seat = waylandServer()->seat(); if (!seat->isDragPointer()) { return false; } seat->setTimestamp(event->timestamp()); switch (event->type()) { case QEvent::MouseMove: { if (Toplevel *t = input()->findToplevel(event->globalPos())) { // TODO: consider decorations if (t->surface() != seat->dragSurface()) { if (AbstractClient *c = qobject_cast(t)) { workspace()->raiseClient(c); } seat->setPointerPos(event->globalPos()); seat->setDragTarget(t->surface(), event->globalPos(), t->inputTransformation()); } } else { // no window at that place, if we have a surface we need to reset seat->setDragTarget(nullptr); } seat->setPointerPos(event->globalPos()); break; } case QEvent::MouseButtonPress: seat->pointerButtonPressed(nativeButton); break; case QEvent::MouseButtonRelease: seat->pointerButtonReleased(nativeButton); break; default: break; } // TODO: should we pass through effects? return true; } }; KWIN_SINGLETON_FACTORY(InputRedirection) InputRedirection::InputRedirection(QObject *parent) : QObject(parent) , m_keyboard(new KeyboardInputRedirection(this)) , m_pointer(new PointerInputRedirection(this)) , m_touch(new TouchInputRedirection(this)) , m_shortcuts(new GlobalShortcutsManager(this)) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); #if HAVE_INPUT if (Application::usesLibinput()) { if (LogindIntegration::self()->hasSessionControl()) { setupLibInput(); } else { if (LogindIntegration::self()->isConnected()) { LogindIntegration::self()->takeControl(); } else { connect(LogindIntegration::self(), &LogindIntegration::connectedChanged, LogindIntegration::self(), &LogindIntegration::takeControl); } connect(LogindIntegration::self(), &LogindIntegration::hasSessionControlChanged, this, [this] (bool sessionControl) { if (sessionControl) { setupLibInput(); } } ); } m_inputConfig = KSharedConfig::openConfig(QStringLiteral("kcminputrc")); } #endif connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace); reconfigure(); } InputRedirection::~InputRedirection() { s_self = NULL; qDeleteAll(m_filters); } void InputRedirection::installInputEventFilter(InputEventFilter *filter) { m_filters << filter; } void InputRedirection::prepandInputEventFilter(InputEventFilter *filter) { m_filters.prepend(filter); } void InputRedirection::uninstallInputEventFilter(InputEventFilter *filter) { m_filters.removeAll(filter); } void InputRedirection::init() { m_shortcuts->init(); } void InputRedirection::setupWorkspace() { if (waylandServer()) { using namespace KWayland::Server; FakeInputInterface *fakeInput = waylandServer()->display()->createFakeInput(this); fakeInput->create(); connect(fakeInput, &FakeInputInterface::deviceCreated, this, [this] (FakeInputDevice *device) { connect(device, &FakeInputDevice::authenticationRequested, this, [this, device] (const QString &application, const QString &reason) { Q_UNUSED(application) Q_UNUSED(reason) // TODO: make secure device->setAuthentication(true); } ); connect(device, &FakeInputDevice::pointerMotionRequested, this, [this] (const QSizeF &delta) { // TODO: Fix time m_pointer->processMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0); } ); connect(device, &FakeInputDevice::pointerButtonPressRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonPressed, 0); } ); connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this, [this] (quint32 button) { // TODO: Fix time m_pointer->processButton(button, InputRedirection::PointerButtonReleased, 0); } ); connect(device, &FakeInputDevice::pointerAxisRequested, this, [this] (Qt::Orientation orientation, qreal delta) { // TODO: Fix time InputRedirection::PointerAxis axis; switch (orientation) { case Qt::Horizontal: axis = InputRedirection::PointerAxisHorizontal; break; case Qt::Vertical: axis = InputRedirection::PointerAxisVertical; break; default: Q_UNREACHABLE(); break; } // TODO: Fix time m_pointer->processAxis(axis, delta, 0); } ); connect(device, &FakeInputDevice::touchDownRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processDown(id, pos, 0); } ); connect(device, &FakeInputDevice::touchMotionRequested, this, [this] (quint32 id, const QPointF &pos) { // TODO: Fix time m_touch->processMotion(id, pos, 0); } ); connect(device, &FakeInputDevice::touchUpRequested, this, [this] (quint32 id) { // TODO: Fix time m_touch->processUp(id, 0); } ); connect(device, &FakeInputDevice::touchCancelRequested, this, [this] () { m_touch->cancel(); } ); connect(device, &FakeInputDevice::touchFrameRequested, this, [this] () { m_touch->frame(); } ); } ); connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure); m_keyboard->init(); m_pointer->init(); m_touch->init(); } setupInputFilters(); } void InputRedirection::setupInputFilters() { #if HAVE_INPUT if (LogindIntegration::self()->hasSessionControl()) { installInputEventFilter(new VirtualTerminalFilter); } #endif if (waylandServer()) { installInputEventFilter(new TerminateServerFilter); installInputEventFilter(new DragAndDropInputFilter); installInputEventFilter(new LockScreenFilter); } installInputEventFilter(new ScreenEdgeInputFilter); installInputEventFilter(new EffectsFilter); installInputEventFilter(new MoveResizeFilter); #ifdef KWIN_BUILD_TABBOX installInputEventFilter(new TabBoxInputFilter); #endif installInputEventFilter(new GlobalShortcutFilter); installInputEventFilter(new InternalWindowEventFilter); installInputEventFilter(new DecorationEventFilter); if (waylandServer()) { installInputEventFilter(new WindowActionInputFilter); installInputEventFilter(new ForwardInputFilter); } } void InputRedirection::reconfigure() { #if HAVE_INPUT if (Application::usesLibinput()) { m_inputConfig->reparseConfiguration(); const auto config = m_inputConfig->group(QStringLiteral("keyboard")); const int delay = config.readEntry("RepeatDelay", 660); const int rate = config.readEntry("RepeatRate", 25); const bool enabled = config.readEntry("KeyboardRepeating", 0) == 0; waylandServer()->seat()->setKeyRepeatInfo(enabled ? rate : 0, delay); } #endif } static KWayland::Server::SeatInterface *findSeat() { auto server = waylandServer(); if (!server) { return nullptr; } return server->seat(); } void InputRedirection::setupLibInput() { #if HAVE_INPUT if (!Application::usesLibinput()) { return; } if (m_libInput) { return; } LibInput::Connection *conn = LibInput::Connection::create(this); m_libInput = conn; if (conn) { conn->setInputConfig(m_inputConfig); conn->setup(); connect(conn, &LibInput::Connection::eventsRead, this, [this] { m_libInput->processEvents(); }, Qt::QueuedConnection ); connect(conn, &LibInput::Connection::pointerButtonChanged, m_pointer, &PointerInputRedirection::processButton); connect(conn, &LibInput::Connection::pointerAxisChanged, m_pointer, &PointerInputRedirection::processAxis); connect(conn, &LibInput::Connection::keyChanged, m_keyboard, &KeyboardInputRedirection::processKey); connect(conn, &LibInput::Connection::pointerMotion, this, [this] (QPointF delta, uint32_t time, LibInput::Device *device) { m_pointer->processMotion(m_pointer->pos() + delta, time, device); } ); connect(conn, &LibInput::Connection::pointerMotionAbsolute, this, [this] (QPointF orig, QPointF screen, uint32_t time, LibInput::Device *device) { Q_UNUSED(orig) m_pointer->processMotion(screen, time, device); } ); connect(conn, &LibInput::Connection::touchDown, m_touch, &TouchInputRedirection::processDown); connect(conn, &LibInput::Connection::touchUp, m_touch, &TouchInputRedirection::processUp); connect(conn, &LibInput::Connection::touchMotion, m_touch, &TouchInputRedirection::processMotion); connect(conn, &LibInput::Connection::touchCanceled, m_touch, &TouchInputRedirection::cancel); connect(conn, &LibInput::Connection::touchFrame, m_touch, &TouchInputRedirection::frame); if (screens()) { setupLibInputWithScreens(); } else { connect(kwinApp(), &Application::screensCreated, this, &InputRedirection::setupLibInputWithScreens); } if (auto s = findSeat()) { - s->setHasKeyboard(conn->hasAlphaNumericKeyboard()); + // Workaround for QTBUG-54371: if there is no real keyboard Qt doesn't request virtual keyboard + s->setHasKeyboard(true); s->setHasPointer(conn->hasPointer()); s->setHasTouch(conn->hasTouch()); connect(conn, &LibInput::Connection::hasAlphaNumericKeyboardChanged, this, - [this, s] (bool set) { + [this] (bool set) { if (m_libInput->isSuspended()) { return; } - s->setHasKeyboard(set); + // TODO: this should update the seat, only workaround for QTBUG-54371 + emit hasAlphaNumericKeyboardChanged(set); } ); connect(conn, &LibInput::Connection::hasPointerChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasPointer(set); } ); connect(conn, &LibInput::Connection::hasTouchChanged, this, [this, s] (bool set) { if (m_libInput->isSuspended()) { return; } s->setHasTouch(set); } ); } connect(LogindIntegration::self(), &LogindIntegration::sessionActiveChanged, m_libInput, [this] (bool active) { if (!active) { m_libInput->deactivate(); } } ); } #endif } +bool InputRedirection::hasAlphaNumericKeyboard() +{ +#if HAVE_INPUT + if (m_libInput) { + return m_libInput->hasAlphaNumericKeyboard(); + } +#endif + return true; +} + void InputRedirection::setupLibInputWithScreens() { #if HAVE_INPUT if (!screens() || !m_libInput) { return; } m_libInput->setScreenSize(screens()->size()); connect(screens(), &Screens::sizeChanged, this, [this] { m_libInput->setScreenSize(screens()->size()); } ); #endif } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { m_pointer->processMotion(pos, time); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { m_pointer->processButton(button, state, time); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { m_pointer->processAxis(axis, delta, time); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { m_keyboard->processKey(key, state, time); } void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { m_keyboard->processModifiers(modsDepressed, modsLatched, modsLocked, group); } void InputRedirection::processKeymapChange(int fd, uint32_t size) { m_keyboard->processKeymapChange(fd, size); } void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time) { m_touch->processDown(id, pos, time); } void InputRedirection::processTouchUp(qint32 id, quint32 time) { m_touch->processUp(id, time); } void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time) { m_touch->processMotion(id, pos, time); } void InputRedirection::cancelTouch() { m_touch->cancel(); } void InputRedirection::touchFrame() { m_touch->frame(); } Qt::MouseButtons InputRedirection::qtButtonStates() const { return m_pointer->buttons(); } static bool acceptsInput(Toplevel *t, const QPoint &pos) { const QRegion input = t->inputShape(); if (input.isEmpty()) { return true; } return input.translated(t->pos()).contains(pos); } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { if (!Workspace::self()) { return nullptr; } const bool isScreenLocked = waylandServer() && waylandServer()->isScreenLocked(); // TODO: check whether the unmanaged wants input events at all if (!isScreenLocked) { // if an effect overrides the cursor we don't have a window to focus if (effects && static_cast(effects)->isMouseInterception()) { return nullptr; } const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(pos) && acceptsInput(u, pos)) { return u; } } } const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (stacking.isEmpty()) { return NULL; } auto it = stacking.end(); do { --it; Toplevel *t = (*it); if (t->isDeleted()) { // a deleted window doesn't get mouse events continue; } if (AbstractClient *c = dynamic_cast(t)) { if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab() || c->isHiddenInternal()) { continue; } } if (!t->readyForPainting()) { continue; } if (isScreenLocked) { if (!t->isLockScreen() && !t->isInputMethod()) { continue; } } if (t->geometry().contains(pos) && acceptsInput(t, pos)) { return t; } } while (it != stacking.begin()); return NULL; } Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const { return m_keyboard->modifiers(); } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { m_shortcuts->registerShortcut(action, shortcut); registerShortcutForGlobalAccelTimestamp(action); } void InputRedirection::registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action) { m_shortcuts->registerPointerShortcut(action, modifiers, pointerButtons); } void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) { m_shortcuts->registerAxisShortcut(action, modifiers, axis); } void InputRedirection::registerGlobalAccel(KGlobalAccelInterface *interface) { m_shortcuts->setKGlobalAccelInterface(interface); } void InputRedirection::registerShortcutForGlobalAccelTimestamp(QAction *action) { connect(action, &QAction::triggered, kwinApp(), [action] { QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp"); bool ok = false; const quint32 t = timestamp.toULongLong(&ok); if (ok) { kwinApp()->setX11Time(t); } }); } void InputRedirection::warpPointer(const QPointF &pos) { m_pointer->warp(pos); } bool InputRedirection::supportsPointerWarping() const { return m_pointer->supportsWarping(); } QPointF InputRedirection::globalPointer() const { return m_pointer->pos(); } InputDeviceHandler::InputDeviceHandler(InputRedirection *input) : QObject(input) , m_input(input) { } InputDeviceHandler::~InputDeviceHandler() = default; void InputDeviceHandler::updateDecoration(Toplevel *t, const QPointF &pos) { const auto oldDeco = m_decoration; bool needsReset = waylandServer()->isScreenLocked(); if (AbstractClient *c = dynamic_cast(t)) { // check whether it's on a Decoration if (c->decoratedClient()) { const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); if (!clientRect.contains(pos.toPoint())) { m_decoration = c->decoratedClient(); } else { needsReset = true; } } else { needsReset = true; } } else { needsReset = true; } if (needsReset) { m_decoration.clear(); } bool leftSend = false; auto oldWindow = qobject_cast(m_window.data()); if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { leftSend = true; oldWindow->leaveEvent(); } if (oldDeco && oldDeco != m_decoration) { if (oldDeco->client() != t && !leftSend) { leftSend = true; oldDeco->client()->leaveEvent(); } // send leave QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); } if (m_decoration) { if (m_decoration->client() != oldWindow) { m_decoration->client()->enterEvent(pos.toPoint()); workspace()->updateFocusMousePosition(pos.toPoint()); } const QPointF p = pos - t->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); m_decoration->client()->processDecorationMove(p.toPoint(), pos.toPoint()); } } 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(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 diff --git a/input.h b/input.h index 896aec248..2829344a1 100644 --- a/input.h +++ b/input.h @@ -1,344 +1,348 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_INPUT_H #define KWIN_INPUT_H #include #include #include #include #include #include #include class KGlobalAccelInterface; class QKeySequence; class QMouseEvent; class QKeyEvent; class QWheelEvent; namespace KWin { class GlobalShortcutsManager; class Toplevel; class InputEventFilter; class KeyboardInputRedirection; class PointerInputRedirection; class TouchInputRedirection; namespace Decoration { class DecoratedClientImpl; } namespace LibInput { class Connection; } /** * @brief This class is responsible for redirecting incoming input to the surface which currently * has input or send enter/leave events. * * In addition input is intercepted before passed to the surfaces to have KWin internal areas * getting input first (e.g. screen edges) and filter the input event out if we currently have * a full input grab. * */ class KWIN_EXPORT InputRedirection : public QObject { Q_OBJECT public: enum PointerButtonState { PointerButtonReleased, PointerButtonPressed }; enum PointerAxis { PointerAxisVertical, PointerAxisHorizontal }; enum KeyboardKeyState { KeyboardKeyReleased, KeyboardKeyPressed, KeyboardKeyAutoRepeat }; virtual ~InputRedirection(); void init(); /** * @return const QPointF& The current global pointer position */ QPointF globalPointer() const; Qt::MouseButtons qtButtonStates() const; Qt::KeyboardModifiers keyboardModifiers() const; void registerShortcut(const QKeySequence &shortcut, QAction *action); /** * @overload * * Like registerShortcut, but also connects QAction::triggered to the @p slot on @p receiver. * It's recommended to use this method as it ensures that the X11 timestamp is updated prior * to the @p slot being invoked. If not using this overload it's required to ensure that * registerShortcut is called before connecting to QAction's triggered signal. **/ template void registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, void (T::*slot)()); void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action); void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action); void registerGlobalAccel(KGlobalAccelInterface *interface); /** * @internal */ void processPointerMotion(const QPointF &pos, uint32_t time); /** * @internal */ void processPointerButton(uint32_t button, PointerButtonState state, uint32_t time); /** * @internal */ void processPointerAxis(PointerAxis axis, qreal delta, uint32_t time); /** * @internal */ void processKeyboardKey(uint32_t key, KeyboardKeyState state, uint32_t time); /** * @internal */ void processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); /** * @internal **/ void processKeymapChange(int fd, uint32_t size); void processTouchDown(qint32 id, const QPointF &pos, quint32 time); void processTouchUp(qint32 id, quint32 time); void processTouchMotion(qint32 id, const QPointF &pos, quint32 time); void cancelTouch(); void touchFrame(); bool supportsPointerWarping() const; void warpPointer(const QPointF &pos); /** * Adds the @p filter to the list of event filters and makes it the first * event filter in processing. * * Note: the event filter will get events before the lock screen can get them, thus * this is a security relevant method. **/ void prepandInputEventFilter(InputEventFilter *filter); void uninstallInputEventFilter(InputEventFilter *filter); Toplevel *findToplevel(const QPoint &pos); GlobalShortcutsManager *shortcuts() const { return m_shortcuts; } QVector filters() const { return m_filters; } KeyboardInputRedirection *keyboard() const { return m_keyboard; } PointerInputRedirection *pointer() const { return m_pointer; } TouchInputRedirection *touch() const { return m_touch; } + bool hasAlphaNumericKeyboard(); + Q_SIGNALS: /** * @brief Emitted when the global pointer position changed * * @param pos The new global pointer position. */ void globalPointerChanged(const QPointF &pos); /** * @brief Emitted when the state of a pointer button changed. * * @param button The button which changed * @param state The new button state */ void pointerButtonStateChanged(uint32_t button, InputRedirection::PointerButtonState state); /** * @brief Emitted when a pointer axis changed * * @param axis The axis on which the even occurred * @param delta The delta of the event. */ void pointerAxisChanged(InputRedirection::PointerAxis axis, qreal delta); /** * @brief Emitted when the modifiers changes. * * Only emitted for the mask which is provided by Qt::KeyboardModifiers, if other modifiers * change signal is not emitted * * @param newMods The new modifiers state * @param oldMods The previous modifiers state */ void keyboardModifiersChanged(Qt::KeyboardModifiers newMods, Qt::KeyboardModifiers oldMods); /** * @brief Emitted when the state of a key changed. * * @param keyCode The keycode of the key which changed * @param oldMods The new key state */ void keyStateChanged(quint32 keyCode, InputRedirection::KeyboardKeyState state); + void hasAlphaNumericKeyboardChanged(bool set); + private: void setupLibInput(); void setupLibInputWithScreens(); void registerShortcutForGlobalAccelTimestamp(QAction *action); void setupWorkspace(); void reconfigure(); void setupInputFilters(); void installInputEventFilter(InputEventFilter *filter); KeyboardInputRedirection *m_keyboard; PointerInputRedirection *m_pointer; TouchInputRedirection *m_touch; GlobalShortcutsManager *m_shortcuts; LibInput::Connection *m_libInput = nullptr; QVector m_filters; KSharedConfigPtr m_inputConfig; KWIN_SINGLETON(InputRedirection) friend InputRedirection *input(); friend class DecorationEventFilter; friend class InternalWindowEventFilter; friend class ForwardInputFilter; }; /** * Base class for filtering input events inside InputRedirection. * * The idea behind the InputEventFilter is to have task oriented * filters. E.g. there is one filter taking care of a locked screen, * one to take care of interacting with window decorations, etc. * * A concrete subclass can reimplement the virtual methods and decide * whether an event should be filtered out or not by returning either * @c true or @c false. E.g. the lock screen filter can easily ensure * that all events are filtered out. * * As soon as a filter returns @c true the processing is stopped. If * a filter returns @c false the next one is invoked. This means a filter * installed early gets to see more events than a filter installed later on. * * Deleting an instance of InputEventFilter automatically uninstalls it from * InputRedirection. **/ class KWIN_EXPORT InputEventFilter { public: InputEventFilter(); virtual ~InputEventFilter(); /** * Event filter for pointer events which can be described by a QMouseEvent. * * Please note that the button translation in QMouseEvent cannot cover all * possible buttons. Because of that also the @p nativeButton code is passed * through the filter. For internal areas it's fine to use @p event, but for * passing to client windows the @p nativeButton should be used. * * @param event The event information about the move or button press/release * @param nativeButton The native key code of the button, for move events 0 * @return @c true to stop further event processing, @c false to pass to next filter **/ virtual bool pointerEvent(QMouseEvent *event, quint32 nativeButton); /** * Event filter for pointer axis events. * * @param event The event information about the axis event * @return @c true to stop further event processing, @c false to pass to next filter **/ virtual bool wheelEvent(QWheelEvent *event); /** * Event filter for keyboard events. * * @param event The event information about the key event * @return @c tru to stop further event processing, @c false to pass to next filter. **/ virtual bool keyEvent(QKeyEvent *event); virtual bool touchDown(quint32 id, const QPointF &pos, quint32 time); virtual bool touchMotion(quint32 id, const QPointF &pos, quint32 time); virtual bool touchUp(quint32 id, quint32 time); }; class InputDeviceHandler : public QObject { Q_OBJECT public: virtual ~InputDeviceHandler(); QPointer window() const { return m_window; } QPointer decoration() const { return m_decoration; } QPointer 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 */ QPointer m_window; /** * @brief The Decoration which currently receives events. **/ QPointer m_decoration; QPointer m_internalWindow; }; inline InputRedirection *input() { return InputRedirection::s_self; } template inline void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action, T *receiver, void (T::*slot)()) { registerShortcut(shortcut, action); connect(action, &QAction::triggered, receiver, slot); } } // namespace KWin Q_DECLARE_METATYPE(KWin::InputRedirection::KeyboardKeyState) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerButtonState) Q_DECLARE_METATYPE(KWin::InputRedirection::PointerAxis) #endif // KWIN_INPUT_H diff --git a/virtualkeyboard.cpp b/virtualkeyboard.cpp index a18a36c40..3ae3e12f9 100644 --- a/virtualkeyboard.cpp +++ b/virtualkeyboard.cpp @@ -1,436 +1,437 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "virtualkeyboard.h" +#include "input.h" #include "utils.h" #include "screens.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KWayland::Server; namespace KWin { KWIN_SINGLETON_FACTORY(VirtualKeyboard) VirtualKeyboard::VirtualKeyboard(QObject *parent) : QObject(parent) { // this is actually too late. Other processes are started before init, // so might miss the availability of text input // but without Workspace we don't have the window listed at all connect(kwinApp(), &Application::workspaceCreated, this, &VirtualKeyboard::init); } VirtualKeyboard::~VirtualKeyboard() = default; void VirtualKeyboard::init() { // TODO: need a shared Qml engine m_inputWindow.reset(new QQuickView(nullptr)); m_inputWindow->setGeometry(screens()->geometry(screens()->current())); m_inputWindow->setResizeMode(QQuickView::SizeRootObjectToView); m_inputWindow->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/virtualkeyboard/main.qml")))); if (m_inputWindow->status() != QQuickView::Status::Ready) { // try enterprise m_inputWindow->setSource(QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME "/virtualkeyboard/main-enterprise.qml")))); if (m_inputWindow->status() != QQuickView::Status::Ready) { m_inputWindow.reset(); return; } } m_inputWindow->setProperty("__kwin_input_method", true); if (waylandServer()) { - m_enabled = !waylandServer()->seat()->hasKeyboard(); - connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::hasKeyboardChanged, this, - [this] { - setEnabled(!waylandServer()->seat()->hasKeyboard()); + m_enabled = !input()->hasAlphaNumericKeyboard(); + connect(input(), &InputRedirection::hasAlphaNumericKeyboardChanged, this, + [this] (bool set) { + setEnabled(!set); } ); } m_sni = new KStatusNotifierItem(QStringLiteral("kwin-virtual-keyboard"), this); m_sni->setCategory(KStatusNotifierItem::Hardware); m_sni->setStatus(KStatusNotifierItem::Passive); m_sni->setTitle(i18n("Virtual Keyboard")); updateSni(); connect(m_sni, &KStatusNotifierItem::activateRequested, this, [this] { setEnabled(!m_enabled); } ); if (waylandServer()) { // we can announce support for the text input interface auto t = waylandServer()->display()->createTextInputManager(TextInputInterfaceVersion::UnstableV0, waylandServer()->display()); t->create(); auto t2 = waylandServer()->display()->createTextInputManager(TextInputInterfaceVersion::UnstableV2, waylandServer()->display()); t2->create(); connect(waylandServer()->seat(), &SeatInterface::focusedTextInputChanged, this, [this] { disconnect(m_waylandShowConnection); disconnect(m_waylandHideConnection); disconnect(m_waylandHintsConnection); disconnect(m_waylandSurroundingTextConnection); disconnect(m_waylandResetConnection); disconnect(m_waylandEnabledConnection); qApp->inputMethod()->reset(); if (auto t = waylandServer()->seat()->focusedTextInput()) { m_waylandShowConnection = connect(t, &TextInputInterface::requestShowInputPanel, this, &VirtualKeyboard::show); m_waylandHideConnection = connect(t, &TextInputInterface::requestHideInputPanel, this, &VirtualKeyboard::hide); m_waylandSurroundingTextConnection = connect(t, &TextInputInterface::surroundingTextChanged, this, [] { qApp->inputMethod()->update(Qt::ImSurroundingText | Qt::ImCursorPosition | Qt::ImAnchorPosition); } ); m_waylandHintsConnection = connect(t, &TextInputInterface::contentTypeChanged, this, [] { qApp->inputMethod()->update(Qt::ImHints); } ); m_waylandResetConnection = connect(t, &TextInputInterface::requestReset, qApp->inputMethod(), &QInputMethod::reset); m_waylandEnabledConnection = connect(t, &TextInputInterface::enabledChanged, this, [] { qApp->inputMethod()->update(Qt::ImQueryAll); } ); // TODO: calculate overlap t->setInputPanelState(m_inputWindow->isVisible(), QRect(0, 0, 0, 0)); } else { m_waylandShowConnection = QMetaObject::Connection(); m_waylandHideConnection = QMetaObject::Connection(); m_waylandHintsConnection = QMetaObject::Connection(); m_waylandSurroundingTextConnection = QMetaObject::Connection(); m_waylandResetConnection = QMetaObject::Connection(); m_waylandEnabledConnection = QMetaObject::Connection(); } qApp->inputMethod()->update(Qt::ImQueryAll); } ); } m_inputWindow->installEventFilter(this); connect(Workspace::self(), &Workspace::destroyed, this, [this] { m_inputWindow.reset(); } ); m_inputWindow->setColor(Qt::transparent); m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); connect(m_inputWindow->rootObject(), &QQuickItem::childrenRectChanged, m_inputWindow.data(), [this] { if (!m_inputWindow) { return; } m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); } ); connect(qApp->inputMethod(), &QInputMethod::visibleChanged, m_inputWindow.data(), [this] { m_inputWindow->setVisible(qApp->inputMethod()->isVisible()); if (qApp->inputMethod()->isVisible()) { m_inputWindow->setMask(m_inputWindow->rootObject()->childrenRect().toRect()); } if (waylandServer()) { if (auto t = waylandServer()->seat()->focusedTextInput()) { // TODO: calculate overlap t->setInputPanelState(m_inputWindow->isVisible(), QRect(0, 0, 0, 0)); } } } ); } void VirtualKeyboard::setEnabled(bool enabled) { if (m_enabled == enabled) { return; } m_enabled = enabled; qApp->inputMethod()->update(Qt::ImQueryAll); updateSni(); // send OSD message QDBusMessage msg = QDBusMessage::createMethodCall( QStringLiteral("org.kde.plasmashell"), QStringLiteral("/org/kde/osdService"), QStringLiteral("org.kde.osdService"), QStringLiteral("virtualKeyboardEnabledChanged") ); msg.setArguments({enabled}); QDBusConnection::sessionBus().asyncCall(msg); } void VirtualKeyboard::updateSni() { if (!m_sni) { return; } if (m_enabled) { m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-on")); m_sni->setToolTipTitle(i18n("Virtual Keyboard is enabled.")); } else { m_sni->setIconByName(QStringLiteral("input-keyboard-virtual-off")); m_sni->setToolTipTitle(i18n("Virtual Keyboard is disabled.")); } } void VirtualKeyboard::show() { if (m_inputWindow.isNull() || !m_enabled) { return; } m_inputWindow->setGeometry(screens()->geometry(screens()->current())); qApp->inputMethod()->show(); } void VirtualKeyboard::hide() { if (m_inputWindow.isNull()) { return; } qApp->inputMethod()->hide(); } bool VirtualKeyboard::event(QEvent *e) { if (e->type() == QEvent::InputMethod) { QInputMethodEvent *event = static_cast(e); if (m_enabled && waylandServer()) { bool isPreedit = false; for (auto attribute : event->attributes()) { switch (attribute.type) { case QInputMethodEvent::TextFormat: case QInputMethodEvent::Cursor: case QInputMethodEvent::Language: case QInputMethodEvent::Ruby: isPreedit = true; break; default: break; } } TextInputInterface *ti = waylandServer()->seat()->focusedTextInput(); if (ti && ti->isEnabled()) { if (!isPreedit && event->preeditString().isEmpty() && !event->commitString().isEmpty()) { ti->commit(event->commitString().toUtf8()); } else { ti->preEdit(event->preeditString().toUtf8(), event->commitString().toUtf8()); } } } } if (e->type() == QEvent::InputMethodQuery) { auto event = static_cast(e); TextInputInterface *ti = nullptr; if (waylandServer() && m_enabled) { ti = waylandServer()->seat()->focusedTextInput(); } if (event->queries().testFlag(Qt::ImEnabled)) { event->setValue(Qt::ImEnabled, QVariant(ti != nullptr && ti->isEnabled())); } if (event->queries().testFlag(Qt::ImCursorRectangle)) { // not used by virtual keyboard } if (event->queries().testFlag(Qt::ImFont)) { // not used by virtual keyboard } if (event->queries().testFlag(Qt::ImCursorPosition)) { // the virtual keyboard doesn't send us the cursor position in the preedit // this would break text input, thus we ignore it // see https://bugreports.qt.io/browse/QTBUG-53517 #if 0 event->setValue(Qt::ImCursorPosition, QString::fromUtf8(ti->surroundingText().left(ti->surroundingTextCursorPosition())).size()); #else event->setValue(Qt::ImCursorPosition, 0); #endif } if (event->queries().testFlag(Qt::ImSurroundingText)) { // the virtual keyboard doesn't send us the cursor position in the preedit // this would break text input, thus we ignore it // see https://bugreports.qt.io/browse/QTBUG-53517 #if 0 event->setValue(Qt::ImSurroundingText, QString::fromUtf8(ti->surroundingText())); #else event->setValue(Qt::ImSurroundingText, QString()); #endif } if (event->queries().testFlag(Qt::ImCurrentSelection)) { // TODO: should be text between cursor and anchor, but might be dangerous } if (event->queries().testFlag(Qt::ImMaximumTextLength)) { // not used by virtual keyboard } if (event->queries().testFlag(Qt::ImAnchorPosition)) { // not used by virtual keyboard } if (event->queries().testFlag(Qt::ImHints)) { if (ti && ti->isEnabled()) { Qt::InputMethodHints hints; const auto contentHints = ti->contentHints(); if (!contentHints.testFlag(TextInputInterface::ContentHint::AutoCompletion)) { hints |= Qt::ImhNoPredictiveText; } if (contentHints.testFlag(TextInputInterface::ContentHint::AutoCorrection)) { // no mapping in Qt } if (!contentHints.testFlag(TextInputInterface::ContentHint::AutoCapitalization)) { hints |= Qt::ImhNoAutoUppercase; } if (contentHints.testFlag(TextInputInterface::ContentHint::LowerCase)) { hints |= Qt::ImhPreferLowercase; } if (contentHints.testFlag(TextInputInterface::ContentHint::UpperCase)) { hints |= Qt::ImhPreferUppercase; } if (contentHints.testFlag(TextInputInterface::ContentHint::TitleCase)) { // no mapping in Qt } if (contentHints.testFlag(TextInputInterface::ContentHint::HiddenText)) { hints |= Qt::ImhHiddenText; } if (contentHints.testFlag(TextInputInterface::ContentHint::SensitiveData)) { hints |= Qt::ImhSensitiveData; } if (contentHints.testFlag(TextInputInterface::ContentHint::Latin)) { hints |= Qt::ImhPreferLatin; } if (contentHints.testFlag(TextInputInterface::ContentHint::MultiLine)) { hints |= Qt::ImhMultiLine; } switch (ti->contentPurpose()) { case TextInputInterface::ContentPurpose::Digits: hints |= Qt::ImhDigitsOnly; break; case TextInputInterface::ContentPurpose::Number: hints |= Qt::ImhFormattedNumbersOnly; break; case TextInputInterface::ContentPurpose::Phone: hints |= Qt::ImhDialableCharactersOnly; break; case TextInputInterface::ContentPurpose::Url: hints |= Qt::ImhUrlCharactersOnly; case TextInputInterface::ContentPurpose::Email: hints |= Qt::ImhEmailCharactersOnly; break; case TextInputInterface::ContentPurpose::Date: hints |= Qt::ImhDate; break; case TextInputInterface::ContentPurpose::Time: hints |= Qt::ImhTime; break; case TextInputInterface::ContentPurpose::DateTime: hints |= Qt::ImhDate; hints |= Qt::ImhTime; break; case TextInputInterface::ContentPurpose::Name: // no mapping in Qt case TextInputInterface::ContentPurpose::Password: // no mapping in Qt case TextInputInterface::ContentPurpose::Terminal: // no mapping in Qt case TextInputInterface::ContentPurpose::Normal: // that's the default case TextInputInterface::ContentPurpose::Alpha: // no mapping in Qt break; } event->setValue(Qt::ImHints, QVariant(int(hints))); } else { event->setValue(Qt::ImHints, Qt::ImhNone); } } if (event->queries().testFlag(Qt::ImPreferredLanguage)) { // not used by virtual keyboard } if (event->queries().testFlag(Qt::ImPlatformData)) { // not used by virtual keyboard } if (event->queries().testFlag(Qt::ImAbsolutePosition)) { // not used by virtual keyboard } if (event->queries().testFlag(Qt::ImTextBeforeCursor)) { // not used by virtual keyboard } if (event->queries().testFlag(Qt::ImTextAfterCursor)) { // not used by virtual keyboard } event->accept(); return true; } return QObject::event(e); } bool VirtualKeyboard::eventFilter(QObject *o, QEvent *e) { if (o != m_inputWindow.data() || !m_inputWindow->isVisible()) { return false; } if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) { QKeyEvent *event = static_cast(e); if (event->nativeScanCode() == 0) { // this is a key composed by the virtual keyboard - we need to send it to the client // TODO: proper xkb support in KWindowSystem needed int sym = 0; KKeyServer::keyQtToSymX(event->key(), &sym); if (sym != 0) { if (waylandServer()) { auto t = waylandServer()->seat()->focusedTextInput(); if (t && t->isEnabled()) { if (e->type() == QEvent::KeyPress) { t->keysymPressed(sym); } else if (e->type() == QEvent::KeyRelease) { t->keysymReleased(sym); } } } } return true; } } return false; } QWindow *VirtualKeyboard::inputPanel() const { return m_inputWindow.data(); } }