1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or
10(at your option) any later version.
11
12This program is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with this program. If not, see <http://www.gnu.org/licenses/>.
19*********************************************************************/
20
21#include "cursor.h"
22// kwin
23#include <kwinglobals.h>
24#include "utils.h"
25// Qt
26#include <QTimer>
27// Xlib
28#include <X11/Xcursor/Xcursor.h>
29#include <fixx11h.h>
30// xcb
31#include <xcb/xfixes.h>
32
33namespace KWin
34{
35
36KWIN_SINGLETON_FACTORY_FACTORED(Cursor, X11Cursor)
37
38Cursor::Cursor(QObject *parent)
39 : QObject(parent)
40 , m_mousePollingCounter(0)
41 , m_cursorTrackingCounter(0)
42{
43}
44
45Cursor::~Cursor()
46{
47 s_self = NULL;
48}
49
50QPoint Cursor::pos()
51{
52 s_self->doGetPos();
53 return s_self->m_pos;
54}
55
56void Cursor::setPos(const QPoint &pos)
57{
58 // first query the current pos to not warp to the already existing pos
59 if (pos == Cursor::pos()) {
60 return;
61 }
62 s_self->m_pos = pos;
63 s_self->doSetPos();
64}
65
66void Cursor::setPos(int x, int y)
67{
68 Cursor::setPos(QPoint(x, y));
69}
70
71xcb_cursor_t Cursor::getX11Cursor(Qt::CursorShape shape)
72{
73 Q_UNUSED(shape)
74 return XCB_CURSOR_NONE;
75}
76
77xcb_cursor_t Cursor::x11Cursor(Qt::CursorShape shape)
78{
79 return s_self->getX11Cursor(shape);
80}
81
82void Cursor::doSetPos()
83{
84 emit posChanged(m_pos);
85}
86
87void Cursor::doGetPos()
88{
89}
90
91void Cursor::updatePos(const QPoint &pos)
92{
93 if (m_pos == pos) {
94 return;
95 }
96 m_pos = pos;
97 emit posChanged(m_pos);
98}
99
100void Cursor::startMousePolling()
101{
102 ++m_mousePollingCounter;
103 if (m_mousePollingCounter == 1) {
104 doStartMousePolling();
105 }
106}
107
108void Cursor::stopMousePolling()
109{
110 Q_ASSERT(m_mousePollingCounter > 0);
111 --m_mousePollingCounter;
112 if (m_mousePollingCounter == 0) {
113 doStopMousePolling();
114 }
115}
116
117void Cursor::doStartMousePolling()
118{
119}
120
121void Cursor::doStopMousePolling()
122{
123}
124
125void Cursor::startCursorTracking()
126{
127 ++m_cursorTrackingCounter;
128 if (m_cursorTrackingCounter == 1) {
129 doStartCursorTracking();
130 }
131}
132
133void Cursor::stopCursorTracking()
134{
135 Q_ASSERT(m_cursorTrackingCounter > 0);
136 --m_cursorTrackingCounter;
137 if (m_cursorTrackingCounter == 0) {
138 doStopCursorTracking();
139 }
140}
141
142void Cursor::doStartCursorTracking()
143{
144}
145
146void Cursor::doStopCursorTracking()
147{
148}
149
150void Cursor::notifyCursorChanged(uint32_t serial)
151{
152 if (m_cursorTrackingCounter <= 0) {
153 // cursor change tracking is currently disabled, so don't emit signal
154 return;
155 }
156 emit cursorChanged(serial);
157}
158
159X11Cursor::X11Cursor(QObject *parent)
160 : Cursor(parent)
161 , m_timeStamp(XCB_TIME_CURRENT_TIME)
162 , m_buttonMask(0)
163 , m_resetTimeStampTimer(new QTimer(this))
164 , m_mousePollingTimer(new QTimer(this))
165{
166 m_resetTimeStampTimer->setSingleShot(true);
167 connect(m_resetTimeStampTimer, SIGNAL(timeout()), SLOT(resetTimeStamp()));
168 // TODO: How often do we really need to poll?
169 m_mousePollingTimer->setInterval(50);
170 connect(m_mousePollingTimer, SIGNAL(timeout()), SLOT(mousePolled()));
171}
172
173X11Cursor::~X11Cursor()
174{
175}
176
177void X11Cursor::doSetPos()
178{
179 const QPoint &pos = currentPos();
180 xcb_warp_pointer(connection(), XCB_WINDOW_NONE, rootWindow(), 0, 0, 0, 0, pos.x(), pos.y());
181 // call default implementation to emit signal
182 Cursor::doSetPos();
183}
184
185void X11Cursor::doGetPos()
186{
187 if (m_timeStamp != XCB_TIME_CURRENT_TIME &&
188 m_timeStamp == QX11Info::appTime()) {
189 // time stamps did not change, no need to query again
190 return;
191 }
192 m_timeStamp = QX11Info::appTime();
193 ScopedCPointer<xcb_query_pointer_reply_t> pointer(xcb_query_pointer_reply(connection(),
194 xcb_query_pointer_unchecked(connection(), rootWindow()), NULL));
195 if (!pointer) {
196 return;
197 }
198 m_buttonMask = pointer->mask;
199 updatePos(pointer->root_x, pointer->root_y);
200 m_resetTimeStampTimer->start(0);
201}
202
203void X11Cursor::resetTimeStamp()
204{
205 m_timeStamp = XCB_TIME_CURRENT_TIME;
206}
207
208void X11Cursor::doStartMousePolling()
209{
210 m_mousePollingTimer->start();
211}
212
213void X11Cursor::doStopMousePolling()
214{
215 m_mousePollingTimer->stop();
216}
217
218void X11Cursor::doStartCursorTracking()
219{
220 xcb_xfixes_select_cursor_input(connection(), rootWindow(), XCB_XFIXES_CURSOR_NOTIFY_MASK_DISPLAY_CURSOR);
221}
222
223void X11Cursor::doStopCursorTracking()
224{
225 xcb_xfixes_select_cursor_input(connection(), rootWindow(), 0);
226}
227
228void X11Cursor::mousePolled()
229{
230 static QPoint lastPos = currentPos();
231 static uint16_t lastMask = m_buttonMask;
232 doGetPos(); // Update if needed
233 if (lastPos != currentPos() || lastMask != m_buttonMask) {
234 emit mouseChanged(currentPos(), lastPos,
235 x11ToQtMouseButtons(m_buttonMask), x11ToQtMouseButtons(lastMask),
236 x11ToQtKeyboardModifiers(m_buttonMask), x11ToQtKeyboardModifiers(lastMask));
237 lastPos = currentPos();
238 lastMask = m_buttonMask;
239 }
240}
241
242xcb_cursor_t X11Cursor::getX11Cursor(Qt::CursorShape shape)
243{
244 QHash<Qt::CursorShape, xcb_cursor_t>::const_iterator it = m_cursors.constFind(shape);
245 if (it != m_cursors.constEnd()) {
246 return it.value();
247 }
248 return createCursor(shape);
249}
250
251xcb_cursor_t X11Cursor::createCursor(Qt::CursorShape shape)
252{
253 const QByteArray name = cursorName(shape);
254 if (name.isEmpty()) {
255 return XCB_CURSOR_NONE;
256 }
257 // XCursor is an XLib only lib
258 const char *theme = XcursorGetTheme(display());
259 const int size = XcursorGetDefaultSize(display());
260 XcursorImage *ximg = XcursorLibraryLoadImage(name.constData(), theme, size);
261 if (!ximg) {
262 return XCB_CURSOR_NONE;
263 }
264 xcb_cursor_t cursor = XcursorImageLoadCursor(display(), ximg);
265 XcursorImageDestroy(ximg);
266 m_cursors.insert(shape, cursor);
267 return cursor;
268}
269
270QByteArray X11Cursor::cursorName(Qt::CursorShape shape) const
271{
272 switch (shape) {
273 case Qt::ArrowCursor:
274 return QByteArray("left_ptr");
275 case Qt::UpArrowCursor:
276 return QByteArray("up_arrow");
277 case Qt::CrossCursor:
278 return QByteArray("cross");
279 case Qt::WaitCursor:
280 return QByteArray("wait");
281 case Qt::IBeamCursor:
282 return QByteArray("ibeam");
283 case Qt::SizeVerCursor:
284 return QByteArray("size_ver");
285 case Qt::SizeHorCursor:
286 return QByteArray("size_hor");
287 case Qt::SizeBDiagCursor:
288 return QByteArray("size_bdiag");
289 case Qt::SizeFDiagCursor:
290 return QByteArray("size_fdiag");
291 case Qt::SizeAllCursor:
292 return QByteArray("size_all");
293 case Qt::SplitVCursor:
294 return QByteArray("split_v");
295 case Qt::SplitHCursor:
296 return QByteArray("split_h");
297 case Qt::PointingHandCursor:
298 return QByteArray("pointing_hand");
299 case Qt::ForbiddenCursor:
300 return QByteArray("forbidden");
301 case Qt::OpenHandCursor:
302 return QByteArray("openhand");
303 case Qt::ClosedHandCursor:
304 return QByteArray("closedhand");
305 case Qt::WhatsThisCursor:
306 return QByteArray("whats_this");
307 case Qt::BusyCursor:
308 return QByteArray("left_ptr_watch");
309 case Qt::DragMoveCursor:
310 return QByteArray("dnd-move");
311 case Qt::DragCopyCursor:
312 return QByteArray("dnd-copy");
313 case Qt::DragLinkCursor:
314 return QByteArray("dnd-link");
315 default:
316 return QByteArray();
317 }
318}
319
320} // namespace
321