/******************************************************************** 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 "cursor.h" // kwin #include #include "input.h" #include "main.h" #include "utils.h" // KDE #include #include #include // Qt #include // Xlib #include #include // xcb #include namespace KWin { Cursor *Cursor::s_self = nullptr; Cursor *Cursor::create(QObject *parent) { Q_ASSERT(!s_self); #ifndef KCMRULES if (kwinApp()->operationMode() == Application::OperationModeX11) { s_self = new X11Cursor(parent); } else { s_self = new InputRedirectionCursor(parent); } #else Q_UNUSED(parent) #endif return s_self; } Cursor::Cursor(QObject *parent) : QObject(parent) , m_mousePollingCounter(0) , m_cursorTrackingCounter(0) , m_themeName("default") , m_themeSize(24) { loadThemeSettings(); // TODO: we need to connect for cursor theme changes // in KDE4 times this was done through KGlobaSettings::cursorChanged // which got emitted from the cursors KCM, this needs porting } Cursor::~Cursor() { s_self = NULL; } void Cursor::loadThemeSettings() { KConfigGroup mousecfg(KSharedConfig::openConfig("kcminputrc", KConfig::NoGlobals), "Mouse"); m_themeName = mousecfg.readEntry("cursorTheme", "default"); bool ok = false; m_themeSize = mousecfg.readEntry("cursorSize", QString("24")).toUInt(&ok); if (!ok) { m_themeSize = 24; } emit themeChanged(); } QPoint Cursor::pos() { s_self->doGetPos(); return s_self->m_pos; } void Cursor::setPos(const QPoint &pos) { // first query the current pos to not warp to the already existing pos if (pos == Cursor::pos()) { return; } s_self->m_pos = pos; s_self->doSetPos(); } void Cursor::setPos(int x, int y) { Cursor::setPos(QPoint(x, y)); } xcb_cursor_t Cursor::getX11Cursor(Qt::CursorShape shape) { Q_UNUSED(shape) return XCB_CURSOR_NONE; } xcb_cursor_t Cursor::x11Cursor(Qt::CursorShape shape) { return s_self->getX11Cursor(shape); } void Cursor::doSetPos() { emit posChanged(m_pos); } void Cursor::doGetPos() { } void Cursor::updatePos(const QPoint &pos) { if (m_pos == pos) { return; } m_pos = pos; emit posChanged(m_pos); } void Cursor::startMousePolling() { ++m_mousePollingCounter; if (m_mousePollingCounter == 1) { doStartMousePolling(); } } void Cursor::stopMousePolling() { Q_ASSERT(m_mousePollingCounter > 0); --m_mousePollingCounter; if (m_mousePollingCounter == 0) { doStopMousePolling(); } } void Cursor::doStartMousePolling() { } void Cursor::doStopMousePolling() { } void Cursor::startCursorTracking() { ++m_cursorTrackingCounter; if (m_cursorTrackingCounter == 1) { doStartCursorTracking(); } } void Cursor::stopCursorTracking() { Q_ASSERT(m_cursorTrackingCounter > 0); --m_cursorTrackingCounter; if (m_cursorTrackingCounter == 0) { doStopCursorTracking(); } } void Cursor::doStartCursorTracking() { } void Cursor::doStopCursorTracking() { } void Cursor::notifyCursorChanged(uint32_t serial) { if (m_cursorTrackingCounter <= 0) { // cursor change tracking is currently disabled, so don't emit signal return; } emit cursorChanged(serial); } X11Cursor::X11Cursor(QObject *parent) : Cursor(parent) , m_timeStamp(XCB_TIME_CURRENT_TIME) , m_buttonMask(0) , m_resetTimeStampTimer(new QTimer(this)) , m_mousePollingTimer(new QTimer(this)) { m_resetTimeStampTimer->setSingleShot(true); connect(m_resetTimeStampTimer, SIGNAL(timeout()), SLOT(resetTimeStamp())); // TODO: How often do we really need to poll? m_mousePollingTimer->setInterval(50); connect(m_mousePollingTimer, SIGNAL(timeout()), SLOT(mousePolled())); } X11Cursor::~X11Cursor() { } void X11Cursor::doSetPos() { const QPoint &pos = currentPos(); xcb_warp_pointer(connection(), XCB_WINDOW_NONE, rootWindow(), 0, 0, 0, 0, pos.x(), pos.y()); // call default implementation to emit signal Cursor::doSetPos(); } void X11Cursor::doGetPos() { if (m_timeStamp != XCB_TIME_CURRENT_TIME && m_timeStamp == QX11Info::appTime()) { // time stamps did not change, no need to query again return; } m_timeStamp = QX11Info::appTime(); ScopedCPointer pointer(xcb_query_pointer_reply(connection(), xcb_query_pointer_unchecked(connection(), rootWindow()), NULL)); if (!pointer) { return; } m_buttonMask = pointer->mask; updatePos(pointer->root_x, pointer->root_y); m_resetTimeStampTimer->start(0); } void X11Cursor::resetTimeStamp() { m_timeStamp = XCB_TIME_CURRENT_TIME; } void X11Cursor::doStartMousePolling() { m_mousePollingTimer->start(); } void X11Cursor::doStopMousePolling() { m_mousePollingTimer->stop(); } void X11Cursor::doStartCursorTracking() { xcb_xfixes_select_cursor_input(connection(), rootWindow(), XCB_XFIXES_CURSOR_NOTIFY_MASK_DISPLAY_CURSOR); } void X11Cursor::doStopCursorTracking() { xcb_xfixes_select_cursor_input(connection(), rootWindow(), 0); } void X11Cursor::mousePolled() { static QPoint lastPos = currentPos(); static uint16_t lastMask = m_buttonMask; doGetPos(); // Update if needed if (lastPos != currentPos() || lastMask != m_buttonMask) { emit mouseChanged(currentPos(), lastPos, x11ToQtMouseButtons(m_buttonMask), x11ToQtMouseButtons(lastMask), x11ToQtKeyboardModifiers(m_buttonMask), x11ToQtKeyboardModifiers(lastMask)); lastPos = currentPos(); lastMask = m_buttonMask; } } xcb_cursor_t X11Cursor::getX11Cursor(Qt::CursorShape shape) { QHash::const_iterator it = m_cursors.constFind(shape); if (it != m_cursors.constEnd()) { return it.value(); } return createCursor(shape); } xcb_cursor_t X11Cursor::createCursor(Qt::CursorShape shape) { const QByteArray name = cursorName(shape); if (name.isEmpty()) { return XCB_CURSOR_NONE; } // XCursor is an XLib only lib XcursorImage *ximg = XcursorLibraryLoadImage(name.constData(), themeName().toUtf8().constData(), themeSize()); if (!ximg) { return XCB_CURSOR_NONE; } xcb_cursor_t cursor = XcursorImageLoadCursor(display(), ximg); XcursorImageDestroy(ximg); m_cursors.insert(shape, cursor); return cursor; } QByteArray Cursor::cursorName(Qt::CursorShape shape) const { switch (shape) { case Qt::ArrowCursor: return QByteArray("left_ptr"); case Qt::UpArrowCursor: return QByteArray("up_arrow"); case Qt::CrossCursor: return QByteArray("cross"); case Qt::WaitCursor: return QByteArray("wait"); case Qt::IBeamCursor: return QByteArray("ibeam"); case Qt::SizeVerCursor: return QByteArray("size_ver"); case Qt::SizeHorCursor: return QByteArray("size_hor"); case Qt::SizeBDiagCursor: return QByteArray("size_bdiag"); case Qt::SizeFDiagCursor: return QByteArray("size_fdiag"); case Qt::SizeAllCursor: return QByteArray("size_all"); case Qt::SplitVCursor: return QByteArray("split_v"); case Qt::SplitHCursor: return QByteArray("split_h"); case Qt::PointingHandCursor: return QByteArray("pointing_hand"); case Qt::ForbiddenCursor: return QByteArray("forbidden"); case Qt::OpenHandCursor: return QByteArray("openhand"); case Qt::ClosedHandCursor: return QByteArray("closedhand"); case Qt::WhatsThisCursor: return QByteArray("whats_this"); case Qt::BusyCursor: return QByteArray("left_ptr_watch"); case Qt::DragMoveCursor: return QByteArray("dnd-move"); case Qt::DragCopyCursor: return QByteArray("dnd-copy"); case Qt::DragLinkCursor: return QByteArray("dnd-link"); default: return QByteArray(); } } InputRedirectionCursor::InputRedirectionCursor(QObject *parent) : Cursor(parent) , m_oldButtons(Qt::NoButton) , m_currentButtons(Qt::NoButton) { connect(input(), SIGNAL(globalPointerChanged(QPointF)), SLOT(slotPosChanged(QPointF))); connect(input(), SIGNAL(pointerButtonStateChanged(uint32_t,InputRedirection::PointerButtonState)), SLOT(slotPointerButtonChanged())); #ifndef KCMRULES connect(input(), &InputRedirection::keyboardModifiersChanged, this, &InputRedirectionCursor::slotModifiersChanged); #endif } InputRedirectionCursor::~InputRedirectionCursor() { } void InputRedirectionCursor::doSetPos() { // no support for pointer warping - reset to true position slotPosChanged(input()->globalPointer()); } void InputRedirectionCursor::slotPosChanged(const QPointF &pos) { const QPoint oldPos = currentPos(); updatePos(pos.toPoint()); emit mouseChanged(pos.toPoint(), oldPos, m_currentButtons, m_oldButtons, input()->keyboardModifiers(), input()->keyboardModifiers()); } void InputRedirectionCursor::slotModifiersChanged(Qt::KeyboardModifiers mods, Qt::KeyboardModifiers oldMods) { emit mouseChanged(currentPos(), currentPos(), m_currentButtons, m_currentButtons, mods, oldMods); } void InputRedirectionCursor::slotPointerButtonChanged() { m_oldButtons = m_currentButtons; m_currentButtons = input()->qtButtonStates(); } void InputRedirectionCursor::doStartCursorTracking() { xcb_xfixes_select_cursor_input(connection(), rootWindow(), XCB_XFIXES_CURSOR_NOTIFY_MASK_DISPLAY_CURSOR); // TODO: also track the Wayland cursor } void InputRedirectionCursor::doStopCursorTracking() { xcb_xfixes_select_cursor_input(connection(), rootWindow(), 0); // TODO: also track the Wayland cursor } } // namespace