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