File indexing completed on 2024-02-18 16:20:07

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
0006     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 #include "main.h"
0011 
0012 #include <config-kwin.h>
0013 
0014 #include "atoms.h"
0015 #include "colormanager.h"
0016 #include "composite.h"
0017 #include "core/outputbackend.h"
0018 #include "core/session.h"
0019 #include "cursor.h"
0020 #include "effects.h"
0021 #include "input.h"
0022 #include "inputmethod.h"
0023 #include "options.h"
0024 #include "outline.h"
0025 #include "pluginmanager.h"
0026 #include "pointer_input.h"
0027 #include "screenedge.h"
0028 #include "sm.h"
0029 #include "tabletmodemanager.h"
0030 #include "utils/xcbutils.h"
0031 #include "wayland/surface_interface.h"
0032 #include "workspace.h"
0033 #include "x11eventfilter.h"
0034 
0035 #if KWIN_BUILD_SCREENLOCKER
0036 #include "screenlockerwatcher.h"
0037 #endif
0038 
0039 #include <kwineffects.h>
0040 
0041 // KDE
0042 #include <KAboutData>
0043 #include <KLocalizedString>
0044 // Qt
0045 #include <QCommandLineParser>
0046 #include <QLibraryInfo>
0047 #include <QQuickWindow>
0048 #include <QStandardPaths>
0049 #include <QTranslator>
0050 #include <qplatformdefs.h>
0051 
0052 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0053 #include <private/qtx11extras_p.h>
0054 #else
0055 #include <QX11Info>
0056 #endif
0057 
0058 #include <cerrno>
0059 
0060 #if __has_include(<malloc.h>)
0061 #include <malloc.h>
0062 #endif
0063 #include <unistd.h>
0064 
0065 // xcb
0066 #include <xcb/damage.h>
0067 #ifndef XCB_GE_GENERIC
0068 #define XCB_GE_GENERIC 35
0069 #endif
0070 
0071 Q_DECLARE_METATYPE(KSharedConfigPtr)
0072 
0073 namespace KWin
0074 {
0075 
0076 Options *options;
0077 Atoms *atoms;
0078 int Application::crashes = 0;
0079 
0080 Application::Application(Application::OperationMode mode, int &argc, char **argv)
0081     : QApplication(argc, argv)
0082     , m_eventFilter(new XcbEventFilter())
0083     , m_configLock(false)
0084     , m_config(KSharedConfig::openConfig(QStringLiteral("kwinrc")))
0085     , m_kxkbConfig()
0086     , m_operationMode(mode)
0087 {
0088     qRegisterMetaType<Options::WindowOperation>("Options::WindowOperation");
0089     qRegisterMetaType<KWin::EffectWindow *>();
0090     qRegisterMetaType<KWaylandServer::SurfaceInterface *>("KWaylandServer::SurfaceInterface *");
0091     qRegisterMetaType<KSharedConfigPtr>();
0092     qRegisterMetaType<std::chrono::nanoseconds>();
0093 }
0094 
0095 void Application::setConfigLock(bool lock)
0096 {
0097     m_configLock = lock;
0098 }
0099 
0100 Application::OperationMode Application::operationMode() const
0101 {
0102     return m_operationMode;
0103 }
0104 
0105 void Application::setOperationMode(OperationMode mode)
0106 {
0107     m_operationMode = mode;
0108 }
0109 
0110 bool Application::shouldUseWaylandForCompositing() const
0111 {
0112     return m_operationMode == OperationModeWaylandOnly || m_operationMode == OperationModeXwayland;
0113 }
0114 
0115 void Application::start()
0116 {
0117     // Prevent KWin from synchronously autostarting kactivitymanagerd
0118     // Indeed, kactivitymanagerd being a QApplication it will depend
0119     // on KWin startup... this is unsatisfactory dependency wise,
0120     // and it turns out that it leads to a deadlock in the Wayland case
0121     setProperty("org.kde.KActivities.core.disableAutostart", true);
0122 
0123     setQuitOnLastWindowClosed(false);
0124 
0125     if (!m_config->isImmutable() && m_configLock) {
0126         // TODO: This shouldn't be necessary
0127         // config->setReadOnly( true );
0128         m_config->reparseConfiguration();
0129     }
0130     if (!m_kxkbConfig) {
0131         m_kxkbConfig = KSharedConfig::openConfig(QStringLiteral("kxkbrc"), KConfig::NoGlobals);
0132     }
0133 
0134     performStartup();
0135 }
0136 
0137 Application::~Application()
0138 {
0139     delete options;
0140     destroyAtoms();
0141     destroyPlatform();
0142     m_session.reset();
0143 }
0144 
0145 void Application::notifyStarted()
0146 {
0147     Q_EMIT started();
0148 }
0149 
0150 void Application::destroyAtoms()
0151 {
0152     delete atoms;
0153     atoms = nullptr;
0154 }
0155 
0156 void Application::destroyPlatform()
0157 {
0158     m_outputBackend.reset();
0159 }
0160 
0161 void Application::resetCrashesCount()
0162 {
0163     crashes = 0;
0164 }
0165 
0166 void Application::setCrashCount(int count)
0167 {
0168     crashes = count;
0169 }
0170 
0171 bool Application::wasCrash()
0172 {
0173     return crashes > 0;
0174 }
0175 
0176 void Application::createAboutData()
0177 {
0178     KAboutData aboutData(QStringLiteral("kwin"), // The program name used internally
0179                          i18n("KWin"), // A displayable program name string
0180                          QStringLiteral(KWIN_VERSION_STRING), // The program version string
0181                          i18n("KDE window manager"), // Short description of what the app does
0182                          KAboutLicense::GPL, // The license this code is released under
0183                          i18n("(c) 1999-2019, The KDE Developers")); // Copyright Statement
0184 
0185     aboutData.addAuthor(i18n("Matthias Ettrich"), QString(), QStringLiteral("ettrich@kde.org"));
0186     aboutData.addAuthor(i18n("Cristian Tibirna"), QString(), QStringLiteral("tibirna@kde.org"));
0187     aboutData.addAuthor(i18n("Daniel M. Duley"), QString(), QStringLiteral("mosfet@kde.org"));
0188     aboutData.addAuthor(i18n("Luboš Luňák"), QString(), QStringLiteral("l.lunak@kde.org"));
0189     aboutData.addAuthor(i18n("Martin Flöser"), QString(), QStringLiteral("mgraesslin@kde.org"));
0190     aboutData.addAuthor(i18n("David Edmundson"), QStringLiteral("Maintainer"), QStringLiteral("davidedmundson@kde.org"));
0191     aboutData.addAuthor(i18n("Roman Gilg"), QStringLiteral("Maintainer"), QStringLiteral("subdiff@gmail.com"));
0192     aboutData.addAuthor(i18n("Vlad Zahorodnii"), QStringLiteral("Maintainer"), QStringLiteral("vlad.zahorodnii@kde.org"));
0193     KAboutData::setApplicationData(aboutData);
0194 }
0195 
0196 static const QString s_lockOption = QStringLiteral("lock");
0197 static const QString s_crashesOption = QStringLiteral("crashes");
0198 
0199 void Application::setupCommandLine(QCommandLineParser *parser)
0200 {
0201     QCommandLineOption lockOption(s_lockOption, i18n("Disable configuration options"));
0202     QCommandLineOption crashesOption(s_crashesOption, i18n("Indicate that KWin has recently crashed n times"), QStringLiteral("n"));
0203 
0204     parser->setApplicationDescription(i18n("KDE window manager"));
0205     parser->addOption(lockOption);
0206     parser->addOption(crashesOption);
0207     KAboutData::applicationData().setupCommandLine(parser);
0208 }
0209 
0210 void Application::processCommandLine(QCommandLineParser *parser)
0211 {
0212     KAboutData aboutData = KAboutData::applicationData();
0213     aboutData.processCommandLine(parser);
0214     setConfigLock(parser->isSet(s_lockOption));
0215     Application::setCrashCount(parser->value(s_crashesOption).toInt());
0216 }
0217 
0218 void Application::setupTranslator()
0219 {
0220     QTranslator *qtTranslator = new QTranslator(qApp);
0221     qtTranslator->load("qt_" + QLocale::system().name(),
0222                        QLibraryInfo::location(QLibraryInfo::TranslationsPath));
0223     installTranslator(qtTranslator);
0224 }
0225 
0226 void Application::setupMalloc()
0227 {
0228 #ifdef M_TRIM_THRESHOLD
0229     // Prevent fragmentation of the heap by malloc (glibc).
0230     //
0231     // The default threshold is 128*1024, which can result in a large memory usage
0232     // due to fragmentation especially if we use the raster graphicssystem. On the
0233     // otherside if the threshold is too low, free() starts to permanently ask the kernel
0234     // about shrinking the heap.
0235     const int pagesize = sysconf(_SC_PAGESIZE);
0236     mallopt(M_TRIM_THRESHOLD, 5 * pagesize);
0237 #endif // M_TRIM_THRESHOLD
0238 }
0239 
0240 void Application::setupLocalizedString()
0241 {
0242     KLocalizedString::setApplicationDomain("kwin");
0243 }
0244 
0245 void Application::createWorkspace()
0246 {
0247     // we want all QQuickWindows with an alpha buffer, do here as Workspace might create QQuickWindows
0248     QQuickWindow::setDefaultAlphaBuffer(true);
0249 
0250     // This tries to detect compositing options and can use GLX. GLX problems
0251     // (X errors) shouldn't cause kwin to abort, so this is out of the
0252     // critical startup section where x errors cause kwin to abort.
0253 
0254     // create workspace.
0255     (void)new Workspace();
0256     Q_EMIT workspaceCreated();
0257 }
0258 
0259 void Application::createInput()
0260 {
0261 #if KWIN_BUILD_SCREENLOCKER
0262     m_screenLockerWatcher = std::make_unique<ScreenLockerWatcher>();
0263 #endif
0264     auto input = InputRedirection::create(this);
0265     input->init();
0266     createPlatformCursor(this);
0267 }
0268 
0269 void Application::createAtoms()
0270 {
0271     atoms = new Atoms;
0272 }
0273 
0274 void Application::createOptions()
0275 {
0276     options = new Options;
0277 }
0278 
0279 void Application::createPlugins()
0280 {
0281     m_pluginManager = std::make_unique<PluginManager>();
0282 }
0283 
0284 void Application::createColorManager()
0285 {
0286     m_colorManager = std::make_unique<ColorManager>();
0287 }
0288 
0289 void Application::createInputMethod()
0290 {
0291     m_inputMethod = std::make_unique<InputMethod>();
0292 }
0293 
0294 void Application::createTabletModeManager()
0295 {
0296     m_tabletModeManager = std::make_unique<TabletModeManager>();
0297 }
0298 
0299 void Application::installNativeX11EventFilter()
0300 {
0301     installNativeEventFilter(m_eventFilter.get());
0302 }
0303 
0304 void Application::removeNativeX11EventFilter()
0305 {
0306     removeNativeEventFilter(m_eventFilter.get());
0307 }
0308 
0309 void Application::destroyInput()
0310 {
0311     delete InputRedirection::self();
0312 }
0313 
0314 void Application::destroyWorkspace()
0315 {
0316     delete Workspace::self();
0317 }
0318 
0319 void Application::destroyCompositor()
0320 {
0321     delete Compositor::self();
0322 }
0323 
0324 void Application::destroyPlugins()
0325 {
0326     m_pluginManager.reset();
0327 }
0328 
0329 void Application::destroyColorManager()
0330 {
0331     m_colorManager.reset();
0332 }
0333 
0334 void Application::destroyInputMethod()
0335 {
0336     m_inputMethod.reset();
0337 }
0338 
0339 std::unique_ptr<Edge> Application::createScreenEdge(ScreenEdges *edges)
0340 {
0341     return std::make_unique<Edge>(edges);
0342 }
0343 
0344 void Application::createPlatformCursor(QObject *parent)
0345 {
0346     new InputRedirectionCursor(parent);
0347 }
0348 
0349 std::unique_ptr<OutlineVisual> Application::createOutline(Outline *outline)
0350 {
0351     if (Compositor::compositing()) {
0352         return std::make_unique<CompositedOutlineVisual>(outline);
0353     }
0354     return nullptr;
0355 }
0356 
0357 void Application::createEffectsHandler(Compositor *compositor, WorkspaceScene *scene)
0358 {
0359     new EffectsHandlerImpl(compositor, scene);
0360 }
0361 
0362 void Application::registerEventFilter(X11EventFilter *filter)
0363 {
0364     if (filter->isGenericEvent()) {
0365         m_genericEventFilters.append(new X11EventFilterContainer(filter));
0366     } else {
0367         m_eventFilters.append(new X11EventFilterContainer(filter));
0368     }
0369 }
0370 
0371 static X11EventFilterContainer *takeEventFilter(X11EventFilter *eventFilter,
0372                                                 QList<QPointer<X11EventFilterContainer>> &list)
0373 {
0374     for (int i = 0; i < list.count(); ++i) {
0375         X11EventFilterContainer *container = list.at(i);
0376         if (container->filter() == eventFilter) {
0377             return list.takeAt(i);
0378         }
0379     }
0380     return nullptr;
0381 }
0382 
0383 void Application::setXwaylandScale(qreal scale)
0384 {
0385     if (scale != m_xwaylandScale) {
0386         m_xwaylandScale = scale;
0387         Q_EMIT xwaylandScaleChanged();
0388     }
0389 }
0390 
0391 void Application::unregisterEventFilter(X11EventFilter *filter)
0392 {
0393     X11EventFilterContainer *container = nullptr;
0394     if (filter->isGenericEvent()) {
0395         container = takeEventFilter(filter, m_genericEventFilters);
0396     } else {
0397         container = takeEventFilter(filter, m_eventFilters);
0398     }
0399     delete container;
0400 }
0401 
0402 bool Application::dispatchEvent(xcb_generic_event_t *event)
0403 {
0404     static const QVector<QByteArray> s_xcbEerrors({QByteArrayLiteral("Success"),
0405                                                    QByteArrayLiteral("BadRequest"),
0406                                                    QByteArrayLiteral("BadValue"),
0407                                                    QByteArrayLiteral("BadWindow"),
0408                                                    QByteArrayLiteral("BadPixmap"),
0409                                                    QByteArrayLiteral("BadAtom"),
0410                                                    QByteArrayLiteral("BadCursor"),
0411                                                    QByteArrayLiteral("BadFont"),
0412                                                    QByteArrayLiteral("BadMatch"),
0413                                                    QByteArrayLiteral("BadDrawable"),
0414                                                    QByteArrayLiteral("BadAccess"),
0415                                                    QByteArrayLiteral("BadAlloc"),
0416                                                    QByteArrayLiteral("BadColor"),
0417                                                    QByteArrayLiteral("BadGC"),
0418                                                    QByteArrayLiteral("BadIDChoice"),
0419                                                    QByteArrayLiteral("BadName"),
0420                                                    QByteArrayLiteral("BadLength"),
0421                                                    QByteArrayLiteral("BadImplementation"),
0422                                                    QByteArrayLiteral("Unknown")});
0423 
0424     kwinApp()->updateX11Time(event);
0425 
0426     const uint8_t x11EventType = event->response_type & ~0x80;
0427     if (!x11EventType) {
0428         // let's check whether it's an error from one of the extensions KWin uses
0429         xcb_generic_error_t *error = reinterpret_cast<xcb_generic_error_t *>(event);
0430         const QVector<Xcb::ExtensionData> extensions = Xcb::Extensions::self()->extensions();
0431         for (const auto &extension : extensions) {
0432             if (error->major_code == extension.majorOpcode) {
0433                 QByteArray errorName;
0434                 if (error->error_code < s_xcbEerrors.size()) {
0435                     errorName = s_xcbEerrors.at(error->error_code);
0436                 } else if (error->error_code >= extension.errorBase) {
0437                     const int index = error->error_code - extension.errorBase;
0438                     if (index >= 0 && index < extension.errorCodes.size()) {
0439                         errorName = extension.errorCodes.at(index);
0440                     }
0441                 }
0442                 if (errorName.isEmpty()) {
0443                     errorName = QByteArrayLiteral("Unknown");
0444                 }
0445                 qCWarning(KWIN_CORE, "XCB error: %d (%s), sequence: %d, resource id: %d, major code: %d (%s), minor code: %d (%s)",
0446                           int(error->error_code), errorName.constData(),
0447                           int(error->sequence), int(error->resource_id),
0448                           int(error->major_code), extension.name.constData(),
0449                           int(error->minor_code),
0450                           extension.opCodes.size() > error->minor_code ? extension.opCodes.at(error->minor_code).constData() : "Unknown");
0451                 return true;
0452             }
0453         }
0454         return false;
0455     }
0456 
0457     if (x11EventType == XCB_GE_GENERIC) {
0458         xcb_ge_generic_event_t *ge = reinterpret_cast<xcb_ge_generic_event_t *>(event);
0459 
0460         // We need to make a shadow copy of the event filter list because an activated event
0461         // filter may mutate it by removing or installing another event filter.
0462         const auto eventFilters = m_genericEventFilters;
0463 
0464         for (X11EventFilterContainer *container : eventFilters) {
0465             if (!container) {
0466                 continue;
0467             }
0468             X11EventFilter *filter = container->filter();
0469             if (filter->extension() == ge->extension && filter->genericEventTypes().contains(ge->event_type) && filter->event(event)) {
0470                 return true;
0471             }
0472         }
0473     } else {
0474         // We need to make a shadow copy of the event filter list because an activated event
0475         // filter may mutate it by removing or installing another event filter.
0476         const auto eventFilters = m_eventFilters;
0477 
0478         for (X11EventFilterContainer *container : eventFilters) {
0479             if (!container) {
0480                 continue;
0481             }
0482             X11EventFilter *filter = container->filter();
0483             if (filter->eventTypes().contains(x11EventType) && filter->event(event)) {
0484                 return true;
0485             }
0486         }
0487     }
0488 
0489     if (workspace()) {
0490         return workspace()->workspaceEvent(event);
0491     }
0492 
0493     return false;
0494 }
0495 
0496 static quint32 monotonicTime()
0497 {
0498     timespec ts;
0499 
0500     const int result = clock_gettime(CLOCK_MONOTONIC, &ts);
0501     if (result) {
0502         qCWarning(KWIN_CORE, "Failed to query monotonic time: %s", strerror(errno));
0503     }
0504 
0505     return ts.tv_sec * 1000 + ts.tv_nsec / 1000000L;
0506 }
0507 
0508 void Application::updateXTime()
0509 {
0510     switch (operationMode()) {
0511     case Application::OperationModeX11:
0512         setX11Time(QX11Info::getTimestamp(), TimestampUpdate::Always);
0513         break;
0514 
0515     case Application::OperationModeXwayland:
0516         setX11Time(monotonicTime(), TimestampUpdate::Always);
0517         break;
0518 
0519     default:
0520         // Do not update the current X11 time stamp if it's the Wayland only session.
0521         break;
0522     }
0523 }
0524 
0525 void Application::updateX11Time(xcb_generic_event_t *event)
0526 {
0527     xcb_timestamp_t time = XCB_TIME_CURRENT_TIME;
0528     const uint8_t eventType = event->response_type & ~0x80;
0529     switch (eventType) {
0530     case XCB_KEY_PRESS:
0531     case XCB_KEY_RELEASE:
0532         time = reinterpret_cast<xcb_key_press_event_t *>(event)->time;
0533         break;
0534     case XCB_BUTTON_PRESS:
0535     case XCB_BUTTON_RELEASE:
0536         time = reinterpret_cast<xcb_button_press_event_t *>(event)->time;
0537         break;
0538     case XCB_MOTION_NOTIFY:
0539         time = reinterpret_cast<xcb_motion_notify_event_t *>(event)->time;
0540         break;
0541     case XCB_ENTER_NOTIFY:
0542     case XCB_LEAVE_NOTIFY:
0543         time = reinterpret_cast<xcb_enter_notify_event_t *>(event)->time;
0544         break;
0545     case XCB_FOCUS_IN:
0546     case XCB_FOCUS_OUT:
0547     case XCB_KEYMAP_NOTIFY:
0548     case XCB_EXPOSE:
0549     case XCB_GRAPHICS_EXPOSURE:
0550     case XCB_NO_EXPOSURE:
0551     case XCB_VISIBILITY_NOTIFY:
0552     case XCB_CREATE_NOTIFY:
0553     case XCB_DESTROY_NOTIFY:
0554     case XCB_UNMAP_NOTIFY:
0555     case XCB_MAP_NOTIFY:
0556     case XCB_MAP_REQUEST:
0557     case XCB_REPARENT_NOTIFY:
0558     case XCB_CONFIGURE_NOTIFY:
0559     case XCB_CONFIGURE_REQUEST:
0560     case XCB_GRAVITY_NOTIFY:
0561     case XCB_RESIZE_REQUEST:
0562     case XCB_CIRCULATE_NOTIFY:
0563     case XCB_CIRCULATE_REQUEST:
0564         // no timestamp
0565         return;
0566     case XCB_PROPERTY_NOTIFY:
0567         time = reinterpret_cast<xcb_property_notify_event_t *>(event)->time;
0568         break;
0569     case XCB_SELECTION_CLEAR:
0570         time = reinterpret_cast<xcb_selection_clear_event_t *>(event)->time;
0571         break;
0572     case XCB_SELECTION_REQUEST:
0573         time = reinterpret_cast<xcb_selection_request_event_t *>(event)->time;
0574         break;
0575     case XCB_SELECTION_NOTIFY:
0576         time = reinterpret_cast<xcb_selection_notify_event_t *>(event)->time;
0577         break;
0578     case XCB_COLORMAP_NOTIFY:
0579     case XCB_CLIENT_MESSAGE:
0580     case XCB_MAPPING_NOTIFY:
0581     case XCB_GE_GENERIC:
0582         // no timestamp
0583         return;
0584     default:
0585         // extension handling
0586         if (Xcb::Extensions::self()) {
0587             if (eventType == Xcb::Extensions::self()->shapeNotifyEvent()) {
0588                 time = reinterpret_cast<xcb_shape_notify_event_t *>(event)->server_time;
0589             }
0590             if (eventType == Xcb::Extensions::self()->damageNotifyEvent()) {
0591                 time = reinterpret_cast<xcb_damage_notify_event_t *>(event)->timestamp;
0592             }
0593         }
0594         break;
0595     }
0596     setX11Time(time);
0597 }
0598 
0599 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0600 bool XcbEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
0601 #else
0602 bool XcbEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
0603 #endif
0604 {
0605     if (eventType == "xcb_generic_event_t") {
0606         return kwinApp()->dispatchEvent(static_cast<xcb_generic_event_t *>(message));
0607     }
0608     return false;
0609 }
0610 
0611 QProcessEnvironment Application::processStartupEnvironment() const
0612 {
0613     return m_processEnvironment;
0614 }
0615 
0616 void Application::setProcessStartupEnvironment(const QProcessEnvironment &environment)
0617 {
0618     m_processEnvironment = environment;
0619 }
0620 
0621 void Application::setOutputBackend(std::unique_ptr<OutputBackend> &&backend)
0622 {
0623     Q_ASSERT(!m_outputBackend);
0624     m_outputBackend = std::move(backend);
0625 }
0626 
0627 void Application::setSession(std::unique_ptr<Session> &&session)
0628 {
0629     Q_ASSERT(!m_session);
0630     m_session = std::move(session);
0631 }
0632 
0633 PluginManager *Application::pluginManager() const
0634 {
0635     return m_pluginManager.get();
0636 }
0637 
0638 InputMethod *Application::inputMethod() const
0639 {
0640     return m_inputMethod.get();
0641 }
0642 
0643 ColorManager *Application::colorManager() const
0644 {
0645     return m_colorManager.get();
0646 }
0647 
0648 XwaylandInterface *Application::xwayland() const
0649 {
0650     return nullptr;
0651 }
0652 
0653 #if KWIN_BUILD_SCREENLOCKER
0654 ScreenLockerWatcher *Application::screenLockerWatcher() const
0655 {
0656     return m_screenLockerWatcher.get();
0657 }
0658 #endif
0659 
0660 PlatformCursorImage Application::cursorImage() const
0661 {
0662     Cursor *cursor = Cursors::self()->currentCursor();
0663     return PlatformCursorImage(cursor->image(), cursor->hotspot());
0664 }
0665 
0666 void Application::startInteractiveWindowSelection(std::function<void(KWin::Window *)> callback, const QByteArray &cursorName)
0667 {
0668     if (!input()) {
0669         callback(nullptr);
0670         return;
0671     }
0672     input()->startInteractiveWindowSelection(callback, cursorName);
0673 }
0674 
0675 void Application::startInteractivePositionSelection(std::function<void(const QPoint &)> callback)
0676 {
0677     if (!input()) {
0678         callback(QPoint(-1, -1));
0679         return;
0680     }
0681     input()->startInteractivePositionSelection(callback);
0682 }
0683 
0684 } // namespace