diff --git a/input.cpp b/input.cpp index 6a05f1760..bcdc7c9dc 100644 --- a/input.cpp +++ b/input.cpp @@ -1,488 +1,491 @@ /******************************************************************** 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 "client.h" #include "effects.h" #include "globalshortcuts.h" #ifdef KWIN_BUILD_TABBOX #include "tabbox/tabbox.h" #endif #include "unmanaged.h" #include "workspace.h" // KDE #include // TODO: remove xtest #include +#if HAVE_XKB +#include +#endif // system #include #include namespace KWin { #if HAVE_XKB Xkb::Xkb() : m_context(xkb_context_new(static_cast(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) { qDebug() << "Could not create xkb context"; } } 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(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) { qDebug() << "Could not map keymap from file"; return; } xkb_state *state = xkb_state_new(keymap); if (!state) { qDebug() << "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); } 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); 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; } 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(state)); } 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(key); } #endif KWIN_SINGLETON_FACTORY(InputRedirection) InputRedirection::InputRedirection(QObject *parent) : QObject(parent) #if HAVE_XKB , m_xkb(new Xkb()) #endif , m_pointerWindow() , m_shortcuts(new GlobalShortcutsManager(this)) { } InputRedirection::~InputRedirection() { s_self = NULL; } void InputRedirection::updatePointerWindow() { // TODO: handle pointer grab aka popups Toplevel *t = findToplevel(m_globalPointer.toPoint()); const bool oldWindowValid = !m_pointerWindow.isNull(); if (oldWindowValid && t == m_pointerWindow.data()) { return; } if (oldWindowValid) { m_pointerWindow.data()->sendPointerLeaveEvent(m_globalPointer); } if (!t) { m_pointerWindow.clear(); return; } m_pointerWindow = QWeakPointer(t); t->sendPointerEnterEvent(m_globalPointer); } void InputRedirection::processPointerMotion(const QPointF &pos, uint32_t time) { Q_UNUSED(time) // first update to new mouse position // const QPointF oldPos = m_globalPointer; m_globalPointer = pos; emit globalPointerChanged(m_globalPointer); // 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(effects)->checkInputWindowEvent(&event)) { // an effect grabbed the pointer, we do not forward the event to surfaces return; } QWeakPointer old = m_pointerWindow; updatePointerWindow(); if (!m_pointerWindow.isNull() && old.data() == m_pointerWindow.data()) { m_pointerWindow.data()->sendPointerMoveEvent(pos); } // TODO: don't use xtest // still doing the fake event here as it requires the event to be send on the root window xcb_test_fake_input(connection(), XCB_MOTION_NOTIFY, 0, XCB_TIME_CURRENT_TIME, XCB_WINDOW_NONE, pos.toPoint().x(), pos.toPoint().y(), 0); } void InputRedirection::processPointerButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { Q_UNUSED(time) m_pointerButtons[button] = state; emit pointerButtonStateChanged(button, state); QMouseEvent event(buttonStateToEvent(state), m_globalPointer.toPoint(), m_globalPointer.toPoint(), buttonToQtMouseButton(button), qtButtonStates(), keyboardModifiers()); // check whether an effect has a mouse grab if (effects && static_cast(effects)->checkInputWindowEvent(&event)) { // an effect grabbed the pointer, we do not forward the event to surfaces return; } #if HAVE_XKB if (state == KWin::InputRedirection::PointerButtonPressed) { if (m_shortcuts->processPointerPressed(m_xkb->modifiers(), qtButtonStates())) { return; } } #endif // TODO: check which part of KWin would like to intercept the event if (m_pointerWindow.isNull()) { // there is no window which can receive the return; } m_pointerWindow.data()->sendPointerButtonEvent(button, state); } void InputRedirection::processPointerAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { Q_UNUSED(time) if (delta == 0) { return; } emit pointerAxisChanged(axis, delta); #if HAVE_XKB 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; } } #endif // TODO: check which part of KWin would like to intercept the event // TODO: Axis support for effect redirection if (m_pointerWindow.isNull()) { // there is no window which can receive the return; } m_pointerWindow.data()->sendPointerAxisEvent(axis, delta); } void InputRedirection::processKeyboardKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { Q_UNUSED(time) #if HAVE_XKB m_xkb->updateKey(key, state); // TODO: pass to internal parts of KWin #ifdef KWIN_BUILD_TABBOX if (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 if (effects && static_cast< EffectsHandlerImpl* >(effects)->hasKeyboardGrab()) { 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)); static_cast< EffectsHandlerImpl* >(effects)->grabbedKeyboardEvent(&event); return; } if (Client *c = workspace()->getMovingClient()) { // TODO: this does not yet fully support moving of the Client // cursor events change the cursor and on Wayland pointer warping is not possible c->keyPressEvent(m_xkb->toQtKey(m_xkb->toKeysym(key))); return; } // process global shortcuts if (state == KeyboardKeyPressed) { if (m_shortcuts->processKey(m_xkb->modifiers(), m_xkb->toKeysym(key))) { return; } } #endif // check unmanaged if (!workspace()->unmanagedList().isEmpty()) { // TODO: better check whether this unmanaged should get the key event workspace()->unmanagedList().first()->sendKeybordKeyEvent(key, state); return; } if (Client *client = workspace()->activeClient()) { client->sendKeybordKeyEvent(key, state); } } 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 #if HAVE_XKB Qt::KeyboardModifiers oldMods = keyboardModifiers(); m_xkb->updateModifiers(modsDepressed, modsLatched, modsLocked, group); if (oldMods != keyboardModifiers()) { emit keyboardModifiersChanged(keyboardModifiers(), oldMods); } #endif } 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 #if HAVE_XKB m_xkb->installKeymap(fd, size); #endif } 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; } Toplevel *InputRedirection::findToplevel(const QPoint &pos) { // TODO: check whether the unmanaged wants input events at all const UnmanagedList &unmanaged = Workspace::self()->unmanagedList(); foreach (Unmanaged *u, unmanaged) { if (u->geometry().contains(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 (t->isClient()) { Client *c = static_cast(t); if (!c->isOnCurrentActivity() || !c->isOnCurrentDesktop() || c->isMinimized() || !c->isCurrentTab()) { continue; } } if (t->geometry().contains(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 { #if HAVE_XKB return m_xkb->modifiers(); #else return Qt::NoModifier; #endif } void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) { m_shortcuts->registerShortcut(action, shortcut); } 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); } } // namespace diff --git a/input.h b/input.h index ca30a0664..bd8c6acb9 100644 --- a/input.h +++ b/input.h @@ -1,229 +1,232 @@ /******************************************************************** 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 -#if HAVE_XKB -#include -#endif class QAction; class QKeySequence; +struct xkb_context; +struct xkb_keymap; +struct xkb_state; +typedef uint32_t xkb_mod_index_t; +typedef uint32_t xkb_keysym_t; + namespace KWin { class GlobalShortcutsManager; class Toplevel; class Xkb; /** * @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 InputRedirection : public QObject { Q_OBJECT public: enum PointerButtonState { PointerButtonReleased, PointerButtonPressed }; enum PointerAxis { PointerAxisVertical, PointerAxisHorizontal }; enum KeyboardKeyState { KeyboardKeyReleased, KeyboardKeyPressed }; virtual ~InputRedirection(); /** * @return const QPointF& The current global pointer position */ const QPointF &globalPointer() const; /** * @brief The last known state of the @p button. If @p button is still unknown the state is * @c PointerButtonReleased. * * @param button The button for which the last known state should be queried. * @return KWin::InputRedirection::PointerButtonState */ PointerButtonState pointerButtonState(uint32_t button) const; Qt::MouseButtons qtButtonStates() const; Qt::KeyboardModifiers keyboardModifiers() const; void registerShortcut(const QKeySequence &shortcut, QAction *action); void registerPointerShortcut(Qt::KeyboardModifiers modifiers, Qt::MouseButton pointerButtons, QAction *action); void registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action); /** * @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); static uint8_t toXPointerButton(uint32_t button); static uint8_t toXPointerButton(PointerAxis axis, qreal delta); public Q_SLOTS: void updatePointerWindow(); 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); private: static QEvent::Type buttonStateToEvent(PointerButtonState state); static Qt::MouseButton buttonToQtMouseButton(uint32_t button); Toplevel *findToplevel(const QPoint &pos); QPointF m_globalPointer; QHash m_pointerButtons; #if HAVE_XKB QScopedPointer m_xkb; #endif /** * @brief The Toplevel which currently receives pointer events */ QWeakPointer m_pointerWindow; GlobalShortcutsManager *m_shortcuts; KWIN_SINGLETON(InputRedirection) friend InputRedirection *input(); }; #if HAVE_XKB class Xkb { public: Xkb(); ~Xkb(); void installKeymap(int fd, uint32_t size); void updateModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group); void updateKey(uint32_t key, InputRedirection::KeyboardKeyState state); xkb_keysym_t toKeysym(uint32_t key); QString toString(xkb_keysym_t keysym); Qt::Key toQtKey(xkb_keysym_t keysym); Qt::KeyboardModifiers modifiers() const; private: xkb_context *m_context; xkb_keymap *m_keymap; xkb_state *m_state; xkb_mod_index_t m_shiftModifier; xkb_mod_index_t m_controlModifier; xkb_mod_index_t m_altModifier; xkb_mod_index_t m_metaModifier; Qt::KeyboardModifiers m_modifiers; }; #endif inline InputRedirection *input() { return InputRedirection::s_self; } inline const QPointF &InputRedirection::globalPointer() const { return m_globalPointer; } inline InputRedirection::PointerButtonState InputRedirection::pointerButtonState(uint32_t button) const { auto it = m_pointerButtons.constFind(button); if (it != m_pointerButtons.constEnd()) { return it.value(); } else { return KWin::InputRedirection::PointerButtonReleased; } } #if HAVE_XKB inline Qt::KeyboardModifiers Xkb::modifiers() const { return m_modifiers; } #endif } // namespace KWin #endif // KWIN_INPUT_H