1 | /******************************************************************** |
2 | KWin - the KDE window manager |
3 | This file is part of the KDE project. |
4 | |
5 | Copyright (C) 2013 Martin Gräßlin <mgraesslin@kde.org> |
6 | |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License as published by |
9 | the Free Software Foundation; either version 2 of the License, or |
10 | (at your option) any later version. |
11 | |
12 | This program is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | GNU General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU General Public License |
18 | along 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 | |
33 | namespace KWin |
34 | { |
35 | |
36 | KWIN_SINGLETON_FACTORY_FACTORED(Cursor, X11Cursor) |
37 | |
38 | Cursor::Cursor(QObject *parent) |
39 | : QObject(parent) |
40 | , m_mousePollingCounter(0) |
41 | , m_cursorTrackingCounter(0) |
42 | { |
43 | } |
44 | |
45 | Cursor::~Cursor() |
46 | { |
47 | s_self = NULL; |
48 | } |
49 | |
50 | QPoint Cursor::pos() |
51 | { |
52 | s_self->doGetPos(); |
53 | return s_self->m_pos; |
54 | } |
55 | |
56 | void 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 | |
66 | void Cursor::setPos(int x, int y) |
67 | { |
68 | Cursor::setPos(QPoint(x, y)); |
69 | } |
70 | |
71 | xcb_cursor_t Cursor::getX11Cursor(Qt::CursorShape shape) |
72 | { |
73 | Q_UNUSED(shape) |
74 | return XCB_CURSOR_NONE; |
75 | } |
76 | |
77 | xcb_cursor_t Cursor::x11Cursor(Qt::CursorShape shape) |
78 | { |
79 | return s_self->getX11Cursor(shape); |
80 | } |
81 | |
82 | void Cursor::doSetPos() |
83 | { |
84 | emit posChanged(m_pos); |
85 | } |
86 | |
87 | void Cursor::doGetPos() |
88 | { |
89 | } |
90 | |
91 | void 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 | |
100 | void Cursor::startMousePolling() |
101 | { |
102 | ++m_mousePollingCounter; |
103 | if (m_mousePollingCounter == 1) { |
104 | doStartMousePolling(); |
105 | } |
106 | } |
107 | |
108 | void Cursor::stopMousePolling() |
109 | { |
110 | Q_ASSERT(m_mousePollingCounter > 0); |
111 | --m_mousePollingCounter; |
112 | if (m_mousePollingCounter == 0) { |
113 | doStopMousePolling(); |
114 | } |
115 | } |
116 | |
117 | void Cursor::doStartMousePolling() |
118 | { |
119 | } |
120 | |
121 | void Cursor::doStopMousePolling() |
122 | { |
123 | } |
124 | |
125 | void Cursor::startCursorTracking() |
126 | { |
127 | ++m_cursorTrackingCounter; |
128 | if (m_cursorTrackingCounter == 1) { |
129 | doStartCursorTracking(); |
130 | } |
131 | } |
132 | |
133 | void Cursor::stopCursorTracking() |
134 | { |
135 | Q_ASSERT(m_cursorTrackingCounter > 0); |
136 | --m_cursorTrackingCounter; |
137 | if (m_cursorTrackingCounter == 0) { |
138 | doStopCursorTracking(); |
139 | } |
140 | } |
141 | |
142 | void Cursor::doStartCursorTracking() |
143 | { |
144 | } |
145 | |
146 | void Cursor::doStopCursorTracking() |
147 | { |
148 | } |
149 | |
150 | void 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 | |
159 | X11Cursor::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 | |
173 | X11Cursor::~X11Cursor() |
174 | { |
175 | } |
176 | |
177 | void 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 | |
185 | void 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 | |
203 | void X11Cursor::resetTimeStamp() |
204 | { |
205 | m_timeStamp = XCB_TIME_CURRENT_TIME; |
206 | } |
207 | |
208 | void X11Cursor::doStartMousePolling() |
209 | { |
210 | m_mousePollingTimer->start(); |
211 | } |
212 | |
213 | void X11Cursor::doStopMousePolling() |
214 | { |
215 | m_mousePollingTimer->stop(); |
216 | } |
217 | |
218 | void X11Cursor::doStartCursorTracking() |
219 | { |
220 | xcb_xfixes_select_cursor_input(connection(), rootWindow(), XCB_XFIXES_CURSOR_NOTIFY_MASK_DISPLAY_CURSOR); |
221 | } |
222 | |
223 | void X11Cursor::doStopCursorTracking() |
224 | { |
225 | xcb_xfixes_select_cursor_input(connection(), rootWindow(), 0); |
226 | } |
227 | |
228 | void 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 | |
242 | xcb_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 | |
251 | xcb_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 | |
270 | QByteArray 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 | |