a47b6f9435
If the screen is locked and no lock screen is shown yet we unset the focused keyboard surface on key event. Similar we restore when screen is unlocked. This should hopefully fix the broken lockscreen unit test which hits the special condition as the greeter doesn't show up on build.kde.org.
1397 lines
47 KiB
C++
1397 lines
47 KiB
C++
/********************************************************************
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*********************************************************************/
|
|
#include "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 "screens.h"
|
|
#include "workspace.h"
|
|
#if HAVE_INPUT
|
|
#include "libinput/connection.h"
|
|
#include "virtual_terminal.h"
|
|
#endif
|
|
#include "abstract_backend.h"
|
|
#include "shell_client.h"
|
|
#include "wayland_server.h"
|
|
#include <KWayland/Server/display.h>
|
|
#include <KWayland/Server/fakeinput_interface.h>
|
|
#include <KWayland/Server/seat_interface.h>
|
|
#include <decorations/decoratedclient.h>
|
|
#include <KDecoration2/Decoration>
|
|
// Qt
|
|
#include <QDBusMessage>
|
|
#include <QDBusPendingCall>
|
|
#include <QKeyEvent>
|
|
#include <QMouseEvent>
|
|
#include <QTemporaryFile>
|
|
// KDE
|
|
#include <kkeyserver.h>
|
|
#include <xkbcommon/xkbcommon.h>
|
|
#include <xkbcommon/xkbcommon-keysyms.h>
|
|
// system
|
|
#include <linux/input.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
Xkb::Xkb(InputRedirection *input)
|
|
: m_input(input)
|
|
, m_context(xkb_context_new(static_cast<xkb_context_flags>(0)))
|
|
, m_keymap(NULL)
|
|
, m_state(NULL)
|
|
, m_shiftModifier(0)
|
|
, m_controlModifier(0)
|
|
, m_altModifier(0)
|
|
, m_metaModifier(0)
|
|
, m_modifiers(Qt::NoModifier)
|
|
{
|
|
if (!m_context) {
|
|
qCDebug(KWIN_CORE) << "Could not create xkb context";
|
|
} else {
|
|
// load default keymap
|
|
xkb_keymap *keymap = xkb_keymap_new_from_names(m_context, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
if (keymap) {
|
|
updateKeymap(keymap);
|
|
} else {
|
|
qCDebug(KWIN_CORE) << "Could not create default xkb keymap";
|
|
}
|
|
}
|
|
}
|
|
|
|
Xkb::~Xkb()
|
|
{
|
|
xkb_state_unref(m_state);
|
|
xkb_keymap_unref(m_keymap);
|
|
xkb_context_unref(m_context);
|
|
}
|
|
|
|
void Xkb::installKeymap(int fd, uint32_t size)
|
|
{
|
|
if (!m_context) {
|
|
return;
|
|
}
|
|
char *map = reinterpret_cast<char*>(mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0));
|
|
if (map == MAP_FAILED) {
|
|
return;
|
|
}
|
|
xkb_keymap *keymap = xkb_keymap_new_from_string(m_context, map, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_MAP_COMPILE_PLACEHOLDER);
|
|
munmap(map, size);
|
|
if (!keymap) {
|
|
qCDebug(KWIN_CORE) << "Could not map keymap from file";
|
|
return;
|
|
}
|
|
updateKeymap(keymap);
|
|
}
|
|
|
|
void Xkb::updateKeymap(xkb_keymap *keymap)
|
|
{
|
|
Q_ASSERT(keymap);
|
|
xkb_state *state = xkb_state_new(keymap);
|
|
if (!state) {
|
|
qCDebug(KWIN_CORE) << "Could not create XKB state";
|
|
xkb_keymap_unref(keymap);
|
|
return;
|
|
}
|
|
// now release the old ones
|
|
xkb_state_unref(m_state);
|
|
xkb_keymap_unref(m_keymap);
|
|
|
|
m_keymap = keymap;
|
|
m_state = state;
|
|
|
|
m_shiftModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_SHIFT);
|
|
m_controlModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_CTRL);
|
|
m_altModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_ALT);
|
|
m_metaModifier = xkb_keymap_mod_get_index(m_keymap, XKB_MOD_NAME_LOGO);
|
|
|
|
createKeymapFile();
|
|
}
|
|
|
|
void Xkb::createKeymapFile()
|
|
{
|
|
if (!waylandServer()) {
|
|
return;
|
|
}
|
|
// TODO: uninstall keymap on server?
|
|
if (!m_keymap) {
|
|
return;
|
|
}
|
|
|
|
ScopedCPointer<char> keymapString(xkb_keymap_get_as_string(m_keymap, XKB_KEYMAP_FORMAT_TEXT_V1));
|
|
if (keymapString.isNull()) {
|
|
return;
|
|
}
|
|
const uint size = qstrlen(keymapString.data()) + 1;
|
|
|
|
QTemporaryFile *tmp = new QTemporaryFile(m_input);
|
|
if (!tmp->open()) {
|
|
delete tmp;
|
|
return;
|
|
}
|
|
unlink(tmp->fileName().toUtf8().constData());
|
|
if (!tmp->resize(size)) {
|
|
delete tmp;
|
|
return;
|
|
}
|
|
uchar *address = tmp->map(0, size);
|
|
if (!address) {
|
|
return;
|
|
}
|
|
if (qstrncpy(reinterpret_cast<char*>(address), keymapString.data(), size) == nullptr) {
|
|
delete tmp;
|
|
return;
|
|
}
|
|
waylandServer()->seat()->setKeymap(tmp->handle(), size);
|
|
}
|
|
|
|
void Xkb::updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
|
{
|
|
if (!m_keymap || !m_state) {
|
|
return;
|
|
}
|
|
xkb_state_update_mask(m_state, modsDepressed, modsLatched, modsLocked, 0, 0, group);
|
|
updateModifiers();
|
|
}
|
|
|
|
void Xkb::updateKey(uint32_t key, InputRedirection::KeyboardKeyState state)
|
|
{
|
|
if (!m_keymap || !m_state) {
|
|
return;
|
|
}
|
|
xkb_state_update_key(m_state, key + 8, static_cast<xkb_key_direction>(state));
|
|
updateModifiers();
|
|
if (state == InputRedirection::KeyboardKeyPressed) {
|
|
m_modOnlyShortcut.pressCount++;
|
|
if (m_modOnlyShortcut.pressCount == 1) {
|
|
m_modOnlyShortcut.modifier = Qt::KeyboardModifier(int(m_modifiers));
|
|
} else {
|
|
m_modOnlyShortcut.modifier = Qt::NoModifier;
|
|
}
|
|
} else {
|
|
m_modOnlyShortcut.pressCount--;
|
|
// TODO: ignore on lock screen
|
|
if (m_modOnlyShortcut.pressCount == 0) {
|
|
if (m_modOnlyShortcut.modifier != Qt::NoModifier) {
|
|
const auto list = options->modifierOnlyDBusShortcut(m_modOnlyShortcut.modifier);
|
|
if (list.size() >= 4) {
|
|
auto call = QDBusMessage::createMethodCall(list.at(0), list.at(1), list.at(2), list.at(3));
|
|
QVariantList args;
|
|
for (int i = 4; i < list.size(); ++i) {
|
|
args << list.at(i);
|
|
}
|
|
call.setArguments(args);
|
|
QDBusConnection::sessionBus().asyncCall(call);
|
|
}
|
|
}
|
|
}
|
|
m_modOnlyShortcut.modifier = Qt::NoModifier;
|
|
}
|
|
}
|
|
|
|
void Xkb::updateModifiers()
|
|
{
|
|
Qt::KeyboardModifiers mods = Qt::NoModifier;
|
|
if (xkb_state_mod_index_is_active(m_state, m_shiftModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
|
|
mods |= Qt::ShiftModifier;
|
|
}
|
|
if (xkb_state_mod_index_is_active(m_state, m_altModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
|
|
mods |= Qt::AltModifier;
|
|
}
|
|
if (xkb_state_mod_index_is_active(m_state, m_controlModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
|
|
mods |= Qt::ControlModifier;
|
|
}
|
|
if (xkb_state_mod_index_is_active(m_state, m_metaModifier, XKB_STATE_MODS_EFFECTIVE) == 1) {
|
|
mods |= Qt::MetaModifier;
|
|
}
|
|
m_modifiers = mods;
|
|
}
|
|
|
|
xkb_keysym_t Xkb::toKeysym(uint32_t key)
|
|
{
|
|
if (!m_state) {
|
|
return XKB_KEY_NoSymbol;
|
|
}
|
|
return xkb_state_key_get_one_sym(m_state, key + 8);
|
|
}
|
|
|
|
QString Xkb::toString(xkb_keysym_t keysym)
|
|
{
|
|
if (!m_state || keysym == XKB_KEY_NoSymbol) {
|
|
return QString();
|
|
}
|
|
QByteArray byteArray(7, 0);
|
|
int ok = xkb_keysym_to_utf8(keysym, byteArray.data(), byteArray.size());
|
|
if (ok == -1 || ok == 0) {
|
|
return QString();
|
|
}
|
|
return QString::fromUtf8(byteArray.constData());
|
|
}
|
|
|
|
Qt::Key Xkb::toQtKey(xkb_keysym_t keysym)
|
|
{
|
|
int key = Qt::Key_unknown;
|
|
KKeyServer::symXToKeyQt(keysym, &key);
|
|
return static_cast<Qt::Key>(key);
|
|
}
|
|
|
|
quint32 Xkb::getMods(quint32 components)
|
|
{
|
|
if (!m_state) {
|
|
return 0;
|
|
}
|
|
return xkb_state_serialize_mods(m_state, xkb_state_component(components));
|
|
}
|
|
|
|
quint32 Xkb::getGroup()
|
|
{
|
|
if (!m_state) {
|
|
return 0;
|
|
}
|
|
return xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE);
|
|
}
|
|
|
|
KWIN_SINGLETON_FACTORY(InputRedirection)
|
|
|
|
InputRedirection::InputRedirection(QObject *parent)
|
|
: QObject(parent)
|
|
, m_xkb(new Xkb(this))
|
|
, m_pointerWindow()
|
|
, m_shortcuts(new GlobalShortcutsManager(this))
|
|
{
|
|
qRegisterMetaType<KWin::InputRedirection::KeyboardKeyState>();
|
|
qRegisterMetaType<KWin::InputRedirection::PointerButtonState>();
|
|
qRegisterMetaType<KWin::InputRedirection::PointerAxis>();
|
|
#if HAVE_INPUT
|
|
if (Application::usesLibinput()) {
|
|
if (VirtualTerminal::self()) {
|
|
setupLibInput();
|
|
} else {
|
|
connect(kwinApp(), &Application::virtualTerminalCreated, this, &InputRedirection::setupLibInput);
|
|
}
|
|
}
|
|
#endif
|
|
connect(kwinApp(), &Application::workspaceCreated, this, &InputRedirection::setupWorkspace);
|
|
reconfigure();
|
|
}
|
|
|
|
InputRedirection::~InputRedirection()
|
|
{
|
|
s_self = NULL;
|
|
}
|
|
|
|
void InputRedirection::init()
|
|
{
|
|
m_shortcuts->init();
|
|
}
|
|
|
|
void InputRedirection::setupWorkspace()
|
|
{
|
|
if (waylandServer()) {
|
|
connect(workspace(), &Workspace::clientActivated, this, &InputRedirection::updateKeyboardWindow);
|
|
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) {
|
|
// TODO: make secure
|
|
device->setAuthentication(true);
|
|
}
|
|
);
|
|
connect(device, &FakeInputDevice::pointerMotionRequested, this,
|
|
[this] (const QSizeF &delta) {
|
|
// TODO: Fix time
|
|
processPointerMotion(globalPointer() + QPointF(delta.width(), delta.height()), 0);
|
|
}
|
|
);
|
|
connect(device, &FakeInputDevice::pointerButtonPressRequested, this,
|
|
[this] (quint32 button) {
|
|
// TODO: Fix time
|
|
processPointerButton(button, InputRedirection::PointerButtonPressed, 0);
|
|
}
|
|
);
|
|
connect(device, &FakeInputDevice::pointerButtonReleaseRequested, this,
|
|
[this] (quint32 button) {
|
|
// TODO: Fix time
|
|
processPointerButton(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
|
|
processPointerAxis(axis, delta, 0);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
connect(this, &InputRedirection::keyboardModifiersChanged, waylandServer(),
|
|
[this] {
|
|
if (!waylandServer()->seat()) {
|
|
return;
|
|
}
|
|
waylandServer()->seat()->updateKeyboardModifiers(m_xkb->getMods(XKB_STATE_MODS_DEPRESSED),
|
|
m_xkb->getMods(XKB_STATE_MODS_LATCHED),
|
|
m_xkb->getMods(XKB_STATE_MODS_LOCKED),
|
|
m_xkb->getGroup());
|
|
}
|
|
);
|
|
connect(workspace(), &Workspace::configChanged, this, &InputRedirection::reconfigure);
|
|
}
|
|
}
|
|
|
|
void InputRedirection::reconfigure()
|
|
{
|
|
#if HAVE_INPUT
|
|
if (Application::usesLibinput()) {
|
|
const auto config = KSharedConfig::openConfig(QStringLiteral("kcminputrc"))->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->setup();
|
|
m_pointerWarping = true;
|
|
connect(conn, &LibInput::Connection::eventsRead, this,
|
|
[this] {
|
|
m_libInput->processEvents();
|
|
}, Qt::QueuedConnection
|
|
);
|
|
connect(conn, &LibInput::Connection::pointerButtonChanged, this, &InputRedirection::processPointerButton);
|
|
connect(conn, &LibInput::Connection::pointerAxisChanged, this, &InputRedirection::processPointerAxis);
|
|
connect(conn, &LibInput::Connection::keyChanged, this, &InputRedirection::processKeyboardKey);
|
|
connect(conn, &LibInput::Connection::pointerMotion, this,
|
|
[this] (QPointF delta, uint32_t time) {
|
|
processPointerMotion(m_globalPointer + delta, time);
|
|
}
|
|
);
|
|
connect(conn, &LibInput::Connection::pointerMotionAbsolute, this,
|
|
[this] (QPointF orig, QPointF screen, uint32_t time) {
|
|
Q_UNUSED(orig)
|
|
processPointerMotion(screen, time);
|
|
}
|
|
);
|
|
connect(conn, &LibInput::Connection::touchDown, this, &InputRedirection::processTouchDown);
|
|
connect(conn, &LibInput::Connection::touchUp, this, &InputRedirection::processTouchUp);
|
|
connect(conn, &LibInput::Connection::touchMotion, this, &InputRedirection::processTouchMotion);
|
|
connect(conn, &LibInput::Connection::touchCanceled, this, &InputRedirection::cancelTouch);
|
|
connect(conn, &LibInput::Connection::touchFrame, this, &InputRedirection::touchFrame);
|
|
if (screens()) {
|
|
setupLibInputWithScreens();
|
|
} else {
|
|
connect(kwinApp(), &Application::screensCreated, this, &InputRedirection::setupLibInputWithScreens);
|
|
}
|
|
if (auto s = findSeat()) {
|
|
s->setHasKeyboard(conn->hasKeyboard());
|
|
s->setHasPointer(conn->hasPointer());
|
|
s->setHasTouch(conn->hasTouch());
|
|
connect(conn, &LibInput::Connection::hasKeyboardChanged, this,
|
|
[this, s] (bool set) {
|
|
if (m_libInput->isSuspended()) {
|
|
return;
|
|
}
|
|
s->setHasKeyboard(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(VirtualTerminal::self(), &VirtualTerminal::activeChanged, m_libInput,
|
|
[this] (bool active) {
|
|
if (!active) {
|
|
m_libInput->deactivate();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
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());
|
|
}
|
|
);
|
|
// set pos to center of all screens
|
|
connect(screens(), &Screens::changed, this, &InputRedirection::updatePointerAfterScreenChange);
|
|
m_globalPointer = screens()->geometry().center();
|
|
emit globalPointerChanged(m_globalPointer);
|
|
// sanitize
|
|
updatePointerAfterScreenChange();
|
|
#endif
|
|
}
|
|
|
|
void InputRedirection::updatePointerWindow()
|
|
{
|
|
if (waylandServer() && waylandServer()->isScreenLocked()) {
|
|
return;
|
|
}
|
|
// TODO: handle pointer grab aka popups
|
|
Toplevel *t = findToplevel(m_globalPointer.toPoint());
|
|
updatePointerInternalWindow();
|
|
if (!m_pointerInternalWindow) {
|
|
updatePointerDecoration(t);
|
|
} else {
|
|
m_pointerDecoration.clear();
|
|
}
|
|
if (m_pointerDecoration || m_pointerInternalWindow) {
|
|
t = nullptr;
|
|
}
|
|
auto oldWindow = m_pointerWindow;
|
|
if (!oldWindow.isNull() && t == m_pointerWindow.data()) {
|
|
return;
|
|
}
|
|
if (auto seat = findSeat()) {
|
|
// disconnect old surface
|
|
if (oldWindow) {
|
|
disconnect(oldWindow.data(), &Toplevel::geometryChanged, this, &InputRedirection::updateFocusedPointerPosition);
|
|
if (AbstractBackend *b = waylandServer()->backend()) {
|
|
if (auto p = seat->focusedPointer()) {
|
|
if (auto c = p->cursor()) {
|
|
disconnect(c, &KWayland::Server::Cursor::changed, b, &AbstractBackend::installCursorFromServer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (t && t->surface()) {
|
|
seat->setFocusedPointerSurface(t->surface(), t->inputTransformation());
|
|
connect(t, &Toplevel::geometryChanged, this, &InputRedirection::updateFocusedPointerPosition);
|
|
if (AbstractBackend *b = waylandServer()->backend()) {
|
|
b->installCursorFromServer();
|
|
if (auto p = seat->focusedPointer()) {
|
|
if (auto c = p->cursor()) {
|
|
connect(c, &KWayland::Server::Cursor::changed, b, &AbstractBackend::installCursorFromServer);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
seat->setFocusedPointerSurface(nullptr);
|
|
t = nullptr;
|
|
}
|
|
}
|
|
if (!t) {
|
|
m_pointerWindow.clear();
|
|
return;
|
|
}
|
|
m_pointerWindow = QWeakPointer<Toplevel>(t);
|
|
}
|
|
|
|
void InputRedirection::updatePointerDecoration(Toplevel *t)
|
|
{
|
|
const auto oldDeco = m_pointerDecoration;
|
|
if (AbstractClient *c = dynamic_cast<AbstractClient*>(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(m_globalPointer.toPoint())) {
|
|
m_pointerDecoration = c->decoratedClient();
|
|
} else {
|
|
m_pointerDecoration.clear();
|
|
}
|
|
} else {
|
|
m_pointerDecoration.clear();
|
|
}
|
|
} else {
|
|
m_pointerDecoration.clear();
|
|
}
|
|
|
|
if (oldDeco && oldDeco != m_pointerDecoration) {
|
|
// send leave
|
|
QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF());
|
|
QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event);
|
|
if (!m_pointerDecoration && waylandServer()) {
|
|
waylandServer()->backend()->installCursorImage(Qt::ArrowCursor);
|
|
}
|
|
}
|
|
if (m_pointerDecoration) {
|
|
const QPointF p = m_globalPointer - t->pos();
|
|
QHoverEvent event(QEvent::HoverMove, p, p);
|
|
QCoreApplication::instance()->sendEvent(m_pointerDecoration->decoration(), &event);
|
|
m_pointerDecoration->client()->processDecorationMove();
|
|
installCursorFromDecoration();
|
|
}
|
|
}
|
|
|
|
void InputRedirection::updatePointerInternalWindow()
|
|
{
|
|
const auto oldInternalWindow = m_pointerInternalWindow;
|
|
if (waylandServer()) {
|
|
bool found = false;
|
|
const auto &internalClients = waylandServer()->internalClients();
|
|
const bool change = m_pointerInternalWindow.isNull() || !(m_pointerInternalWindow->flags().testFlag(Qt::Popup) && m_pointerInternalWindow->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_globalPointer.toPoint())) {
|
|
m_pointerInternalWindow = QPointer<QWindow>(w);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
} while (it != internalClients.begin());
|
|
if (!found) {
|
|
m_pointerInternalWindow.clear();
|
|
}
|
|
}
|
|
}
|
|
if (oldInternalWindow != m_pointerInternalWindow) {
|
|
// changed
|
|
if (oldInternalWindow) {
|
|
disconnect(oldInternalWindow.data(), &QWindow::visibleChanged, this, &InputRedirection::pointerInternalWindowVisibilityChanged);
|
|
QEvent event(QEvent::Leave);
|
|
QCoreApplication::sendEvent(oldInternalWindow.data(), &event);
|
|
}
|
|
if (m_pointerInternalWindow) {
|
|
connect(oldInternalWindow.data(), &QWindow::visibleChanged, this, &InputRedirection::pointerInternalWindowVisibilityChanged);
|
|
QEnterEvent event(m_globalPointer - m_pointerInternalWindow->position(),
|
|
m_globalPointer - m_pointerInternalWindow->position(),
|
|
m_globalPointer);
|
|
QCoreApplication::sendEvent(m_pointerInternalWindow.data(), &event);
|
|
return;
|
|
}
|
|
}
|
|
if (m_pointerInternalWindow) {
|
|
// send mouse move
|
|
QMouseEvent event(QEvent::MouseMove,
|
|
m_globalPointer.toPoint() - m_pointerInternalWindow->position(),
|
|
m_globalPointer.toPoint(),
|
|
Qt::NoButton, qtButtonStates(), keyboardModifiers());
|
|
QCoreApplication::sendEvent(m_pointerInternalWindow.data(), &event);
|
|
}
|
|
}
|
|
|
|
void InputRedirection::pointerInternalWindowVisibilityChanged(bool visible)
|
|
{
|
|
if (!visible) {
|
|
updatePointerWindow();
|
|
}
|
|
}
|
|
|
|
void InputRedirection::installCursorFromDecoration()
|
|
{
|
|
if (waylandServer() && m_pointerDecoration) {
|
|
waylandServer()->backend()->installCursorImage(m_pointerDecoration->client()->cursor());
|
|
}
|
|
}
|
|
|
|
void InputRedirection::updateFocusedPointerPosition()
|
|
{
|
|
if (!workspace()) {
|
|
return;
|
|
}
|
|
if (m_pointerWindow.isNull()) {
|
|
return;
|
|
}
|
|
if (workspace()->getMovingClient()) {
|
|
// don't update while moving
|
|
return;
|
|
}
|
|
if (auto seat = findSeat()) {
|
|
if (m_pointerWindow.data()->surface() != seat->focusedPointerSurface()) {
|
|
return;
|
|
}
|
|
seat->setFocusedPointerSurfaceTransformation(m_pointerWindow.data()->inputTransformation());
|
|
}
|
|
}
|
|
|
|
void InputRedirection::updateFocusedTouchPosition()
|
|
{
|
|
if (m_touchWindow.isNull()) {
|
|
return;
|
|
}
|
|
if (auto seat = findSeat()) {
|
|
if (m_touchWindow.data()->surface() != seat->focusedTouchSurface()) {
|
|
return;
|
|
}
|
|
seat->setFocusedTouchSurfacePosition(m_touchWindow.data()->pos());
|
|
}
|
|
}
|
|
|
|
void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time)
|
|
{
|
|
if (!workspace()) {
|
|
return;
|
|
}
|
|
|
|
// first update to new mouse position
|
|
// const QPointF oldPos = m_globalPointer;
|
|
updatePointerPosition(pos);
|
|
|
|
if (waylandServer()->isScreenLocked()) {
|
|
Toplevel *t = findToplevel(m_globalPointer.toPoint());
|
|
if (t && t->surface()) {
|
|
if (auto seat = findSeat()) {
|
|
seat->setFocusedPointerSurface(t->surface(), t->inputTransformation());
|
|
seat->setTimestamp(time);
|
|
seat->setPointerPos(m_globalPointer);
|
|
}
|
|
} else {
|
|
if (auto seat = findSeat()) {
|
|
seat->setFocusedPointerSurface(nullptr);
|
|
seat->setTimestamp(time);
|
|
seat->setPointerPos(m_globalPointer);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// TODO: check which part of KWin would like to intercept the event
|
|
QMouseEvent event(QEvent::MouseMove, m_globalPointer.toPoint(), m_globalPointer.toPoint(),
|
|
Qt::NoButton, qtButtonStates(), keyboardModifiers());
|
|
// check whether an effect has a mouse grab
|
|
if (effects && static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowEvent(&event)) {
|
|
// an effect grabbed the pointer, we do not forward the event to surfaces
|
|
return;
|
|
}
|
|
if (AbstractClient *c = workspace()->getMovingClient()) {
|
|
c->updateMoveResize(m_globalPointer);
|
|
} else {
|
|
QWeakPointer<Toplevel> old = m_pointerWindow;
|
|
if (!areButtonsPressed()) {
|
|
// update pointer window only if no button is pressed
|
|
updatePointerWindow();
|
|
} else if (m_pointerDecoration) {
|
|
const QPointF p = m_globalPointer - m_pointerDecoration->client()->pos();
|
|
QHoverEvent event(QEvent::HoverMove, p, p);
|
|
QCoreApplication::instance()->sendEvent(m_pointerDecoration->decoration(), &event);
|
|
m_pointerDecoration->client()->processDecorationMove();
|
|
}
|
|
}
|
|
if (auto seat = findSeat()) {
|
|
seat->setTimestamp(time);
|
|
seat->setPointerPos(m_globalPointer);
|
|
}
|
|
}
|
|
|
|
void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time)
|
|
{
|
|
if (!workspace()) {
|
|
return;
|
|
}
|
|
m_pointerButtons[button] = state;
|
|
emit pointerButtonStateChanged(button, state);
|
|
|
|
QMouseEvent event(buttonStateToEvent(state), m_globalPointer.toPoint(), m_globalPointer.toPoint(),
|
|
buttonToQtMouseButton(button), qtButtonStates(), keyboardModifiers());
|
|
|
|
if (waylandServer()->isScreenLocked()) {
|
|
if (auto seat = findSeat()) {
|
|
KWayland::Server::SurfaceInterface *s = seat->focusedPointerSurface();
|
|
if (s) {
|
|
Toplevel *t = waylandServer()->findClient(s);
|
|
if (t->isLockScreen() || t->isInputMethod()) {
|
|
seat->setTimestamp(time);
|
|
state == PointerButtonPressed ? seat->pointerButtonPressed(button) : seat->pointerButtonReleased(button);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// check whether an effect has a mouse grab
|
|
if (effects && static_cast<EffectsHandlerImpl*>(effects)->checkInputWindowEvent(&event)) {
|
|
// an effect grabbed the pointer, we do not forward the event to surfaces
|
|
return;
|
|
}
|
|
if (AbstractClient *c = workspace()->getMovingClient()) {
|
|
if (state == KWin::InputRedirection::PointerButtonReleased) {
|
|
if (!areButtonsPressed()) {
|
|
c->endMoveResize();
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (state == KWin::InputRedirection::PointerButtonPressed) {
|
|
if (m_shortcuts->processPointerPressed(m_xkb->modifiers(), qtButtonStates())) {
|
|
return;
|
|
}
|
|
}
|
|
if (m_pointerInternalWindow) {
|
|
// send mouse move
|
|
QMouseEvent event(buttonStateToEvent(state),
|
|
m_globalPointer.toPoint() - m_pointerInternalWindow->position(),
|
|
m_globalPointer.toPoint(),
|
|
buttonToQtMouseButton(button), qtButtonStates(), keyboardModifiers());
|
|
event.setAccepted(false);
|
|
QCoreApplication::sendEvent(m_pointerInternalWindow.data(), &event);
|
|
}
|
|
if (m_pointerDecoration) {
|
|
const QPoint localPos = m_globalPointer.toPoint() - m_pointerDecoration->client()->pos();
|
|
QMouseEvent event(buttonStateToEvent(state),
|
|
localPos,
|
|
m_globalPointer.toPoint(),
|
|
buttonToQtMouseButton(button), qtButtonStates(), keyboardModifiers());
|
|
event.setAccepted(false);
|
|
QCoreApplication::sendEvent(m_pointerDecoration->decoration(), &event);
|
|
if (!event.isAccepted()) {
|
|
if (state == PointerButtonPressed) {
|
|
m_pointerDecoration->client()->processDecorationButtonPress(&event);
|
|
}
|
|
}
|
|
if (state == PointerButtonReleased) {
|
|
m_pointerDecoration->client()->processDecorationButtonRelease(&event);
|
|
}
|
|
installCursorFromDecoration();
|
|
}
|
|
// TODO: check which part of KWin would like to intercept the event
|
|
if (auto seat = findSeat()) {
|
|
seat->setTimestamp(time);
|
|
bool passThrough = true;
|
|
if (state == PointerButtonPressed) {
|
|
if (AbstractClient *c = dynamic_cast<AbstractClient*>(m_pointerWindow.data())) {
|
|
bool wasAction = false;
|
|
const Options::MouseCommand command = c->getMouseCommand(buttonToQtMouseButton(button), &wasAction);
|
|
if (wasAction) {
|
|
passThrough = c->performMouseCommand(command, m_globalPointer.toPoint());
|
|
}
|
|
}
|
|
}
|
|
if (passThrough) {
|
|
state == PointerButtonPressed ? seat->pointerButtonPressed(button) : seat->pointerButtonReleased(button);
|
|
}
|
|
}
|
|
if (state == PointerButtonReleased && !areButtonsPressed()) {
|
|
updatePointerWindow();
|
|
}
|
|
}
|
|
|
|
void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time)
|
|
{
|
|
if (delta == 0) {
|
|
return;
|
|
}
|
|
|
|
emit pointerAxisChanged(axis, delta);
|
|
|
|
if (waylandServer()->isScreenLocked()) {
|
|
if (auto seat = findSeat()) {
|
|
KWayland::Server::SurfaceInterface *s = seat->focusedPointerSurface();
|
|
if (s) {
|
|
Toplevel *t = waylandServer()->findClient(s);
|
|
if (t->isLockScreen() || t->isInputMethod()) {
|
|
seat->setTimestamp(time);
|
|
seat->pointerAxis(axis == InputRedirection::PointerAxisHorizontal ? Qt::Horizontal : Qt::Vertical, delta);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (m_xkb->modifiers() != Qt::NoModifier) {
|
|
PointerAxisDirection direction = PointerAxisUp;
|
|
if (axis == PointerAxisVertical) {
|
|
if (delta > 0) {
|
|
direction = PointerAxisUp;
|
|
} else {
|
|
direction = PointerAxisDown;
|
|
}
|
|
} else {
|
|
if (delta > 0) {
|
|
direction = PointerAxisLeft;
|
|
} else {
|
|
direction = PointerAxisRight;
|
|
}
|
|
}
|
|
if (m_shortcuts->processAxis(m_xkb->modifiers(), direction)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto sendWheelEvent = [this, delta, axis] (const QPoint targetPos, QObject *target) -> bool {
|
|
const QPointF localPos = m_globalPointer - targetPos;
|
|
// TODO: add modifiers and buttons
|
|
QWheelEvent event(localPos, m_globalPointer, QPoint(),
|
|
(axis == PointerAxisHorizontal) ? QPoint(delta, 0) : QPoint(0, delta),
|
|
delta,
|
|
(axis == PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical,
|
|
Qt::NoButton,
|
|
Qt::NoModifier);
|
|
event.setAccepted(false);
|
|
QCoreApplication::sendEvent(target, &event);
|
|
return event.isAccepted();
|
|
};
|
|
if (m_pointerDecoration) {
|
|
if (!sendWheelEvent(m_pointerDecoration->client()->pos(), m_pointerDecoration.data())
|
|
&& axis == PointerAxisVertical) {
|
|
if (m_pointerDecoration->decoration()->titleBar().contains(m_globalPointer.toPoint() - m_pointerDecoration->client()->pos())) {
|
|
m_pointerDecoration->client()->performMouseCommand(options->operationTitlebarMouseWheel(delta * -1),
|
|
m_globalPointer.toPoint());
|
|
}
|
|
}
|
|
}
|
|
if (m_pointerInternalWindow) {
|
|
sendWheelEvent(m_pointerInternalWindow->position(), m_pointerInternalWindow.data());
|
|
}
|
|
|
|
// TODO: check which part of KWin would like to intercept the event
|
|
// TODO: Axis support for effect redirection
|
|
if (auto seat = findSeat()) {
|
|
seat->setTimestamp(time);
|
|
seat->pointerAxis(axis == InputRedirection::PointerAxisHorizontal ? Qt::Horizontal : Qt::Vertical, delta);
|
|
}
|
|
}
|
|
|
|
void InputRedirection::updateKeyboardWindow()
|
|
{
|
|
if (!workspace()) {
|
|
return;
|
|
}
|
|
if (auto seat = findSeat()) {
|
|
// TODO: this needs better integration
|
|
Toplevel *t = workspace()->activeClient();
|
|
if (t && t->surface()) {
|
|
if (t->surface() != seat->focusedKeyboardSurface()) {
|
|
seat->setFocusedKeyboardSurface(t->surface());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time)
|
|
{
|
|
emit keyStateChanged(key, state);
|
|
const Qt::KeyboardModifiers oldMods = keyboardModifiers();
|
|
m_xkb->updateKey(key, state);
|
|
if (oldMods != keyboardModifiers()) {
|
|
emit keyboardModifiersChanged(keyboardModifiers(), oldMods);
|
|
}
|
|
// check for vt-switch
|
|
#if HAVE_INPUT
|
|
if (VirtualTerminal::self()) {
|
|
const xkb_keysym_t keysym = m_xkb->toKeysym(key);
|
|
if (state == KWin::InputRedirection::KeyboardKeyPressed &&
|
|
(keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12)) {
|
|
VirtualTerminal::self()->activate(keysym - XKB_KEY_XF86Switch_VT_1 + 1);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
if (waylandServer()->isScreenLocked()) {
|
|
const ToplevelList &stacking = Workspace::self()->stackingOrder();
|
|
if (stacking.isEmpty()) {
|
|
return;
|
|
}
|
|
auto it = stacking.end();
|
|
do {
|
|
--it;
|
|
Toplevel *t = (*it);
|
|
if (t->isDeleted()) {
|
|
// a deleted window doesn't get mouse events
|
|
continue;
|
|
}
|
|
if (!t->isLockScreen()) {
|
|
continue;
|
|
}
|
|
if (!t->readyForPainting()) {
|
|
continue;
|
|
}
|
|
if (auto seat = findSeat()) {
|
|
seat->setFocusedKeyboardSurface(t->surface());
|
|
seat->setTimestamp(time);
|
|
state == InputRedirection::KeyboardKeyPressed ? seat->keyPressed(key) : seat->keyReleased(key);
|
|
}
|
|
return;
|
|
} while (it != stacking.begin());
|
|
if (auto seat = findSeat()) {
|
|
seat->setFocusedKeyboardSurface(nullptr);
|
|
seat->setTimestamp(time);
|
|
state == InputRedirection::KeyboardKeyPressed ? seat->keyPressed(key) : seat->keyReleased(key);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// TODO: pass to internal parts of KWin
|
|
#ifdef KWIN_BUILD_TABBOX
|
|
if (TabBox::TabBox::self() && TabBox::TabBox::self()->isGrabbed()) {
|
|
if (state == KWin::InputRedirection::KeyboardKeyPressed) {
|
|
TabBox::TabBox::self()->keyPress(m_xkb->modifiers() | m_xkb->toQtKey(m_xkb->toKeysym(key)));
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
auto toKeyEvent = [&] {
|
|
const xkb_keysym_t keysym = m_xkb->toKeysym(key);
|
|
// TODO: start auto-repeat
|
|
// TODO: add modifiers to the event
|
|
const QEvent::Type type = (state == KeyboardKeyPressed) ? QEvent::KeyPress : QEvent::KeyRelease;
|
|
QKeyEvent event(type, m_xkb->toQtKey(keysym), m_xkb->modifiers(), m_xkb->toString(keysym));
|
|
return event;
|
|
};
|
|
if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) {
|
|
QKeyEvent event = toKeyEvent();
|
|
static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(&event);
|
|
return;
|
|
}
|
|
if (workspace()) {
|
|
if (AbstractClient *c = workspace()->getMovingClient()) {
|
|
// TODO: handle key repeat
|
|
if (state == KeyboardKeyPressed) {
|
|
c->keyPressEvent(m_xkb->toQtKey(m_xkb->toKeysym(key)) | m_xkb->modifiers());
|
|
if (c->isMove() || c->isResize()) {
|
|
// only update if mode didn't end
|
|
c->updateMoveResize(m_globalPointer);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// TODO: Maybe it's better to select the top most visible internal window?
|
|
if (m_pointerInternalWindow) {
|
|
QKeyEvent event = toKeyEvent();
|
|
event.setAccepted(false);
|
|
QCoreApplication::sendEvent(m_pointerInternalWindow.data(), &event);
|
|
return;
|
|
}
|
|
}
|
|
// process global shortcuts
|
|
if (state == KeyboardKeyPressed) {
|
|
if (m_shortcuts->processKey(m_xkb->modifiers(), m_xkb->toKeysym(key))) {
|
|
return;
|
|
}
|
|
}
|
|
if (auto seat = findSeat()) {
|
|
if (workspace()->activeClient() &&
|
|
(seat->focusedKeyboardSurface() != workspace()->activeClient()->surface())) {
|
|
seat->setFocusedKeyboardSurface(workspace()->activeClient()->surface());
|
|
}
|
|
seat->setTimestamp(time);
|
|
state == InputRedirection::KeyboardKeyPressed ? seat->keyPressed(key) : seat->keyReleased(key);
|
|
}
|
|
}
|
|
|
|
void InputRedirection::processKeyboardModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group)
|
|
{
|
|
// TODO: send to proper Client and also send when active Client changes
|
|
Qt::KeyboardModifiers oldMods = keyboardModifiers();
|
|
m_xkb->updateModifiers(modsDepressed, modsLatched, modsLocked, group);
|
|
if (oldMods != keyboardModifiers()) {
|
|
emit keyboardModifiersChanged(keyboardModifiers(), oldMods);
|
|
}
|
|
}
|
|
|
|
void InputRedirection::processKeymapChange(int fd, uint32_t size)
|
|
{
|
|
// TODO: should we pass the keymap to our Clients? Or only to the currently active one and update
|
|
m_xkb->installKeymap(fd, size);
|
|
}
|
|
|
|
void InputRedirection::processTouchDown(qint32 id, const QPointF &pos, quint32 time)
|
|
{
|
|
// TODO: internal handling?
|
|
if (auto seat = findSeat()) {
|
|
seat->setTimestamp(time);
|
|
if (!seat->isTouchSequence()) {
|
|
updateTouchWindow(pos);
|
|
if (AbstractClient *c = dynamic_cast<AbstractClient*>(m_touchWindow.data())) {
|
|
// perform same handling as if it were a left click
|
|
bool wasAction = false;
|
|
const Options::MouseCommand command = c->getMouseCommand(Qt::LeftButton, &wasAction);
|
|
if (wasAction) {
|
|
// if no replay we filter out this touch point
|
|
if (!c->performMouseCommand(command, pos.toPoint())) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
m_touchIdMapper.insert(id, seat->touchDown(pos));
|
|
}
|
|
}
|
|
|
|
void InputRedirection::updateTouchWindow(const QPointF &pos)
|
|
{
|
|
// TODO: handle pointer grab aka popups
|
|
Toplevel *t = findToplevel(pos.toPoint());
|
|
auto oldWindow = m_touchWindow;
|
|
if (!oldWindow.isNull() && t == oldWindow.data()) {
|
|
return;
|
|
}
|
|
if (auto seat = findSeat()) {
|
|
// disconnect old surface
|
|
if (oldWindow) {
|
|
disconnect(oldWindow.data(), &Toplevel::geometryChanged, this, &InputRedirection::updateFocusedTouchPosition);
|
|
}
|
|
if (t && t->surface()) {
|
|
seat->setFocusedTouchSurface(t->surface(), t->pos());
|
|
connect(t, &Toplevel::geometryChanged, this, &InputRedirection::updateFocusedTouchPosition);
|
|
} else {
|
|
seat->setFocusedTouchSurface(nullptr);
|
|
t = nullptr;
|
|
}
|
|
}
|
|
if (!t) {
|
|
m_touchWindow.clear();
|
|
return;
|
|
}
|
|
m_touchWindow = QWeakPointer<Toplevel>(t);
|
|
}
|
|
|
|
|
|
void InputRedirection::processTouchUp(qint32 id, quint32 time)
|
|
{
|
|
// TODO: internal handling?
|
|
if (auto seat = findSeat()) {
|
|
auto it = m_touchIdMapper.constFind(id);
|
|
if (it != m_touchIdMapper.constEnd()) {
|
|
seat->setTimestamp(time);
|
|
seat->touchUp(it.value());
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputRedirection::processTouchMotion(qint32 id, const QPointF &pos, quint32 time)
|
|
{
|
|
// TODO: internal handling?
|
|
if (auto seat = findSeat()) {
|
|
seat->setTimestamp(time);
|
|
auto it = m_touchIdMapper.constFind(id);
|
|
if (it != m_touchIdMapper.constEnd()) {
|
|
seat->setTimestamp(time);
|
|
seat->touchMove(it.value(), pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputRedirection::cancelTouch()
|
|
{
|
|
if (auto seat = findSeat()) {
|
|
seat->cancelTouchSequence();
|
|
}
|
|
}
|
|
|
|
void InputRedirection::touchFrame()
|
|
{
|
|
if (auto seat = findSeat()) {
|
|
seat->touchFrame();
|
|
}
|
|
}
|
|
|
|
QEvent::Type InputRedirection::buttonStateToEvent(InputRedirection::PointerButtonState state)
|
|
{
|
|
switch (state) {
|
|
case KWin::InputRedirection::PointerButtonReleased:
|
|
return QEvent::MouseButtonRelease;
|
|
case KWin::InputRedirection::PointerButtonPressed:
|
|
return QEvent::MouseButtonPress;
|
|
}
|
|
return QEvent::None;
|
|
}
|
|
|
|
Qt::MouseButton InputRedirection::buttonToQtMouseButton(uint32_t button)
|
|
{
|
|
switch (button) {
|
|
case BTN_LEFT:
|
|
return Qt::LeftButton;
|
|
case BTN_MIDDLE:
|
|
return Qt::MiddleButton;
|
|
case BTN_RIGHT:
|
|
return Qt::RightButton;
|
|
case BTN_BACK:
|
|
return Qt::XButton1;
|
|
case BTN_FORWARD:
|
|
return Qt::XButton2;
|
|
}
|
|
return Qt::NoButton;
|
|
}
|
|
|
|
Qt::MouseButtons InputRedirection::qtButtonStates() const
|
|
{
|
|
Qt::MouseButtons buttons;
|
|
for (auto it = m_pointerButtons.constBegin(); it != m_pointerButtons.constEnd(); ++it) {
|
|
if (it.value() == KWin::InputRedirection::PointerButtonReleased) {
|
|
continue;
|
|
}
|
|
Qt::MouseButton button = buttonToQtMouseButton(it.key());
|
|
if (button != Qt::NoButton) {
|
|
buttons |= button;
|
|
}
|
|
}
|
|
return buttons;
|
|
}
|
|
|
|
bool InputRedirection::areButtonsPressed() const
|
|
{
|
|
for (auto it = m_pointerButtons.constBegin(); it != m_pointerButtons.constEnd(); ++it) {
|
|
if (it.value() == KWin::InputRedirection::PointerButtonPressed) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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) {
|
|
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<AbstractClient*>(t)) {
|
|
if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab()) {
|
|
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;
|
|
}
|
|
|
|
uint8_t InputRedirection::toXPointerButton(uint32_t button)
|
|
{
|
|
switch (button) {
|
|
case BTN_LEFT:
|
|
return XCB_BUTTON_INDEX_1;
|
|
case BTN_RIGHT:
|
|
return XCB_BUTTON_INDEX_3;
|
|
case BTN_MIDDLE:
|
|
return XCB_BUTTON_INDEX_2;
|
|
default:
|
|
// TODO: add more buttons
|
|
return XCB_BUTTON_INDEX_ANY;
|
|
}
|
|
}
|
|
|
|
uint8_t InputRedirection::toXPointerButton(InputRedirection::PointerAxis axis, qreal delta)
|
|
{
|
|
switch (axis) {
|
|
case PointerAxisVertical:
|
|
if (delta < 0) {
|
|
return 4;
|
|
} else {
|
|
return 5;
|
|
}
|
|
case PointerAxisHorizontal:
|
|
if (delta < 0) {
|
|
return 6;
|
|
} else {
|
|
return 7;
|
|
}
|
|
}
|
|
return XCB_BUTTON_INDEX_ANY;
|
|
}
|
|
|
|
Qt::KeyboardModifiers InputRedirection::keyboardModifiers() const
|
|
{
|
|
return m_xkb->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);
|
|
}
|
|
});
|
|
}
|
|
|
|
static bool screenContainsPos(const QPointF &pos)
|
|
{
|
|
for (int i = 0; i < screens()->count(); ++i) {
|
|
if (screens()->geometry(i).contains(pos.toPoint())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void InputRedirection::updatePointerPosition(const QPointF &pos)
|
|
{
|
|
// verify that at least one screen contains the pointer position
|
|
QPointF p = pos;
|
|
if (!screenContainsPos(p)) {
|
|
// allow either x or y to pass
|
|
p = QPointF(m_globalPointer.x(), pos.y());
|
|
if (!screenContainsPos(p)) {
|
|
p = QPointF(pos.x(), m_globalPointer.y());
|
|
if (!screenContainsPos(p)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
m_globalPointer = p;
|
|
emit globalPointerChanged(m_globalPointer);
|
|
}
|
|
|
|
void InputRedirection::updatePointerAfterScreenChange()
|
|
{
|
|
if (screenContainsPos(m_globalPointer)) {
|
|
// pointer still on a screen
|
|
return;
|
|
}
|
|
// pointer no longer on a screen, reposition to closes screen
|
|
m_globalPointer = screens()->geometry(screens()->number(m_globalPointer.toPoint())).center();
|
|
emit globalPointerChanged(m_globalPointer);
|
|
}
|
|
|
|
void InputRedirection::warpPointer(const QPointF &pos)
|
|
{
|
|
if (supportsPointerWarping()) {
|
|
if (waylandServer()) {
|
|
waylandServer()->backend()->warpPointer(pos);
|
|
}
|
|
updatePointerPosition(pos);
|
|
}
|
|
}
|
|
|
|
bool InputRedirection::supportsPointerWarping() const
|
|
{
|
|
if (waylandServer() && waylandServer()->backend()->supportsPointerWarping()) {
|
|
return true;
|
|
}
|
|
return m_pointerWarping;
|
|
}
|
|
|
|
} // namespace
|