From 9bab40d995bf3c2d8b0b7c60e496fb3480695fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Sat, 26 Jan 2013 11:50:14 +0100 Subject: [PATCH] Notifications when approaching a screen edge For each edge an additional "approach" area window is created. When the mouse enters this approach window, it gets unmapped and a mouse polling interval is started. If the mouse leaves the approach area again, the window gets mapped again and the mouse polling is stopped. During the approaching a signal is emitted with a factor in [0.0,1.0] to describe how close the mouse is to the edge. 0.0 means far away, 1.0 means triggering the edge. This signal is passed to the effects to allow using this information. E.g. to provide a glow corner effect or to make use of it in the cube animation effect to start the animation on desktop switch. --- effects.cpp | 3 + libkwineffects/kwineffects.h | 12 ++ screenedge.cpp | 205 ++++++++++++++++++++++++++++++++++- screenedge.h | 50 ++++++++- 4 files changed, 263 insertions(+), 7 deletions(-) diff --git a/effects.cpp b/effects.cpp index 1f581fa667..5c6272c602 100644 --- a/effects.cpp +++ b/effects.cpp @@ -140,6 +140,9 @@ EffectsHandlerImpl::EffectsHandlerImpl(Compositor *compositor, Scene *scene) connect(ws->tabBox(), SIGNAL(tabBoxUpdated()), SIGNAL(tabBoxUpdated())); connect(ws->tabBox(), SIGNAL(tabBoxClosed()), SIGNAL(tabBoxClosed())); connect(ws->tabBox(), SIGNAL(tabBoxKeyEvent(QKeyEvent*)), SIGNAL(tabBoxKeyEvent(QKeyEvent*))); +#endif +#ifdef KWIN_BUILD_SCREENEDGES + connect(ScreenEdges::self(), SIGNAL(approaching(ElectricBorder,qreal,QRect)), SIGNAL(screenEdgeApproaching(ElectricBorder,qreal,QRect))); #endif // connect all clients foreach (Client *c, ws->clientList()) { diff --git a/libkwineffects/kwineffects.h b/libkwineffects/kwineffects.h index 31607c1648..7f0e790947 100644 --- a/libkwineffects/kwineffects.h +++ b/libkwineffects/kwineffects.h @@ -1162,6 +1162,18 @@ Q_SIGNALS: * @since 4.10 */ void stackingOrderChanged(); + /** + * This signal is emitted when the user starts to approach the @p border with the mouse. + * The @p factor describes how far away the mouse is in a relative mean. The values are in + * [0.0, 1.0] with 0.0 being emitted when first entered and on leaving. The value 1.0 means that + * the @p border is reached with the mouse. So the values are well suited for animations. + * The signal is always emitted when the mouse cursor position changes. + * @param border The screen edge which is being approached + * @param factor Value in range [0.0,1.0] to describe how close the mouse is to the border + * @param geometry The geometry of the edge which is being approached + * @since 4.11 + **/ + void screenEdgeApproaching(ElectricBorder border, qreal factor, const QRect &geometry); protected: QVector< EffectPair > loaded_effects; diff --git a/screenedge.cpp b/screenedge.cpp index 8b7837a0f2..0e300baa63 100644 --- a/screenedge.cpp +++ b/screenedge.cpp @@ -58,6 +58,8 @@ Edge::Edge(ScreenEdges *parent) , m_border(ElectricNone) , m_action(ElectricActionNone) , m_reserved(0) + , m_approaching(false) + , m_lastApproachingFactor(0.0) { } @@ -268,6 +270,52 @@ void Edge::pushCursorBack(const QPoint &cursorPos) QCursor::setPos(x, y); } +void Edge::setGeometry(const QRect &geometry) +{ + if (m_geometry == geometry) { + return; + } + m_geometry = geometry; + int x = m_geometry.x(); + int y = m_geometry.y(); + int width = m_geometry.width(); + int height = m_geometry.height(); + // TODO: better not hard coded value + const int size = 20; + if (isCorner()) { + if (isRight()) { + x = x - size +1; + } + if (isBottom()) { + y = y - size +1; + } + width = size; + height = size; + } else { + if (isLeft()) { + y += size + 1; + width = size; + height = height - size * 2; + } else if (isRight()) { + x = x - size + 1; + y += size; + width = size; + height = height - size * 2; + } else if (isTop()) { + x += size; + width = width - size * 2; + height = size; + } else if (isBottom()) { + x += size; + y = y - size +1; + width = width - size * 2; + height = size; + } + } + m_approachGeometry = QRect(x, y, width, height); + doGeometryUpdate(); +} + void Edge::doGeometryUpdate() { } @@ -280,12 +328,88 @@ void Edge::deactivate() { } +void Edge::startApproaching() +{ + if (m_approaching) { + return; + } + m_approaching = true; + doStartApproaching(); + m_lastApproachingFactor = 0.0; + emit approaching(border(), 0.0, m_approachGeometry); +} + +void Edge::doStartApproaching() +{ +} + +void Edge::stopApproaching() +{ + if (!m_approaching) { + return; + } + m_approaching = false; + doStopApproaching(); + m_lastApproachingFactor = 0.0; + emit approaching(border(), 0.0, m_approachGeometry); +} + +void Edge::doStopApproaching() +{ +} + +void Edge::updateApproaching(const QPoint &point) +{ + if (approachGeometry().contains(point)) { + qreal factor = 0.0; + // manhattan length for our edge + const qreal cornerDistance = 40.0; + const qreal edgeDistance = 20.0; + switch (border()) { + case ElectricTopLeft: + factor = point.manhattanLength() / cornerDistance; + break; + case ElectricTopRight: + factor = (point - approachGeometry().topRight()).manhattanLength() / cornerDistance; + break; + case ElectricBottomRight: + factor = (point - approachGeometry().bottomRight()).manhattanLength() / cornerDistance; + break; + case ElectricBottomLeft: + factor = (point - approachGeometry().bottomLeft()).manhattanLength() / cornerDistance; + break; + case ElectricTop: + factor = qAbs(point.y() - approachGeometry().y()) / edgeDistance; + break; + case ElectricRight: + factor = qAbs(point.x() - approachGeometry().right()) / edgeDistance; + break; + case ElectricBottom: + factor = qAbs(point.y() - approachGeometry().bottom()) / edgeDistance; + break; + case ElectricLeft: + factor = qAbs(point.x() - approachGeometry().x()) / edgeDistance; + break; + default: + break; + } + factor = 1.0 - factor; + if (m_lastApproachingFactor != factor) { + m_lastApproachingFactor = factor; + emit approaching(border(), m_lastApproachingFactor, m_approachGeometry); + } + } else { + stopApproaching(); + } +} + /********************************************************** * ScreenEdges *********************************************************/ WindowBasedEdge::WindowBasedEdge(ScreenEdges *parent) : Edge(parent) , m_window(XCB_WINDOW_NONE) + , m_approachWindow(XCB_WINDOW_NONE) { } @@ -297,6 +421,7 @@ WindowBasedEdge::~WindowBasedEdge() void WindowBasedEdge::activate() { createWindow(); + createApproachWindow(); } void WindowBasedEdge::deactivate() @@ -322,17 +447,56 @@ void WindowBasedEdge::createWindow() atoms->xdnd_aware, XCB_ATOM_ATOM, 32, 1, (unsigned char*)(&version)); } +void WindowBasedEdge::createApproachWindow() +{ + if (m_approachWindow != XCB_WINDOW_NONE) { + return; + } + if (!approachGeometry().isValid()) { + return; + } + const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK; + const uint32_t values[] = { + true, + XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW + }; + const QRect geo = approachGeometry(); + m_approachWindow = Xcb::createInputWindow(geo, mask, values); + xcb_map_window(connection(), m_approachWindow); +} + void WindowBasedEdge::destroyWindow() { if (m_window != XCB_WINDOW_NONE) { xcb_destroy_window(connection(), m_window); m_window = XCB_WINDOW_NONE; } + if (m_approachWindow != XCB_WINDOW_NONE) { + xcb_destroy_window(connection(), m_approachWindow); + m_approachWindow = XCB_WINDOW_NONE; + } } void WindowBasedEdge::doGeometryUpdate() { Xcb::moveResizeWindow(m_window, geometry()); + Xcb::moveResizeWindow(m_approachWindow, approachGeometry()); +} + +void WindowBasedEdge::doStartApproaching() +{ + xcb_unmap_window(connection(), m_approachWindow); + connect(edges(), SIGNAL(mousePollingTimerEvent(QPoint)), SLOT(updateApproaching(QPoint))); + edges()->startMousePolling(); +} + +void WindowBasedEdge::doStopApproaching() +{ + disconnect(edges(), SIGNAL(mousePollingTimerEvent(QPoint)), this, SLOT(updateApproaching(QPoint))); + edges()->stopMousePolling(); + if (m_approachWindow != XCB_WINDOW_NONE) { + xcb_map_window(connection(), m_approachWindow); + } } /********************************************************** @@ -362,7 +526,10 @@ ScreenEdges::ScreenEdges(QObject *parent) , m_actionBottom(ElectricActionNone) , m_actionBottomLeft(ElectricActionNone) , m_actionLeft(ElectricActionNone) + , m_mousePolling(0) + , m_mousePollingTimer(new QTimer(this)) { + connect(m_mousePollingTimer, SIGNAL(timeout()), SLOT(performMousePoll())); } ScreenEdges::~ScreenEdges() @@ -704,6 +871,7 @@ WindowBasedEdge *ScreenEdges::createEdge(ElectricBorder border, int x, int y, in } } } + connect(edge, SIGNAL(approaching(ElectricBorder,qreal,QRect)), SIGNAL(approaching(ElectricBorder,qreal,QRect))); return edge; } @@ -782,10 +950,18 @@ bool ScreenEdges::isEntered(XEvent* e) if (e->type == EnterNotify) { for (QList::iterator it = m_edges.begin(); it != m_edges.end(); ++it) { WindowBasedEdge *edge = *it; - if (edge->isReserved() && edge->window() == e->xcrossing.window) { + if (!edge->isReserved()) { + continue; + } + if (edge->window() == e->xcrossing.window) { edge->check(QPoint(e->xcrossing.x_root, e->xcrossing.y_root), QDateTime::fromMSecsSinceEpoch(e->xcrossing.time)); return true; } + if (edge->approachWindow() == e->xcrossing.window) { + edge->startApproaching(); + // TODO: if it's a corner, it should also trigger for other windows + return true; + } } } if (e->type == ClientMessage) { @@ -808,6 +984,28 @@ void ScreenEdges::ensureOnTop() Xcb::restackWindowsWithRaise(windows()); } +void ScreenEdges::startMousePolling() +{ + m_mousePolling++; + if (m_mousePolling == 1) { + m_mousePollingTimer->start(100); // TODO: How often do we really need to poll? + } +} + +void ScreenEdges::stopMousePolling() +{ + m_mousePolling--; + if (m_mousePolling == 0) { + m_mousePollingTimer->stop(); + } +} + +void ScreenEdges::performMousePoll() +{ + Workspace::self()->checkCursorPos(); + emit mousePollingTimerEvent(Workspace::self()->cursorPos()); +} + /* * NOTICE THIS IS A HACK * or at least a quite cumbersome way to handle conflictive electric borders @@ -882,6 +1080,11 @@ QVector< xcb_window_t > ScreenEdges::windows() const if (w != XCB_WINDOW_NONE) { wins.append(w); } + // TODO: lambda + w = (*it)->approachWindow(); + if (w != XCB_WINDOW_NONE) { + wins.append(w); + } } return wins; } diff --git a/screenedge.h b/screenedge.h index b6efe1ab6e..83e39dc15c 100644 --- a/screenedge.h +++ b/screenedge.h @@ -57,10 +57,13 @@ public: bool triggersFor(const QPoint &cursorPos) const; void check(const QPoint &cursorPos, const QDateTime &triggerTime, bool forceNoPushBack = false); bool isReserved() const; + const QRect &approachGeometry() const; ElectricBorder border() const; void reserve(QObject *object, const char *slot); const QHash &callBacks() const; + void startApproaching(); + void stopApproaching(); public Q_SLOTS: void reserve(); @@ -69,6 +72,9 @@ public Q_SLOTS: void setBorder(ElectricBorder border); void setAction(ElectricBorderAction action); void setGeometry(const QRect &geometry); + void updateApproaching(const QPoint &point); +Q_SIGNALS: + void approaching(ElectricBorder border, qreal factor, const QRect &geometry); protected: ScreenEdges *edges(); const ScreenEdges *edges() const; @@ -76,6 +82,8 @@ protected: virtual void doGeometryUpdate(); virtual void activate(); virtual void deactivate(); + virtual void doStartApproaching(); + virtual void doStopApproaching(); private: bool canActivate(const QPoint &cursorPos, const QDateTime &triggerTime); void handle(const QPoint &cursorPos); @@ -88,10 +96,13 @@ private: ElectricBorderAction m_action; int m_reserved; QRect m_geometry; + QRect m_approachGeometry; QDateTime m_lastTrigger; QDateTime m_lastReset; QPoint m_triggeredPoint; QHash m_callBacks; + bool m_approaching; + qreal m_lastApproachingFactor; }; class WindowBasedEdge : public Edge @@ -102,16 +113,25 @@ public: virtual ~WindowBasedEdge(); xcb_window_t window() const; + /** + * The approach window is a special window to notice when get close to the screen border but + * not yet triggering the border. + **/ + xcb_window_t approachWindow() const; protected: virtual void doGeometryUpdate(); virtual void activate(); virtual void deactivate(); + virtual void doStartApproaching(); + virtual void doStopApproaching(); private: void destroyWindow(); void createWindow(); + void createApproachWindow(); xcb_window_t m_window; + xcb_window_t m_approachWindow; }; /** @@ -261,6 +281,8 @@ public: ElectricBorderAction actionBottom() const; ElectricBorderAction actionBottomLeft() const; ElectricBorderAction actionLeft() const; + void startMousePolling(); + void stopMousePolling(); /** * Singleton getter for this manager. @@ -286,6 +308,19 @@ public Q_SLOTS: * Recreates all edges e.g. after the screen size changes. **/ void recreateEdges(); + +Q_SIGNALS: + /** + * Signal emitted during approaching of mouse towards @p border. The @p factor indicates how + * far away the mouse is from the approaching area. The values are clamped into [0.0,1.0] with + * @c 0.0 meaning far away from the border, @c 1.0 in trigger distance. + **/ + void approaching(ElectricBorder border, qreal factor, const QRect &geometry); + void mousePollingTimerEvent(QPoint cursorPos); + +private Q_SLOTS: + void performMousePoll(); + private: enum { ElectricDisabled = 0, ElectricMoveOnly = 1, ElectricAlways = 2 }; void setDesktopSwitching(bool enable); @@ -314,6 +349,8 @@ private: ElectricBorderAction m_actionBottom; ElectricBorderAction m_actionBottomLeft; ElectricBorderAction m_actionLeft; + int m_mousePolling; + QTimer *m_mousePollingTimer; static ScreenEdges *s_self; }; @@ -388,13 +425,9 @@ inline const QRect &Edge::geometry() const return m_geometry; } -inline void Edge::setGeometry(const QRect &geometry) +inline const QRect &Edge::approachGeometry() const { - if (m_geometry == geometry) { - return; - } - m_geometry = geometry; - doGeometryUpdate(); + return m_approachGeometry; } inline ElectricBorder Edge::border() const @@ -416,6 +449,11 @@ inline xcb_window_t WindowBasedEdge::window() const return m_window; } +inline xcb_window_t WindowBasedEdge::approachWindow() const +{ + return m_approachWindow; +} + /********************************************************** * Inlines ScreenEdges *********************************************************/