1 /*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "cursor.h"
11 // kwin
12 #include "input.h"
13 #include "keyboard_input.h"
14 #include "main.h"
15 #include "platform.h"
16 #include "utils.h"
17 #include "xcbutils.h"
18 // KDE
19 #include <KConfig>
20 #include <KConfigGroup>
21 // Qt
22 #include <QAbstractEventDispatcher>
23 #include <QDBusConnection>
24 #include <QScreen>
25 #include <QTimer>
26
27 #include <xcb/xcb_cursor.h>
28
29 namespace KWin
30 {
31 Cursors *Cursors::s_self = nullptr;
self()32 Cursors *Cursors::self() {
33 if (!s_self)
34 s_self = new Cursors;
35 return s_self;
36 }
37
addCursor(Cursor * cursor)38 void Cursors::addCursor(Cursor* cursor)
39 {
40 Q_ASSERT(!m_cursors.contains(cursor));
41 m_cursors += cursor;
42
43 connect(cursor, &Cursor::posChanged, this, [this, cursor] (const QPoint &pos) {
44 setCurrentCursor(cursor);
45 Q_EMIT positionChanged(cursor, pos);
46 });
47 }
48
removeCursor(Cursor * cursor)49 void Cursors::removeCursor(Cursor* cursor)
50 {
51 m_cursors.removeOne(cursor);
52 if (m_currentCursor == cursor) {
53 if (m_cursors.isEmpty())
54 m_currentCursor = nullptr;
55 else
56 setCurrentCursor(m_cursors.constFirst());
57 }
58 if (m_mouse == cursor) {
59 m_mouse = nullptr;
60 }
61 }
62
setCurrentCursor(Cursor * cursor)63 void Cursors::setCurrentCursor(Cursor* cursor)
64 {
65 if (m_currentCursor == cursor)
66 return;
67
68 Q_ASSERT(m_cursors.contains(cursor) || !cursor);
69
70 if (m_currentCursor) {
71 disconnect(m_currentCursor, &Cursor::rendered, this, &Cursors::currentCursorRendered);
72 disconnect(m_currentCursor, &Cursor::cursorChanged, this, &Cursors::emitCurrentCursorChanged);
73 }
74 m_currentCursor = cursor;
75 connect(m_currentCursor, &Cursor::rendered, this, &Cursors::currentCursorRendered);
76 connect(m_currentCursor, &Cursor::cursorChanged, this, &Cursors::emitCurrentCursorChanged);
77
78 Q_EMIT currentCursorChanged(m_currentCursor);
79 }
80
emitCurrentCursorChanged()81 void Cursors::emitCurrentCursorChanged()
82 {
83 Q_EMIT currentCursorChanged(m_currentCursor);
84 }
85
Cursor(QObject * parent)86 Cursor::Cursor(QObject *parent)
87 : QObject(parent)
88 , m_mousePollingCounter(0)
89 , m_cursorTrackingCounter(0)
90 , m_themeName(defaultThemeName())
91 , m_themeSize(defaultThemeSize())
92 {
93 loadThemeSettings();
94 QDBusConnection::sessionBus().connect(QString(), QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"),
95 QStringLiteral("notifyChange"), this, SLOT(slotKGlobalSettingsNotifyChange(int,int)));
96 }
97
~Cursor()98 Cursor::~Cursor()
99 {
100 Cursors::self()->removeCursor(this);
101 }
102
loadThemeSettings()103 void Cursor::loadThemeSettings()
104 {
105 QString themeName = QString::fromUtf8(qgetenv("XCURSOR_THEME"));
106 bool ok = false;
107 // XCURSOR_SIZE might not be set (e.g. by startkde)
108 const uint themeSize = qEnvironmentVariableIntValue("XCURSOR_SIZE", &ok);
109 if (!themeName.isEmpty() && ok) {
110 updateTheme(themeName, themeSize);
111 return;
112 }
113 // didn't get from environment variables, read from config file
114 loadThemeFromKConfig();
115 }
116
loadThemeFromKConfig()117 void Cursor::loadThemeFromKConfig()
118 {
119 KConfigGroup mousecfg(InputConfig::self()->inputConfig(), "Mouse");
120 const QString themeName = mousecfg.readEntry("cursorTheme", defaultThemeName());
121 const uint themeSize = mousecfg.readEntry("cursorSize", defaultThemeSize());
122 updateTheme(themeName, themeSize);
123 }
124
updateTheme(const QString & name,int size)125 void Cursor::updateTheme(const QString &name, int size)
126 {
127 if (m_themeName != name || m_themeSize != size) {
128 m_themeName = name;
129 m_themeSize = size;
130 m_cursors.clear();
131 Q_EMIT themeChanged();
132 }
133 }
134
slotKGlobalSettingsNotifyChange(int type,int arg)135 void Cursor::slotKGlobalSettingsNotifyChange(int type, int arg)
136 {
137 // #endif
138 Q_UNUSED(arg)
139 if (type == 5 /*CursorChanged*/) {
140 InputConfig::self()->inputConfig()->reparseConfiguration();
141 loadThemeFromKConfig();
142 // sync to environment
143 qputenv("XCURSOR_THEME", m_themeName.toUtf8());
144 qputenv("XCURSOR_SIZE", QByteArray::number(m_themeSize));
145 }
146 }
147
geometry() const148 QRect Cursor::geometry() const
149 {
150 return rect().translated(m_pos - hotspot());
151 }
152
rect() const153 QRect Cursor::rect() const
154 {
155 return QRect(QPoint(0, 0), image().size() / image().devicePixelRatio());
156 }
157
pos()158 QPoint Cursor::pos()
159 {
160 doGetPos();
161 return m_pos;
162 }
163
setPos(const QPoint & pos)164 void Cursor::setPos(const QPoint &pos)
165 {
166 // first query the current pos to not warp to the already existing pos
167 if (pos == m_pos) {
168 return;
169 }
170 m_pos = pos;
171 doSetPos();
172 }
173
setPos(int x,int y)174 void Cursor::setPos(int x, int y)
175 {
176 setPos(QPoint(x, y));
177 }
178
updateCursor(const QImage & image,const QPoint & hotspot)179 void Cursor::updateCursor(const QImage &image, const QPoint &hotspot)
180 {
181 m_image = image;
182 m_hotspot = hotspot;
183 Q_EMIT cursorChanged();
184 }
185
x11Cursor(CursorShape shape)186 xcb_cursor_t Cursor::x11Cursor(CursorShape shape)
187 {
188 return x11Cursor(shape.name());
189 }
190
x11Cursor(const QByteArray & name)191 xcb_cursor_t Cursor::x11Cursor(const QByteArray &name)
192 {
193 Q_ASSERT(kwinApp()->x11Connection());
194 auto it = m_cursors.constFind(name);
195 if (it != m_cursors.constEnd()) {
196 return it.value();
197 }
198
199 if (name.isEmpty()) {
200 return XCB_CURSOR_NONE;
201 }
202
203 xcb_cursor_context_t *ctx;
204 if (xcb_cursor_context_new(kwinApp()->x11Connection(), kwinApp()->x11DefaultScreen(), &ctx) < 0) {
205 return XCB_CURSOR_NONE;
206 }
207
208 xcb_cursor_t cursor = xcb_cursor_load_cursor(ctx, name.constData());
209 if (cursor == XCB_CURSOR_NONE) {
210 const auto &names = Cursor::cursorAlternativeNames(name);
211 for (const QByteArray &cursorName : names) {
212 cursor = xcb_cursor_load_cursor(ctx, cursorName.constData());
213 if (cursor != XCB_CURSOR_NONE) {
214 break;
215 }
216 }
217 }
218 if (cursor != XCB_CURSOR_NONE) {
219 m_cursors.insert(name, cursor);
220 }
221
222 xcb_cursor_context_free(ctx);
223 return cursor;
224 }
225
doSetPos()226 void Cursor::doSetPos()
227 {
228 Q_EMIT posChanged(m_pos);
229 }
230
doGetPos()231 void Cursor::doGetPos()
232 {
233 }
234
updatePos(const QPoint & pos)235 void Cursor::updatePos(const QPoint &pos)
236 {
237 if (m_pos == pos) {
238 return;
239 }
240 m_pos = pos;
241 Q_EMIT posChanged(m_pos);
242 }
243
startMousePolling()244 void Cursor::startMousePolling()
245 {
246 ++m_mousePollingCounter;
247 if (m_mousePollingCounter == 1) {
248 doStartMousePolling();
249 }
250 }
251
stopMousePolling()252 void Cursor::stopMousePolling()
253 {
254 Q_ASSERT(m_mousePollingCounter > 0);
255 --m_mousePollingCounter;
256 if (m_mousePollingCounter == 0) {
257 doStopMousePolling();
258 }
259 }
260
doStartMousePolling()261 void Cursor::doStartMousePolling()
262 {
263 }
264
doStopMousePolling()265 void Cursor::doStopMousePolling()
266 {
267 }
268
startCursorTracking()269 void Cursor::startCursorTracking()
270 {
271 ++m_cursorTrackingCounter;
272 if (m_cursorTrackingCounter == 1) {
273 doStartCursorTracking();
274 }
275 }
276
stopCursorTracking()277 void Cursor::stopCursorTracking()
278 {
279 Q_ASSERT(m_cursorTrackingCounter > 0);
280 --m_cursorTrackingCounter;
281 if (m_cursorTrackingCounter == 0) {
282 doStopCursorTracking();
283 }
284 }
285
doStartCursorTracking()286 void Cursor::doStartCursorTracking()
287 {
288 }
289
doStopCursorTracking()290 void Cursor::doStopCursorTracking()
291 {
292 }
293
cursorAlternativeNames(const QByteArray & name)294 QVector<QByteArray> Cursor::cursorAlternativeNames(const QByteArray &name)
295 {
296 static const QHash<QByteArray, QVector<QByteArray>> alternatives = {
297 {QByteArrayLiteral("left_ptr"), {QByteArrayLiteral("arrow"),
298 QByteArrayLiteral("dnd-none"),
299 QByteArrayLiteral("op_left_arrow")}},
300 {QByteArrayLiteral("cross"), {QByteArrayLiteral("crosshair"),
301 QByteArrayLiteral("diamond-cross"),
302 QByteArrayLiteral("cross-reverse")}},
303 {QByteArrayLiteral("up_arrow"), {QByteArrayLiteral("center_ptr"),
304 QByteArrayLiteral("sb_up_arrow"),
305 QByteArrayLiteral("centre_ptr")}},
306 {QByteArrayLiteral("wait"), {QByteArrayLiteral("watch"),
307 QByteArrayLiteral("progress")}},
308 {QByteArrayLiteral("ibeam"), {QByteArrayLiteral("xterm"),
309 QByteArrayLiteral("text")}},
310 {QByteArrayLiteral("size_all"), {QByteArrayLiteral("fleur")}},
311 {QByteArrayLiteral("pointing_hand"), {QByteArrayLiteral("hand2"),
312 QByteArrayLiteral("hand"),
313 QByteArrayLiteral("hand1"),
314 QByteArrayLiteral("pointer"),
315 QByteArrayLiteral("e29285e634086352946a0e7090d73106"),
316 QByteArrayLiteral("9d800788f1b08800ae810202380a0822")}},
317 {QByteArrayLiteral("size_ver"), {QByteArrayLiteral("00008160000006810000408080010102"),
318 QByteArrayLiteral("sb_v_double_arrow"),
319 QByteArrayLiteral("v_double_arrow"),
320 QByteArrayLiteral("n-resize"),
321 QByteArrayLiteral("s-resize"),
322 QByteArrayLiteral("col-resize"),
323 QByteArrayLiteral("top_side"),
324 QByteArrayLiteral("bottom_side"),
325 QByteArrayLiteral("base_arrow_up"),
326 QByteArrayLiteral("base_arrow_down"),
327 QByteArrayLiteral("based_arrow_down"),
328 QByteArrayLiteral("based_arrow_up")}},
329 {QByteArrayLiteral("size_hor"), {QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"),
330 QByteArrayLiteral("sb_h_double_arrow"),
331 QByteArrayLiteral("h_double_arrow"),
332 QByteArrayLiteral("e-resize"),
333 QByteArrayLiteral("w-resize"),
334 QByteArrayLiteral("row-resize"),
335 QByteArrayLiteral("right_side"),
336 QByteArrayLiteral("left_side")}},
337 {QByteArrayLiteral("size_bdiag"), {QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"),
338 QByteArrayLiteral("fd_double_arrow"),
339 QByteArrayLiteral("bottom_left_corner"),
340 QByteArrayLiteral("top_right_corner")}},
341 {QByteArrayLiteral("size_fdiag"), {QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"),
342 QByteArrayLiteral("bd_double_arrow"),
343 QByteArrayLiteral("bottom_right_corner"),
344 QByteArrayLiteral("top_left_corner")}},
345 {QByteArrayLiteral("whats_this"), {QByteArrayLiteral("d9ce0ab605698f320427677b458ad60b"),
346 QByteArrayLiteral("left_ptr_help"),
347 QByteArrayLiteral("help"),
348 QByteArrayLiteral("question_arrow"),
349 QByteArrayLiteral("dnd-ask"),
350 QByteArrayLiteral("5c6cd98b3f3ebcb1f9c7f1c204630408")}},
351 {QByteArrayLiteral("split_h"), {QByteArrayLiteral("14fef782d02440884392942c11205230"),
352 QByteArrayLiteral("size_hor")}},
353 {QByteArrayLiteral("split_v"), {QByteArrayLiteral("2870a09082c103050810ffdffffe0204"),
354 QByteArrayLiteral("size_ver")}},
355 {QByteArrayLiteral("forbidden"), {QByteArrayLiteral("03b6e0fcb3499374a867c041f52298f0"),
356 QByteArrayLiteral("circle"),
357 QByteArrayLiteral("dnd-no-drop"),
358 QByteArrayLiteral("not-allowed")}},
359 {QByteArrayLiteral("left_ptr_watch"), {QByteArrayLiteral("3ecb610c1bf2410f44200f48c40d3599"),
360 QByteArrayLiteral("00000000000000020006000e7e9ffc3f"),
361 QByteArrayLiteral("08e8e1c95fe2fc01f976f1e063a24ccd")}},
362 {QByteArrayLiteral("openhand"), {QByteArrayLiteral("9141b49c8149039304290b508d208c40"),
363 QByteArrayLiteral("all_scroll"),
364 QByteArrayLiteral("all-scroll")}},
365 {QByteArrayLiteral("closedhand"), {QByteArrayLiteral("05e88622050804100c20044008402080"),
366 QByteArrayLiteral("4498f0e0c1937ffe01fd06f973665830"),
367 QByteArrayLiteral("9081237383d90e509aa00f00170e968f"),
368 QByteArrayLiteral("fcf21c00b30f7e3f83fe0dfd12e71cff")}},
369 {QByteArrayLiteral("dnd-link"), {QByteArrayLiteral("link"),
370 QByteArrayLiteral("alias"),
371 QByteArrayLiteral("3085a0e285430894940527032f8b26df"),
372 QByteArrayLiteral("640fb0e74195791501fd1ed57b41487f"),
373 QByteArrayLiteral("a2a266d0498c3104214a47bd64ab0fc8")}},
374 {QByteArrayLiteral("dnd-copy"), {QByteArrayLiteral("copy"),
375 QByteArrayLiteral("1081e37283d90000800003c07f3ef6bf"),
376 QByteArrayLiteral("6407b0e94181790501fd1e167b474872"),
377 QByteArrayLiteral("b66166c04f8c3109214a4fbd64a50fc8")}},
378 {QByteArrayLiteral("dnd-move"), {QByteArrayLiteral("move")}},
379 {QByteArrayLiteral("sw-resize"), {QByteArrayLiteral("size_bdiag"),
380 QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"),
381 QByteArrayLiteral("fd_double_arrow"),
382 QByteArrayLiteral("bottom_left_corner")}},
383 {QByteArrayLiteral("se-resize"), {QByteArrayLiteral("size_fdiag"),
384 QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"),
385 QByteArrayLiteral("bd_double_arrow"),
386 QByteArrayLiteral("bottom_right_corner")}},
387 {QByteArrayLiteral("ne-resize"), {QByteArrayLiteral("size_bdiag"),
388 QByteArrayLiteral("fcf1c3c7cd4491d801f1e1c78f100000"),
389 QByteArrayLiteral("fd_double_arrow"),
390 QByteArrayLiteral("top_right_corner")}},
391 {QByteArrayLiteral("nw-resize"), {QByteArrayLiteral("size_fdiag"),
392 QByteArrayLiteral("c7088f0f3e6c8088236ef8e1e3e70000"),
393 QByteArrayLiteral("bd_double_arrow"),
394 QByteArrayLiteral("top_left_corner")}},
395 {QByteArrayLiteral("n-resize"), {QByteArrayLiteral("size_ver"),
396 QByteArrayLiteral("00008160000006810000408080010102"),
397 QByteArrayLiteral("sb_v_double_arrow"),
398 QByteArrayLiteral("v_double_arrow"),
399 QByteArrayLiteral("col-resize"),
400 QByteArrayLiteral("top_side")}},
401 {QByteArrayLiteral("e-resize"), {QByteArrayLiteral("size_hor"),
402 QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"),
403 QByteArrayLiteral("sb_h_double_arrow"),
404 QByteArrayLiteral("h_double_arrow"),
405 QByteArrayLiteral("row-resize"),
406 QByteArrayLiteral("left_side")}},
407 {QByteArrayLiteral("s-resize"), {QByteArrayLiteral("size_ver"),
408 QByteArrayLiteral("00008160000006810000408080010102"),
409 QByteArrayLiteral("sb_v_double_arrow"),
410 QByteArrayLiteral("v_double_arrow"),
411 QByteArrayLiteral("col-resize"),
412 QByteArrayLiteral("bottom_side")}},
413 {QByteArrayLiteral("w-resize"), {QByteArrayLiteral("size_hor"),
414 QByteArrayLiteral("028006030e0e7ebffc7f7070c0600140"),
415 QByteArrayLiteral("sb_h_double_arrow"),
416 QByteArrayLiteral("h_double_arrow"),
417 QByteArrayLiteral("right_side")}}
418 };
419 auto it = alternatives.find(name);
420 if (it != alternatives.end()) {
421 return it.value();
422 }
423 return QVector<QByteArray>();
424 }
425
defaultThemeName()426 QString Cursor::defaultThemeName()
427 {
428 return QStringLiteral("default");
429 }
430
defaultThemeSize()431 int Cursor::defaultThemeSize()
432 {
433 return 24;
434 }
435
name() const436 QByteArray CursorShape::name() const
437 {
438 switch (m_shape) {
439 case Qt::ArrowCursor:
440 return QByteArrayLiteral("left_ptr");
441 case Qt::UpArrowCursor:
442 return QByteArrayLiteral("up_arrow");
443 case Qt::CrossCursor:
444 return QByteArrayLiteral("cross");
445 case Qt::WaitCursor:
446 return QByteArrayLiteral("wait");
447 case Qt::IBeamCursor:
448 return QByteArrayLiteral("ibeam");
449 case Qt::SizeVerCursor:
450 return QByteArrayLiteral("size_ver");
451 case Qt::SizeHorCursor:
452 return QByteArrayLiteral("size_hor");
453 case Qt::SizeBDiagCursor:
454 return QByteArrayLiteral("size_bdiag");
455 case Qt::SizeFDiagCursor:
456 return QByteArrayLiteral("size_fdiag");
457 case Qt::SizeAllCursor:
458 return QByteArrayLiteral("size_all");
459 case Qt::SplitVCursor:
460 return QByteArrayLiteral("split_v");
461 case Qt::SplitHCursor:
462 return QByteArrayLiteral("split_h");
463 case Qt::PointingHandCursor:
464 return QByteArrayLiteral("pointing_hand");
465 case Qt::ForbiddenCursor:
466 return QByteArrayLiteral("forbidden");
467 case Qt::OpenHandCursor:
468 return QByteArrayLiteral("openhand");
469 case Qt::ClosedHandCursor:
470 return QByteArrayLiteral("closedhand");
471 case Qt::WhatsThisCursor:
472 return QByteArrayLiteral("whats_this");
473 case Qt::BusyCursor:
474 return QByteArrayLiteral("left_ptr_watch");
475 case Qt::DragMoveCursor:
476 return QByteArrayLiteral("dnd-move");
477 case Qt::DragCopyCursor:
478 return QByteArrayLiteral("dnd-copy");
479 case Qt::DragLinkCursor:
480 return QByteArrayLiteral("dnd-link");
481 case KWin::ExtendedCursor::SizeNorthEast:
482 return QByteArrayLiteral("ne-resize");
483 case KWin::ExtendedCursor::SizeNorth:
484 return QByteArrayLiteral("n-resize");
485 case KWin::ExtendedCursor::SizeNorthWest:
486 return QByteArrayLiteral("nw-resize");
487 case KWin::ExtendedCursor::SizeEast:
488 return QByteArrayLiteral("e-resize");
489 case KWin::ExtendedCursor::SizeWest:
490 return QByteArrayLiteral("w-resize");
491 case KWin::ExtendedCursor::SizeSouthEast:
492 return QByteArrayLiteral("se-resize");
493 case KWin::ExtendedCursor::SizeSouth:
494 return QByteArrayLiteral("s-resize");
495 case KWin::ExtendedCursor::SizeSouthWest:
496 return QByteArrayLiteral("sw-resize");
497 default:
498 return QByteArray();
499 }
500 }
501
502 InputConfig *InputConfig::s_self = nullptr;
self()503 InputConfig *InputConfig::self() {
504 if (!s_self)
505 s_self = new InputConfig;
506 return s_self;
507 }
508
InputConfig()509 InputConfig::InputConfig()
510 : m_inputConfig(KSharedConfig::openConfig(QStringLiteral("kcminputrc"), KConfig::NoGlobals))
511 {
512 }
513
514 } // namespace
515