diff --git a/keyboard_input.cpp b/keyboard_input.cpp index 436e56c1f..26287e832 100644 --- a/keyboard_input.cpp +++ b/keyboard_input.cpp @@ -1,543 +1,545 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "keyboard_input.h" #include "abstract_client.h" #include "options.h" #include "utils.h" #include "toplevel.h" #include "wayland_server.h" #include "workspace.h" // KWayland #include //screenlocker #include // Frameworks #include #include // Qt #include #include #include #include #include // xkbcommon #include #include // system #include #include Q_LOGGING_CATEGORY(KWIN_XKB, "kwin_xkbcommon", QtCriticalMsg); namespace KWin { static void xkbLogHandler(xkb_context *context, xkb_log_level priority, const char *format, va_list args) { Q_UNUSED(context) char buf[1024]; if (std::vsnprintf(buf, 1023, format, args) <= 0) { return; } switch (priority) { case XKB_LOG_LEVEL_DEBUG: qCDebug(KWIN_XKB) << "XKB:" << buf; break; case XKB_LOG_LEVEL_INFO: #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) qCInfo(KWIN_XKB) << "XKB:" << buf; #endif break; case XKB_LOG_LEVEL_WARNING: qCWarning(KWIN_XKB) << "XKB:" << buf; break; case XKB_LOG_LEVEL_ERROR: case XKB_LOG_LEVEL_CRITICAL: default: qCCritical(KWIN_XKB) << "XKB:" << buf; break; } } Xkb::Xkb(InputRedirection *input) : m_input(input) , 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) { qCDebug(KWIN_XKB) << "Could not create xkb context"; } else { xkb_context_set_log_level(m_context, XKB_LOG_LEVEL_DEBUG); xkb_context_set_log_fn(m_context, &xkbLogHandler); } } Xkb::~Xkb() { xkb_state_unref(m_state); xkb_keymap_unref(m_keymap); xkb_context_unref(m_context); } void Xkb::reconfigure() { if (!m_context) { return; } xkb_keymap *keymap = loadKeymapFromConfig(); if (!keymap) { qCDebug(KWIN_XKB) << "Could not create xkb keymap from configuration"; keymap = loadDefaultKeymap(); } if (keymap) { updateKeymap(keymap); } else { qCDebug(KWIN_XKB) << "Could not create default xkb keymap"; } } xkb_keymap *Xkb::loadKeymapFromConfig() { // load config const KConfigGroup config = KSharedConfig::openConfig(QStringLiteral("kxkbrc"), KConfig::NoGlobals)->group("Layout"); const QByteArray model = config.readEntry("Model", "pc104").toLocal8Bit(); const QByteArray layout = config.readEntry("LayoutList", "").toLocal8Bit(); const QByteArray options = config.readEntry("Options", "").toLocal8Bit(); xkb_rule_names ruleNames = { .rules = nullptr, .model = model.constData(), .layout = layout.constData(), .variant = nullptr, .options = options.constData() }; return xkb_keymap_new_from_names(m_context, &ruleNames, XKB_KEYMAP_COMPILE_NO_FLAGS); } xkb_keymap *Xkb::loadDefaultKeymap() { return xkb_keymap_new_from_names(m_context, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS); } 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) { qCDebug(KWIN_XKB) << "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_XKB) << "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); m_currentLayout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE); createKeymapFile(); } void Xkb::createKeymapFile() { if (!waylandServer()) { return; } // TODO: uninstall keymap on server? if (!m_keymap) { return; } ScopedCPointer 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(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(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; const xkb_layout_index_t layout = xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE); if (layout != m_currentLayout) { m_currentLayout = layout; // notify OSD service about the new layout QDBusMessage msg = QDBusMessage::createMethodCall( QStringLiteral("org.kde.plasmashell"), QStringLiteral("/org/kde/osdService"), QStringLiteral("org.kde.osdService"), QStringLiteral("kbdLayoutChanged")); msg << QString::fromLocal8Bit(xkb_keymap_layout_get_name(m_keymap, layout)); QDBusConnection::sessionBus().asyncCall(msg); } waylandServer()->seat()->updateKeyboardModifiers(xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)), xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)), xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)), layout); } 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); } bool Xkb::shouldKeyRepeat(quint32 key) const { if (!m_keymap) { return false; } return xkb_keymap_key_repeats(m_keymap, key) != 0; } void Xkb::switchToNextLayout() { if (!m_keymap || !m_state) { return; } const xkb_layout_index_t numLayouts = xkb_keymap_num_layouts(m_keymap); const xkb_layout_index_t nextLayout = (xkb_state_serialize_layout(m_state, XKB_STATE_LAYOUT_EFFECTIVE) + 1) % numLayouts; const xkb_mod_mask_t depressed = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_DEPRESSED)); const xkb_mod_mask_t latched = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LATCHED)); const xkb_mod_mask_t locked = xkb_state_serialize_mods(m_state, xkb_state_component(XKB_STATE_MODS_LOCKED)); xkb_state_update_mask(m_state, depressed, latched, locked, 0, 0, nextLayout); updateModifiers(); } KeyboardInputRedirection::KeyboardInputRedirection(InputRedirection *parent) : QObject(parent) , m_input(parent) , m_xkb(new Xkb(parent)) { } KeyboardInputRedirection::~KeyboardInputRedirection() { qDeleteAll(m_repeatTimers); m_repeatTimers.clear(); } void KeyboardInputRedirection::init() { Q_ASSERT(!m_inited); m_inited = true; connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(workspace(), &Workspace::clientActivated, this, &KeyboardInputRedirection::update); - connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &KeyboardInputRedirection::update); + if (waylandServer()->hasScreenLockerIntegration()) { + connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &KeyboardInputRedirection::update); + } QAction *switchKeyboardAction = new QAction(this); switchKeyboardAction->setObjectName(QStringLiteral("Switch to Next Keyboard Layout")); switchKeyboardAction->setProperty("componentName", QStringLiteral("KDE Keyboard Layout Switcher")); const QKeySequence sequence = QKeySequence(Qt::ALT+Qt::CTRL+Qt::Key_K); KGlobalAccel::self()->setDefaultShortcut(switchKeyboardAction, QList({sequence})); KGlobalAccel::self()->setShortcut(switchKeyboardAction, QList({sequence})); m_input->registerShortcut(sequence, switchKeyboardAction); connect(switchKeyboardAction, &QAction::triggered, this, [this] { m_xkb->switchToNextLayout(); } ); QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/Layouts"), QStringLiteral("org.kde.keyboard"), QStringLiteral("reloadConfig"), this, SLOT(reconfigure())); m_xkb->reconfigure(); } void KeyboardInputRedirection::reconfigure() { m_xkb->reconfigure(); } void KeyboardInputRedirection::update() { if (!m_inited) { return; } auto seat = waylandServer()->seat(); // TODO: this needs better integration Toplevel *found = nullptr; if (waylandServer()->isScreenLocked()) { const ToplevelList &stacking = Workspace::self()->stackingOrder(); if (!stacking.isEmpty()) { 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; } found = t; break; } while (it != stacking.begin()); } } else { found = workspace()->activeClient(); } if (found && found->surface()) { if (found->surface() != seat->focusedKeyboardSurface()) { seat->setFocusedKeyboardSurface(found->surface()); } } else { seat->setFocusedKeyboardSurface(nullptr); } } void KeyboardInputRedirection::processKey(uint32_t key, InputRedirection::KeyboardKeyState state, uint32_t time) { if (!m_inited) { return; } QEvent::Type type; bool autoRepeat = false; switch (state) { case InputRedirection::KeyboardKeyAutoRepeat: autoRepeat = true; // fall through case InputRedirection::KeyboardKeyPressed: type = QEvent::KeyPress; break; case InputRedirection::KeyboardKeyReleased: type = QEvent::KeyRelease; break; default: Q_UNREACHABLE(); } if (!autoRepeat) { emit m_input->keyStateChanged(key, state); const Qt::KeyboardModifiers oldMods = modifiers(); m_xkb->updateKey(key, state); if (oldMods != modifiers()) { emit m_input->keyboardModifiersChanged(modifiers(), oldMods); } } const xkb_keysym_t keySym = m_xkb->toKeysym(key); QKeyEvent event(type, m_xkb->toQtKey(keySym), m_xkb->modifiers(), key, keySym, 0, m_xkb->toString(m_xkb->toKeysym(key)), autoRepeat); event.setTimestamp(time); if (state == InputRedirection::KeyboardKeyPressed) { if (m_xkb->shouldKeyRepeat(key) && waylandServer()->seat()->keyRepeatDelay() != 0) { QTimer *timer = new QTimer; timer->setInterval(waylandServer()->seat()->keyRepeatDelay()); connect(timer, &QTimer::timeout, this, [this, timer, time, key] { const int delay = 1000 / waylandServer()->seat()->keyRepeatRate(); if (timer->interval() != delay) { timer->setInterval(delay); } // TODO: better time processKey(key, InputRedirection::KeyboardKeyAutoRepeat, time); } ); m_repeatTimers.insert(key, timer); timer->start(); } } else if (state == InputRedirection::KeyboardKeyReleased) { auto it = m_repeatTimers.find(key); if (it != m_repeatTimers.end()) { delete it.value(); m_repeatTimers.erase(it); } } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->keyEvent(&event)) { return; } } } void KeyboardInputRedirection::processModifiers(uint32_t modsDepressed, uint32_t modsLatched, uint32_t modsLocked, uint32_t group) { if (!m_inited) { return; } // TODO: send to proper Client and also send when active Client changes Qt::KeyboardModifiers oldMods = modifiers(); m_xkb->updateModifiers(modsDepressed, modsLatched, modsLocked, group); if (oldMods != modifiers()) { emit m_input->keyboardModifiersChanged(modifiers(), oldMods); } } void KeyboardInputRedirection::processKeymapChange(int fd, uint32_t size) { if (!m_inited) { return; } // TODO: should we pass the keymap to our Clients? Or only to the currently active one and update m_xkb->installKeymap(fd, size); } } diff --git a/pointer_input.cpp b/pointer_input.cpp index f4aaf8eb6..1c5b92eb3 100644 --- a/pointer_input.cpp +++ b/pointer_input.cpp @@ -1,902 +1,906 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "pointer_input.h" #include "platform.h" #include "effects.h" #include "screens.h" #include "shell_client.h" #include "wayland_cursor_theme.h" #include "wayland_server.h" #include "workspace.h" #include "decorations/decoratedclient.h" // KDecoration #include // KWayland #include #include #include #include #include #include #include // screenlocker #include #include #include // Wayland #include #include namespace KWin { static Qt::MouseButton 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_SIDE: // in QtWayland mapped like that return Qt::ExtraButton1; case BTN_EXTRA: // in QtWayland mapped like that return Qt::ExtraButton2; case BTN_BACK: return Qt::BackButton; case BTN_FORWARD: return Qt::ForwardButton; case BTN_TASK: return Qt::TaskButton; // mapped like that in QtWayland case 0x118: return Qt::ExtraButton6; case 0x119: return Qt::ExtraButton7; case 0x11a: return Qt::ExtraButton8; case 0x11b: return Qt::ExtraButton9; case 0x11c: return Qt::ExtraButton10; case 0x11d: return Qt::ExtraButton11; case 0x11e: return Qt::ExtraButton12; case 0x11f: return Qt::ExtraButton13; } // all other values get mapped to ExtraButton24 // this is actually incorrect but doesn't matter in our usage // KWin internally doesn't use these high extra buttons anyway // it's only needed for recognizing whether buttons are pressed // if multiple buttons are mapped to the value the evaluation whether // buttons are pressed is correct and that's all we care about. return Qt::ExtraButton24; } 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; } PointerInputRedirection::PointerInputRedirection(InputRedirection* parent) : QObject(parent) , m_input(parent) , m_cursor(nullptr) , m_supportsWarping(Application::usesLibinput()) { } PointerInputRedirection::~PointerInputRedirection() = default; void PointerInputRedirection::init() { Q_ASSERT(!m_inited); m_cursor = new CursorImage(this); m_inited = true; connect(m_cursor, &CursorImage::changed, kwinApp()->platform(), &Platform::cursorChanged); emit m_cursor->changed(); connect(workspace(), &Workspace::stackingOrderChanged, this, &PointerInputRedirection::update); connect(screens(), &Screens::changed, this, &PointerInputRedirection::updateAfterScreenChange); - connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update); + if (waylandServer()->hasScreenLockerIntegration()) { + connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &PointerInputRedirection::update); + } connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { // need to force a focused pointer change waylandServer()->seat()->setFocusedPointerSurface(nullptr); m_window.clear(); update(); } ); // warp the cursor to center of screen warp(screens()->geometry().center()); updateAfterScreenChange(); } void PointerInputRedirection::processMotion(const QPointF &pos, uint32_t time) { if (!m_inited) { return; } updatePosition(pos); QMouseEvent event(QEvent::MouseMove, m_pos.toPoint(), m_pos.toPoint(), Qt::NoButton, m_qtButtons, m_input->keyboardModifiers()); event.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pointerEvent(&event, 0)) { return; } } } void PointerInputRedirection::processButton(uint32_t button, InputRedirection::PointerButtonState state, uint32_t time) { if (!m_inited) { return; } updateButton(button, state); QEvent::Type type; switch (state) { case InputRedirection::PointerButtonReleased: type = QEvent::MouseButtonRelease; break; case InputRedirection::PointerButtonPressed: type = QEvent::MouseButtonPress; break; default: Q_UNREACHABLE(); return; } QMouseEvent event(type, m_pos.toPoint(), m_pos.toPoint(), buttonToQtMouseButton(button), m_qtButtons, m_input->keyboardModifiers()); event.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->pointerEvent(&event, button)) { return; } } } void PointerInputRedirection::processAxis(InputRedirection::PointerAxis axis, qreal delta, uint32_t time) { if (!m_inited) { return; } if (delta == 0) { return; } emit m_input->pointerAxisChanged(axis, delta); QWheelEvent wheelEvent(m_pos, m_pos, QPoint(), (axis == InputRedirection::PointerAxisHorizontal) ? QPoint(delta, 0) : QPoint(0, delta), delta, (axis == InputRedirection::PointerAxisHorizontal) ? Qt::Horizontal : Qt::Vertical, m_qtButtons, m_input->keyboardModifiers()); wheelEvent.setTimestamp(time); const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->wheelEvent(&wheelEvent)) { return; } } } void PointerInputRedirection::update() { if (!m_inited) { return; } if (waylandServer()->seat()->isDragPointer()) { // ignore during drag and drop return; } // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(m_pos.toPoint()); const auto oldDeco = m_decoration; updateInternalWindow(); if (!m_internalWindow) { updateDecoration(t); } else { // TODO: send hover leave to decoration if (m_decoration) { m_decoration->client()->leaveEvent(); } m_decoration.clear(); } if (m_decoration || m_internalWindow) { t = nullptr; } if (m_decoration != oldDeco) { emit decorationChanged(); } auto oldWindow = m_window; if (!oldWindow.isNull() && t == m_window.data()) { return; } auto seat = waylandServer()->seat(); // disconnect old surface if (oldWindow) { if (AbstractClient *c = qobject_cast(oldWindow.data())) { c->leaveEvent(); } disconnect(m_windowGeometryConnection); m_windowGeometryConnection = QMetaObject::Connection(); } if (AbstractClient *c = qobject_cast(t)) { // only send enter if it wasn't on deco for the same client before if (m_decoration.isNull() || m_decoration->client() != c) { c->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } } if (t && t->surface()) { m_window = QPointer(t); seat->setFocusedPointerSurface(t->surface(), t->inputTransformation()); m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, [this] { if (m_window.isNull()) { return; } // TODO: can we check on the client instead? if (workspace()->getMovingClient()) { // don't update while moving return; } auto seat = waylandServer()->seat(); if (m_window.data()->surface() != seat->focusedPointerSurface()) { return; } seat->setFocusedPointerSurfaceTransformation(m_window.data()->inputTransformation()); } ); } else { m_window.clear(); seat->setFocusedPointerSurface(nullptr); t = nullptr; } } void PointerInputRedirection::updateInternalWindow() { const auto oldInternalWindow = m_internalWindow; bool found = false; // TODO: screen locked check without going through wayland server bool needsReset = waylandServer()->isScreenLocked(); const auto &internalClients = waylandServer()->internalClients(); const bool change = m_internalWindow.isNull() || !(m_internalWindow->flags().testFlag(Qt::Popup) && m_internalWindow->isVisible()); if (!internalClients.isEmpty() && change) { auto it = internalClients.end(); do { it--; if (QWindow *w = (*it)->internalWindow()) { if (!w->isVisible()) { continue; } if (w->geometry().contains(m_pos.toPoint())) { m_internalWindow = QPointer(w); found = true; break; } } } while (it != internalClients.begin()); if (!found) { needsReset = true; } } if (needsReset) { m_internalWindow.clear(); } if (oldInternalWindow != m_internalWindow) { // changed if (oldInternalWindow) { disconnect(m_internalWindowConnection); m_internalWindowConnection = QMetaObject::Connection(); QEvent event(QEvent::Leave); QCoreApplication::sendEvent(oldInternalWindow.data(), &event); } if (m_internalWindow) { m_internalWindowConnection = connect(m_internalWindow.data(), &QWindow::visibleChanged, this, [this] (bool visible) { if (!visible) { update(); } }); QEnterEvent event(m_pos - m_internalWindow->position(), m_pos - m_internalWindow->position(), m_pos); QCoreApplication::sendEvent(m_internalWindow.data(), &event); return; } } } void PointerInputRedirection::updateDecoration(Toplevel *t) { const auto oldDeco = m_decoration; bool needsReset = waylandServer()->isScreenLocked(); if (AbstractClient *c = dynamic_cast(t)) { // check whether it's on a Decoration if (c->decoratedClient()) { const QRect clientRect = QRect(c->clientPos(), c->clientSize()).translated(c->pos()); if (!clientRect.contains(m_pos.toPoint())) { m_decoration = c->decoratedClient(); } else { needsReset = true; } } else { needsReset = true; } } else { needsReset = true; } if (needsReset) { m_decoration.clear(); } bool leftSend = false; auto oldWindow = qobject_cast(m_window.data()); if (oldWindow && (m_decoration && m_decoration->client() != oldWindow)) { leftSend = true; oldWindow->leaveEvent(); } if (oldDeco && oldDeco != m_decoration) { if (oldDeco->client() != t && !leftSend) { leftSend = true; oldDeco->client()->leaveEvent(); } // send leave QHoverEvent event(QEvent::HoverLeave, QPointF(), QPointF()); QCoreApplication::instance()->sendEvent(oldDeco->decoration(), &event); } if (m_decoration) { if (m_decoration->client() != oldWindow) { m_decoration->client()->enterEvent(m_pos.toPoint()); workspace()->updateFocusMousePosition(m_pos.toPoint()); } const QPointF p = m_pos - t->pos(); QHoverEvent event(QEvent::HoverMove, p, p); QCoreApplication::instance()->sendEvent(m_decoration->decoration(), &event); m_decoration->client()->processDecorationMove(p.toPoint(), m_pos.toPoint()); } } void PointerInputRedirection::updatePosition(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_pos.x(), pos.y()); if (!screenContainsPos(p)) { p = QPointF(pos.x(), m_pos.y()); if (!screenContainsPos(p)) { return; } } } m_pos = p; emit m_input->globalPointerChanged(m_pos); } void PointerInputRedirection::updateButton(uint32_t button, InputRedirection::PointerButtonState state) { m_buttons[button] = state; // update Qt buttons m_qtButtons = Qt::NoButton; for (auto it = m_buttons.constBegin(); it != m_buttons.constEnd(); ++it) { if (it.value() == InputRedirection::PointerButtonReleased) { continue; } m_qtButtons |= buttonToQtMouseButton(it.key()); } emit m_input->pointerButtonStateChanged(button, state); } void PointerInputRedirection::warp(const QPointF &pos) { if (supportsWarping()) { kwinApp()->platform()->warpPointer(pos); processMotion(pos, waylandServer()->seat()->timestamp()); } } bool PointerInputRedirection::supportsWarping() const { if (!m_inited) { return false; } if (m_supportsWarping) { return true; } if (kwinApp()->platform()->supportsPointerWarping()) { return true; } return false; } void PointerInputRedirection::updateAfterScreenChange() { if (!m_inited) { return; } if (screenContainsPos(m_pos)) { // pointer still on a screen return; } // pointer no longer on a screen, reposition to closes screen const QPointF pos = screens()->geometry(screens()->number(m_pos.toPoint())).center(); // TODO: better way to get timestamps processMotion(pos, waylandServer()->seat()->timestamp()); } QImage PointerInputRedirection::cursorImage() const { if (!m_inited) { return QImage(); } return m_cursor->image(); } QPoint PointerInputRedirection::cursorHotSpot() const { if (!m_inited) { return QPoint(); } return m_cursor->hotSpot(); } void PointerInputRedirection::markCursorAsRendered() { if (!m_inited) { return; } m_cursor->markAsRendered(); } void PointerInputRedirection::setEffectsOverrideCursor(Qt::CursorShape shape) { if (!m_inited) { return; } // current pointer focus window should get a leave event update(); m_cursor->setEffectsOverrideCursor(shape); } void PointerInputRedirection::removeEffectsOverrideCursor() { if (!m_inited) { return; } // cursor position might have changed while there was an effect in place update(); m_cursor->removeEffectsOverrideCursor(); } CursorImage::CursorImage(PointerInputRedirection *parent) : QObject(parent) , m_pointer(parent) { connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::focusedPointerChanged, this, &CursorImage::update); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragStarted, this, &CursorImage::updateDrag); connect(waylandServer()->seat(), &KWayland::Server::SeatInterface::dragEnded, this, [this] { disconnect(m_drag.connection); reevaluteSource(); } ); - connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource); + if (waylandServer()->hasScreenLockerIntegration()) { + connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, &CursorImage::reevaluteSource); + } connect(m_pointer, &PointerInputRedirection::decorationChanged, this, &CursorImage::updateDecoration); // connect the move resize of all window auto setupMoveResizeConnection = [this] (AbstractClient *c) { connect(c, &AbstractClient::moveResizedChanged, this, &CursorImage::updateMoveResize); }; const auto clients = workspace()->allClientList(); std::for_each(clients.begin(), clients.end(), setupMoveResizeConnection); connect(workspace(), &Workspace::clientAdded, this, setupMoveResizeConnection); connect(waylandServer(), &WaylandServer::shellClientAdded, this, setupMoveResizeConnection); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); if (m_cursorTheme) { connect(m_cursorTheme, &WaylandCursorTheme::themeChanged, this, [this] { m_cursors.clear(); loadThemeCursor(Qt::ArrowCursor, &m_fallbackCursor); updateDecorationCursor(); updateMoveResize(); // TODO: update effects } ); } m_surfaceRenderedTimer.start(); } CursorImage::~CursorImage() = default; void CursorImage::markAsRendered() { if (m_currentSource == CursorSource::DragAndDrop) { // always sending a frame rendered to the drag icon surface to not freeze QtWayland (see https://bugreports.qt.io/browse/QTBUG-51599 ) if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto s = ddi->icon()) { s->frameRendered(m_surfaceRenderedTimer.elapsed()); } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); return; } if (m_currentSource != CursorSource::LockScreen && m_currentSource != CursorSource::PointerSurface) { return; } auto p = waylandServer()->seat()->focusedPointer(); if (!p) { return; } auto c = p->cursor(); if (!c) { return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { return; } cursorSurface->frameRendered(m_surfaceRenderedTimer.elapsed()); } void CursorImage::update() { using namespace KWayland::Server; disconnect(m_serverCursor.connection); auto p = waylandServer()->seat()->focusedPointer(); if (p) { m_serverCursor.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateServerCursor); } else { m_serverCursor.connection = QMetaObject::Connection(); } updateServerCursor(); } void CursorImage::updateDecoration() { disconnect(m_decorationConnection); auto deco = m_pointer->decoration(); AbstractClient *c = deco.isNull() ? nullptr : deco->client(); if (c) { m_decorationConnection = connect(c, &AbstractClient::moveResizeCursorChanged, this, &CursorImage::updateDecorationCursor); } else { m_decorationConnection = QMetaObject::Connection(); } updateDecorationCursor(); } void CursorImage::updateDecorationCursor() { m_decorationCursor.image = QImage(); m_decorationCursor.hotSpot = QPoint(); auto deco = m_pointer->decoration(); if (AbstractClient *c = deco.isNull() ? nullptr : deco->client()) { loadThemeCursor(c->cursor(), &m_decorationCursor); if (m_currentSource == CursorSource::Decoration) { emit changed(); } } reevaluteSource(); } void CursorImage::updateMoveResize() { m_moveResizeCursor.image = QImage(); m_moveResizeCursor.hotSpot = QPoint(); if (AbstractClient *c = workspace()->getMovingClient()) { loadThemeCursor(c->isMove() ? Qt::SizeAllCursor : Qt::SizeBDiagCursor, &m_moveResizeCursor); if (m_currentSource == CursorSource::MoveResize) { emit changed(); } } reevaluteSource(); } void CursorImage::updateServerCursor() { m_serverCursor.image = QImage(); m_serverCursor.hotSpot = QPoint(); reevaluteSource(); const bool needsEmit = m_currentSource == CursorSource::LockScreen || m_currentSource == CursorSource::PointerSurface; auto p = waylandServer()->seat()->focusedPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_serverCursor.hotSpot = c->hotspot(); m_serverCursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } } void CursorImage::loadTheme() { if (m_cursorTheme) { return; } // check whether we can create it if (waylandServer()->internalShmPool()) { m_cursorTheme = new WaylandCursorTheme(waylandServer()->internalShmPool(), this); connect(waylandServer(), &WaylandServer::terminatingInternalClientConnection, this, [this] { delete m_cursorTheme; m_cursorTheme = nullptr; } ); } } void CursorImage::setEffectsOverrideCursor(Qt::CursorShape shape) { loadThemeCursor(shape, &m_effectsCursor); if (m_currentSource == CursorSource::EffectsOverride) { emit changed(); } reevaluteSource(); } void CursorImage::removeEffectsOverrideCursor() { reevaluteSource(); } void CursorImage::updateDrag() { using namespace KWayland::Server; disconnect(m_drag.connection); m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); reevaluteSource(); if (auto p = waylandServer()->seat()->dragPointer()) { m_drag.connection = connect(p, &PointerInterface::cursorChanged, this, &CursorImage::updateDragCursor); } else { m_drag.connection = QMetaObject::Connection(); } updateDragCursor(); } void CursorImage::updateDragCursor() { m_drag.cursor.image = QImage(); m_drag.cursor.hotSpot = QPoint(); const bool needsEmit = m_currentSource == CursorSource::DragAndDrop; QImage additionalIcon; if (auto ddi = waylandServer()->seat()->dragSource()) { if (auto dragIcon = ddi->icon()) { if (auto buffer = dragIcon->buffer()) { additionalIcon = buffer->data().copy(); } } } auto p = waylandServer()->seat()->dragPointer(); if (!p) { if (needsEmit) { emit changed(); } return; } auto c = p->cursor(); if (!c) { if (needsEmit) { emit changed(); } return; } auto cursorSurface = c->surface(); if (cursorSurface.isNull()) { if (needsEmit) { emit changed(); } return; } auto buffer = cursorSurface.data()->buffer(); if (!buffer) { if (needsEmit) { emit changed(); } return; } m_drag.cursor.hotSpot = c->hotspot(); m_drag.cursor.image = buffer->data().copy(); if (needsEmit) { emit changed(); } // TODO: add the cursor image } void CursorImage::loadThemeCursor(Qt::CursorShape shape, Image *image) { loadTheme(); if (!m_cursorTheme) { return; } auto it = m_cursors.constFind(shape); if (it == m_cursors.constEnd()) { image->image = QImage(); image->hotSpot = QPoint(); wl_cursor_image *cursor = m_cursorTheme->get(shape); if (!cursor) { return; } wl_buffer *b = wl_cursor_image_get_buffer(cursor); if (!b) { return; } waylandServer()->internalClientConection()->flush(); waylandServer()->dispatch(); auto buffer = KWayland::Server::BufferInterface::get(waylandServer()->internalConnection()->getResource(KWayland::Client::Buffer::getId(b))); if (!buffer) { return; } it = decltype(it)(m_cursors.insert(shape, {buffer->data().copy(), QPoint(cursor->hotspot_x, cursor->hotspot_y)})); } image->hotSpot = it.value().hotSpot; image->image = it.value().image; } void CursorImage::reevaluteSource() { if (waylandServer()->seat()->isDragPointer()) { // TODO: touch drag? setSource(CursorSource::DragAndDrop); return; } if (waylandServer()->isScreenLocked()) { setSource(CursorSource::LockScreen); return; } if (effects && static_cast(effects)->isMouseInterception()) { setSource(CursorSource::EffectsOverride); return; } if (workspace() && workspace()->getMovingClient()) { setSource(CursorSource::MoveResize); return; } if (!m_pointer->decoration().isNull()) { setSource(CursorSource::Decoration); return; } if (!m_pointer->window().isNull()) { setSource(CursorSource::PointerSurface); return; } setSource(CursorSource::Fallback); } void CursorImage::setSource(CursorSource source) { if (m_currentSource == source) { return; } m_currentSource = source; emit changed(); } QImage CursorImage::image() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.image; case CursorSource::MoveResize: return m_moveResizeCursor.image; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.image; case CursorSource::Decoration: return m_decorationCursor.image; case CursorSource::DragAndDrop: return m_drag.cursor.image; case CursorSource::Fallback: return m_fallbackCursor.image; default: Q_UNREACHABLE(); } } QPoint CursorImage::hotSpot() const { switch (m_currentSource) { case CursorSource::EffectsOverride: return m_effectsCursor.hotSpot; case CursorSource::MoveResize: return m_moveResizeCursor.hotSpot; case CursorSource::LockScreen: case CursorSource::PointerSurface: // lockscreen also uses server cursor image return m_serverCursor.hotSpot; case CursorSource::Decoration: return m_decorationCursor.hotSpot; case CursorSource::DragAndDrop: return m_drag.cursor.hotSpot; case CursorSource::Fallback: return m_fallbackCursor.hotSpot; default: Q_UNREACHABLE(); } } } diff --git a/touch_input.cpp b/touch_input.cpp index fb01a47c9..ec4f99cb1 100644 --- a/touch_input.cpp +++ b/touch_input.cpp @@ -1,174 +1,176 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013, 2016 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "touch_input.h" #include "input.h" #include "toplevel.h" #include "wayland_server.h" #include "workspace.h" // KWayland #include // screenlocker #include namespace KWin { TouchInputRedirection::TouchInputRedirection(InputRedirection *parent) : QObject(parent) , m_input(parent) { } TouchInputRedirection::~TouchInputRedirection() = default; void TouchInputRedirection::init() { Q_ASSERT(!m_inited); m_inited = true; - connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, - [this] { - cancel(); - // position doesn't matter - update(); - } - ); + if (waylandServer()->hasScreenLockerIntegration()) { + connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged, this, + [this] { + cancel(); + // position doesn't matter + update(); + } + ); + } connect(workspace(), &QObject::destroyed, this, [this] { m_inited = false; }); connect(waylandServer(), &QObject::destroyed, this, [this] { m_inited = false; }); } void TouchInputRedirection::update(const QPointF &pos) { if (!m_inited) { return; } // TODO: handle pointer grab aka popups Toplevel *t = m_input->findToplevel(pos.toPoint()); auto oldWindow = m_window; if (!oldWindow.isNull() && t == oldWindow.data()) { return; } auto seat = waylandServer()->seat(); // disconnect old surface if (oldWindow) { disconnect(m_windowGeometryConnection); m_windowGeometryConnection = QMetaObject::Connection(); } if (t && t->surface()) { seat->setFocusedTouchSurface(t->surface(), t->pos()); m_windowGeometryConnection = connect(t, &Toplevel::geometryChanged, this, [this] { if (m_window.isNull()) { return; } auto seat = waylandServer()->seat(); if (m_window.data()->surface() != seat->focusedTouchSurface()) { return; } seat->setFocusedTouchSurfacePosition(m_window.data()->pos()); } ); } else { seat->setFocusedTouchSurface(nullptr); t = nullptr; } if (!t) { m_window.clear(); return; } m_window = QPointer(t); } void TouchInputRedirection::insertId(quint32 internalId, qint32 kwaylandId) { m_idMapper.insert(internalId, kwaylandId); } qint32 TouchInputRedirection::mappedId(quint32 internalId) { auto it = m_idMapper.constFind(internalId); if (it != m_idMapper.constEnd()) { return it.value(); } return -1; } void TouchInputRedirection::removeId(quint32 internalId) { m_idMapper.remove(internalId); } void TouchInputRedirection::processDown(qint32 id, const QPointF &pos, quint32 time) { if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->touchDown(id, pos, time)) { return; } } } void TouchInputRedirection::processUp(qint32 id, quint32 time) { if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->touchUp(id, time)) { return; } } } void TouchInputRedirection::processMotion(qint32 id, const QPointF &pos, quint32 time) { if (!m_inited) { return; } const auto &filters = m_input->filters(); for (auto it = filters.begin(), end = filters.end(); it != end; it++) { if ((*it)->touchMotion(id, pos, time)) { return; } } } void TouchInputRedirection::cancel() { if (!m_inited) { return; } waylandServer()->seat()->cancelTouchSequence(); m_idMapper.clear(); } void TouchInputRedirection::frame() { if (!m_inited) { return; } waylandServer()->seat()->touchFrame(); } } diff --git a/wayland_server.cpp b/wayland_server.cpp index cac929c53..ff2e64e2a 100644 --- a/wayland_server.cpp +++ b/wayland_server.cpp @@ -1,518 +1,528 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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 "wayland_server.h" #include "client.h" #include "platform.h" #include "composite.h" #include "screens.h" #include "shell_client.h" #include "workspace.h" // Client #include #include #include #include #include // Server #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include // system #include #include //screenlocker #include using namespace KWayland::Server; namespace KWin { KWIN_SINGLETON_FACTORY(WaylandServer) WaylandServer::WaylandServer(QObject *parent) : QObject(parent) { qRegisterMetaType("KWayland::Server::SurfaceInterface *"); qRegisterMetaType(); } WaylandServer::~WaylandServer() { destroyInputMethodConnection(); } void WaylandServer::destroyInternalConnection() { emit terminatingInternalClientConnection(); if (m_internalConnection.client) { delete m_internalConnection.registry; delete m_internalConnection.shm; dispatch(); m_internalConnection.client->deleteLater(); m_internalConnection.clientThread->quit(); m_internalConnection.clientThread->wait(); delete m_internalConnection.clientThread; m_internalConnection.client = nullptr; m_internalConnection.server->destroy(); } } void WaylandServer::terminateClientConnections() { destroyInternalConnection(); destroyInputMethodConnection(); if (m_display) { const auto connections = m_display->connections(); for (auto it = connections.begin(); it != connections.end(); ++it) { (*it)->destroy(); } } } void WaylandServer::init(const QByteArray &socketName, InitalizationFlags flags) { m_initFlags = flags; m_display = new KWayland::Server::Display(this); if (!socketName.isNull() && !socketName.isEmpty()) { m_display->setSocketName(QString::fromUtf8(socketName)); } m_display->start(); m_compositor = m_display->createCompositor(m_display); m_compositor->create(); connect(m_compositor, &CompositorInterface::surfaceCreated, this, [this] (SurfaceInterface *surface) { // check whether we have a Toplevel with the Surface's id Workspace *ws = Workspace::self(); if (!ws) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() != xWaylandConnection()) { // setting surface is only relevat for Xwayland clients return; } auto check = [surface] (const Toplevel *t) { return t->surfaceId() == surface->id(); }; if (Toplevel *t = ws->findToplevel(check)) { t->setSurface(surface); } } ); m_shell = m_display->createShell(m_display); m_shell->create(); connect(m_shell, &ShellInterface::surfaceCreated, this, [this] (ShellSurfaceInterface *surface) { if (!Workspace::self()) { // it's possible that a Surface gets created before Workspace is created return; } if (surface->client() == m_xwayland.client) { // skip Xwayland clients, those are created using standard X11 way return; } if (surface->client() == m_screenLockerClientConnection) { ScreenLocker::KSldApp::self()->lockScreenShown(); } auto client = new ShellClient(surface); if (auto c = Compositor::self()) { connect(client, &Toplevel::needsRepaint, c, &Compositor::scheduleRepaint); } if (client->isInternal()) { m_internalClients << client; } else { m_clients << client; } if (client->readyForPainting()) { emit shellClientAdded(client); } else { connect(client, &ShellClient::windowShown, this, [this, client] { emit shellClientAdded(client); } ); } } ); m_display->createShm(); m_seat = m_display->createSeat(m_display); m_seat->create(); m_display->createDataDeviceManager(m_display)->create(); m_display->createIdle(m_display)->create(); m_plasmaShell = m_display->createPlasmaShell(m_display); m_plasmaShell->create(); connect(m_plasmaShell, &PlasmaShellInterface::surfaceCreated, [this] (PlasmaShellSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installPlasmaShellSurface(surface); } } ); m_qtExtendedSurface = m_display->createQtSurfaceExtension(m_display); m_qtExtendedSurface->create(); connect(m_qtExtendedSurface, &QtSurfaceExtensionInterface::surfaceCreated, [this] (QtExtendedSurfaceInterface *surface) { if (ShellClient *client = findClient(surface->surface())) { client->installQtExtendedSurface(surface); } } ); m_windowManagement = m_display->createPlasmaWindowManagement(m_display); m_windowManagement->create(); m_windowManagement->setShowingDesktopState(PlasmaWindowManagementInterface::ShowingDesktopState::Disabled); connect(m_windowManagement, &PlasmaWindowManagementInterface::requestChangeShowingDesktop, this, [] (PlasmaWindowManagementInterface::ShowingDesktopState state) { if (!workspace()) { return; } bool set = false; switch (state) { case PlasmaWindowManagementInterface::ShowingDesktopState::Disabled: set = false; break; case PlasmaWindowManagementInterface::ShowingDesktopState::Enabled: set = true; break; default: Q_UNREACHABLE(); break; } if (set == workspace()->showingDesktop()) { return; } workspace()->setShowingDesktop(set); } ); auto shadowManager = m_display->createShadowManager(m_display); shadowManager->create(); m_display->createDpmsManager(m_display)->create(); m_decorationManager = m_display->createServerSideDecorationManager(m_display); connect(m_decorationManager, &ServerSideDecorationManagerInterface::decorationCreated, this, [this] (ServerSideDecorationInterface *deco) { if (ShellClient *c = findClient(deco->surface())) { c->installServerSideDecoration(deco); } } ); m_decorationManager->create(); m_outputManagement = m_display->createOutputManagement(m_display); connect(m_outputManagement, &OutputManagementInterface::configurationChangeRequested, this, [this](KWayland::Server::OutputConfigurationInterface *config) { kwinApp()->platform()->configurationChangeRequested(config); }); m_display->createSubCompositor(m_display)->create(); } void WaylandServer::initWorkspace() { if (m_windowManagement) { connect(workspace(), &Workspace::showingDesktopChanged, this, [this] (bool set) { using namespace KWayland::Server; m_windowManagement->setShowingDesktopState(set ? PlasmaWindowManagementInterface::ShowingDesktopState::Enabled : PlasmaWindowManagementInterface::ShowingDesktopState::Disabled ); } ); } - ScreenLocker::KSldApp::self(); - ScreenLocker::KSldApp::self()->setWaylandDisplay(m_display); - ScreenLocker::KSldApp::self()->setGreeterEnvironment(kwinApp()->processStartupEnvironment()); - ScreenLocker::KSldApp::self()->initialize(); - - connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::greeterClientConnectionChanged, this, - [this] () { - m_screenLockerClientConnection = ScreenLocker::KSldApp::self()->greeterClientConnection(); - } - ); + if (hasScreenLockerIntegration()) { + ScreenLocker::KSldApp::self(); + ScreenLocker::KSldApp::self()->setWaylandDisplay(m_display); + ScreenLocker::KSldApp::self()->setGreeterEnvironment(kwinApp()->processStartupEnvironment()); + ScreenLocker::KSldApp::self()->initialize(); + + connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::greeterClientConnectionChanged, this, + [this] () { + m_screenLockerClientConnection = ScreenLocker::KSldApp::self()->greeterClientConnection(); + } + ); - connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::unlocked, this, - [this] () { - m_screenLockerClientConnection = nullptr; - } - ); + connect(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::unlocked, this, + [this] () { + m_screenLockerClientConnection = nullptr; + } + ); - if (m_initFlags.testFlag(InitalizationFlag::LockScreen)) { - ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); + if (m_initFlags.testFlag(InitalizationFlag::LockScreen)) { + ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); + } } } void WaylandServer::initOutputs() { if (kwinApp()->platform()->handlesOutputs()) { return; } Screens *s = screens(); Q_ASSERT(s); for (int i = 0; i < s->count(); ++i) { OutputInterface *output = m_display->createOutput(m_display); const QRect &geo = s->geometry(i); output->setGlobalPosition(geo.topLeft()); output->setPhysicalSize(geo.size() / 3.8); output->addMode(geo.size()); output->create(); } } int WaylandServer::createXWaylandConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return -1; } m_xwayland.client = m_display->createClient(sx[0]); m_xwayland.destroyConnection = connect(m_xwayland.client, &KWayland::Server::ClientConnection::disconnected, this, [] { qFatal("Xwayland Connection died"); } ); return sx[1]; } void WaylandServer::destroyXWaylandConnection() { if (!m_xwayland.client) { return; } disconnect(m_xwayland.destroyConnection); m_xwayland.client->destroy(); m_xwayland.client = nullptr; } int WaylandServer::createInputMethodConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return -1; } m_inputMethodServerConnection = m_display->createClient(sx[0]); return sx[1]; } void WaylandServer::destroyInputMethodConnection() { if (!m_inputMethodServerConnection) { return; } m_inputMethodServerConnection->destroy(); m_inputMethodServerConnection = nullptr; } void WaylandServer::createInternalConnection() { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_CORE) << "Could not create socket"; return; } m_internalConnection.server = m_display->createClient(sx[0]); using namespace KWayland::Client; m_internalConnection.client = new ConnectionThread(); m_internalConnection.client->setSocketFd(sx[1]); m_internalConnection.clientThread = new QThread; m_internalConnection.client->moveToThread(m_internalConnection.clientThread); m_internalConnection.clientThread->start(); connect(m_internalConnection.client, &ConnectionThread::connected, this, [this] { Registry *registry = new Registry(this); EventQueue *eventQueue = new EventQueue(this); eventQueue->setup(m_internalConnection.client); registry->setEventQueue(eventQueue); registry->create(m_internalConnection.client); m_internalConnection.registry = registry; connect(registry, &Registry::shmAnnounced, this, [this] (quint32 name, quint32 version) { m_internalConnection.shm = m_internalConnection.registry->createShmPool(name, version, this); } ); registry->setup(); } ); m_internalConnection.client->initConnection(); } void WaylandServer::removeClient(ShellClient *c) { m_clients.removeAll(c); m_internalClients.removeAll(c); emit shellClientRemoved(c); } void WaylandServer::dispatch() { if (!m_display) { return; } if (m_internalConnection.server) { m_internalConnection.server->flush(); } m_display->dispatchEvents(0); } static ShellClient *findClientInList(const QList &clients, quint32 id) { auto it = std::find_if(clients.begin(), clients.end(), [id] (ShellClient *c) { return c->windowId() == id; } ); if (it == clients.end()) { return nullptr; } return *it; } static ShellClient *findClientInList(const QList &clients, KWayland::Server::SurfaceInterface *surface) { auto it = std::find_if(clients.begin(), clients.end(), [surface] (ShellClient *c) { return c->surface() == surface; } ); if (it == clients.end()) { return nullptr; } return *it; } ShellClient *WaylandServer::findClient(quint32 id) const { if (id == 0) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, id)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, id)) { return c; } return nullptr; } ShellClient *WaylandServer::findClient(SurfaceInterface *surface) const { if (!surface) { return nullptr; } if (ShellClient *c = findClientInList(m_clients, surface)) { return c; } if (ShellClient *c = findClientInList(m_internalClients, surface)) { return c; } return nullptr; } ShellClient *WaylandServer::findClient(QWindow *w) const { if (!w) { return nullptr; } auto it = std::find_if(m_internalClients.constBegin(), m_internalClients.constEnd(), [w] (const ShellClient *c) { return c->internalWindow() == w; } ); if (it != m_internalClients.constEnd()) { return *it; } return nullptr; } quint32 WaylandServer::createWindowId(SurfaceInterface *surface) { auto it = m_clientIds.constFind(surface->client()); quint16 clientId = 0; if (it != m_clientIds.constEnd()) { clientId = it.value(); } else { clientId = createClientId(surface->client()); } Q_ASSERT(clientId != 0); quint32 id = clientId; // TODO: this does not prevent that two surfaces of same client get same id id = (id << 16) | (surface->id() & 0xFFFF); if (findClient(id)) { qCWarning(KWIN_CORE) << "Invalid client windowId generated:" << id; return 0; } return id; } quint16 WaylandServer::createClientId(ClientConnection *c) { auto ids = m_clientIds.values().toSet(); quint16 id = 1; if (!ids.isEmpty()) { for (quint16 i = ids.count() + 1; i >= 1 ; i--) { if (!ids.contains(i)) { id = i; break; } } } Q_ASSERT(!ids.contains(id)); m_clientIds.insert(c, id); connect(c, &ClientConnection::disconnected, this, [this] (ClientConnection *c) { m_clientIds.remove(c); } ); return id; } bool WaylandServer::isScreenLocked() const { + if (!hasScreenLockerIntegration()) { + return false; + } return ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::Locked || ScreenLocker::KSldApp::self()->lockState() == ScreenLocker::KSldApp::AcquiringLock; } +bool WaylandServer::hasScreenLockerIntegration() const +{ + return !m_initFlags.testFlag(InitalizationFlag::NoLockScreenIntegration); +} + } diff --git a/wayland_server.h b/wayland_server.h index 5f1918462..a44af9a29 100644 --- a/wayland_server.h +++ b/wayland_server.h @@ -1,199 +1,204 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2015 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_WAYLAND_SERVER_H #define KWIN_WAYLAND_SERVER_H #include #include class QThread; class QWindow; namespace KWayland { namespace Client { class ConnectionThread; class Registry; class ShmPool; class Surface; } namespace Server { class ClientConnection; class CompositorInterface; class Display; class ShellInterface; class SeatInterface; class ServerSideDecorationManagerInterface; class SurfaceInterface; class OutputInterface; class PlasmaShellInterface; class PlasmaWindowManagementInterface; class QtSurfaceExtensionInterface; class OutputManagementInterface; class OutputConfigurationInterface; } } namespace KWin { class ShellClient; class AbstractClient; class KWIN_EXPORT WaylandServer : public QObject { Q_OBJECT public: enum class InitalizationFlag { NoOptions = 0x0, - LockScreen = 0x1 + LockScreen = 0x1, + NoLockScreenIntegration = 0x2 }; Q_DECLARE_FLAGS(InitalizationFlags, InitalizationFlag) virtual ~WaylandServer(); void init(const QByteArray &socketName = QByteArray(), InitalizationFlags flags = InitalizationFlag::NoOptions); void initOutputs(); void terminateClientConnections(); KWayland::Server::Display *display() { return m_display; } KWayland::Server::CompositorInterface *compositor() { return m_compositor; } KWayland::Server::SeatInterface *seat() { return m_seat; } KWayland::Server::ShellInterface *shell() { return m_shell; } KWayland::Server::PlasmaWindowManagementInterface *windowManagement() { return m_windowManagement; } KWayland::Server::ServerSideDecorationManagerInterface *decorationManager() const { return m_decorationManager; } QList clients() const { return m_clients; } QList internalClients() const { return m_internalClients; } void removeClient(ShellClient *c); ShellClient *findClient(quint32 id) const; ShellClient *findClient(KWayland::Server::SurfaceInterface *surface) const; ShellClient *findClient(QWindow *w) const; /** * @returns file descriptor for Xwayland to connect to. **/ int createXWaylandConnection(); void destroyXWaylandConnection(); /** * @returns file descriptor to the input method server's socket. **/ int createInputMethodConnection(); void destroyInputMethodConnection(); /** * @returns true if screen is locked. **/ bool isScreenLocked() const; + /** + * @returns whether integration with KScreenLocker is available. + **/ + bool hasScreenLockerIntegration() const; void createInternalConnection(); void initWorkspace(); KWayland::Server::ClientConnection *xWaylandConnection() const { return m_xwayland.client; } KWayland::Server::ClientConnection *inputMethodConnection() const { return m_inputMethodServerConnection; } KWayland::Server::ClientConnection *internalConnection() const { return m_internalConnection.server; } KWayland::Server::ClientConnection *screenLockerClientConnection() const { return m_screenLockerClientConnection; } KWayland::Client::ShmPool *internalShmPool() { return m_internalConnection.shm; } KWayland::Client::ConnectionThread *internalClientConection() { return m_internalConnection.client; } KWayland::Client::Registry *internalClientRegistry() { return m_internalConnection.registry; } void dispatch(); quint32 createWindowId(KWayland::Server::SurfaceInterface *surface); Q_SIGNALS: void shellClientAdded(KWin::ShellClient*); void shellClientRemoved(KWin::ShellClient*); void terminatingInternalClientConnection(); private: quint16 createClientId(KWayland::Server::ClientConnection *c); void destroyInternalConnection(); void configurationChangeRequested(KWayland::Server::OutputConfigurationInterface *config); KWayland::Server::Display *m_display = nullptr; KWayland::Server::CompositorInterface *m_compositor = nullptr; KWayland::Server::SeatInterface *m_seat = nullptr; KWayland::Server::ShellInterface *m_shell = nullptr; KWayland::Server::PlasmaShellInterface *m_plasmaShell = nullptr; KWayland::Server::PlasmaWindowManagementInterface *m_windowManagement = nullptr; KWayland::Server::QtSurfaceExtensionInterface *m_qtExtendedSurface = nullptr; KWayland::Server::ServerSideDecorationManagerInterface *m_decorationManager = nullptr; KWayland::Server::OutputManagementInterface *m_outputManagement = nullptr; struct { KWayland::Server::ClientConnection *client = nullptr; QMetaObject::Connection destroyConnection; } m_xwayland; KWayland::Server::ClientConnection *m_inputMethodServerConnection = nullptr; KWayland::Server::ClientConnection *m_screenLockerClientConnection = nullptr; struct { KWayland::Server::ClientConnection *server = nullptr; KWayland::Client::ConnectionThread *client = nullptr; QThread *clientThread = nullptr; KWayland::Client::Registry *registry = nullptr; KWayland::Client::ShmPool *shm = nullptr; } m_internalConnection; QList m_clients; QList m_internalClients; QHash m_clientIds; InitalizationFlags m_initFlags; KWIN_SINGLETON(WaylandServer) }; inline WaylandServer *waylandServer() { return WaylandServer::self(); } } // namespace KWin #endif