You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1377 lines
46 KiB
1377 lines
46 KiB
/******************************************************************** |
|
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" |
|
#endif |
|
#include "abstract_backend.h" |
|
#include "shell_client.h" |
|
#include "wayland_server.h" |
|
#include "virtual_terminal.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->pos()); |
|
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 (Client *c = dynamic_cast<Client*>(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->setFocusedPointerSurfacePosition(m_pointerWindow.data()->pos()); |
|
} |
|
} |
|
|
|
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->pos()); |
|
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 == PointerAxisHorizontal) { |
|
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 (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; |
|
} |
|
} |
|
|
|
|
|
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()); |
|
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()) { |
|
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()) { |
|
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
|
|
|