File indexing completed on 2020-04-30 10:48:28

0001 /********************************************************************
0002  KWin - the KDE window manager
0003  This file is part of the KDE project.
0004 
0005 Copyright (C) 2016 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007 This program is free software; you can redistribute it and/or modify
0008 it under the terms of the GNU General Public License as published by
0009 the Free Software Foundation; either version 2 of the License, or
0010 (at your option) any later version.
0011 
0012 This program is distributed in the hope that it will be useful,
0013 but WITHOUT ANY WARRANTY; without even the implied warranty of
0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015 GNU General Public License for more details.
0016 
0017 You should have received a copy of the GNU General Public License
0018 along with this program.  If not, see <http://www.gnu.org/licenses/>.
0019 *********************************************************************/
0020 #include "x11_platform.h"
0021 #include "x11cursor.h"
0022 #include "edge.h"
0023 #include "windowselector.h"
0024 #include <config-kwin.h>
0025 #include <kwinconfig.h>
0026 #if HAVE_EPOXY_GLX
0027 #include "glxbackend.h"
0028 #endif
0029 #if HAVE_X11_XINPUT
0030 #include "xinputintegration.h"
0031 #endif
0032 #include "abstract_client.h"
0033 #include "effects_x11.h"
0034 #include "eglonxbackend.h"
0035 #include "keyboard_input.h"
0036 #include "logging.h"
0037 #include "screens_xrandr.h"
0038 #include "screenedges_filter.h"
0039 #include "options.h"
0040 #include "overlaywindow_x11.h"
0041 #include "non_composited_outline.h"
0042 #include "workspace.h"
0043 #include "x11_decoration_renderer.h"
0044 #include "x11_output.h"
0045 #include "xcbutils.h"
0046 
0047 #include <kwinxrenderutils.h>
0048 
0049 #include <KConfigGroup>
0050 #include <KLocalizedString>
0051 #include <KCrash>
0052 
0053 #include <QThread>
0054 #include <QOpenGLContext>
0055 #include <QX11Info>
0056 
0057 namespace KWin
0058 {
0059 
0060 X11StandalonePlatform::X11StandalonePlatform(QObject *parent)
0061     : Platform(parent)
0062     , m_x11Display(QX11Info::display())
0063 {
0064 #if HAVE_X11_XINPUT
0065     if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) {
0066         m_xinputIntegration = new XInputIntegration(m_x11Display, this);
0067         m_xinputIntegration->init();
0068         if (!m_xinputIntegration->hasXinput()) {
0069             delete m_xinputIntegration;
0070             m_xinputIntegration = nullptr;
0071         } else {
0072             connect(kwinApp(), &Application::workspaceCreated, m_xinputIntegration, &XInputIntegration::startListening);
0073         }
0074     }
0075 #endif
0076 
0077     setSupportsGammaControl(true);
0078 }
0079 
0080 X11StandalonePlatform::~X11StandalonePlatform()
0081 {
0082     if (m_openGLFreezeProtectionThread) {
0083         m_openGLFreezeProtectionThread->quit();
0084         m_openGLFreezeProtectionThread->wait();
0085         delete m_openGLFreezeProtectionThread;
0086     }
0087     if (isReady()) {
0088         XRenderUtils::cleanup();
0089     }
0090 }
0091 
0092 void X11StandalonePlatform::init()
0093 {
0094     if (!QX11Info::isPlatformX11()) {
0095         emit initFailed();
0096         return;
0097     }
0098     XRenderUtils::init(kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
0099     setReady(true);
0100     emit screensQueried();
0101 }
0102 
0103 Screens *X11StandalonePlatform::createScreens(QObject *parent)
0104 {
0105     return new XRandRScreens(this, parent);
0106 }
0107 
0108 OpenGLBackend *X11StandalonePlatform::createOpenGLBackend()
0109 {
0110     switch (options->glPlatformInterface()) {
0111 #if HAVE_EPOXY_GLX
0112     case GlxPlatformInterface:
0113         if (hasGlx()) {
0114             return new GlxBackend(m_x11Display);
0115         } else {
0116             qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead.";
0117             // no break, needs fall-through
0118             Q_FALLTHROUGH();
0119         }
0120 #endif
0121     case EglPlatformInterface:
0122         return new EglOnXBackend(m_x11Display);
0123     default:
0124         // no backend available
0125         return nullptr;
0126     }
0127 }
0128 
0129 Edge *X11StandalonePlatform::createScreenEdge(ScreenEdges *edges)
0130 {
0131     if (m_screenEdgesFilter.isNull()) {
0132         m_screenEdgesFilter.reset(new ScreenEdgesFilter);
0133     }
0134     return new WindowBasedEdge(edges);
0135 }
0136 
0137 void X11StandalonePlatform::createPlatformCursor(QObject *parent)
0138 {
0139     auto c = new X11Cursor(parent, m_xinputIntegration != nullptr);
0140 #if HAVE_X11_XINPUT
0141     if (m_xinputIntegration) {
0142         m_xinputIntegration->setCursor(c);
0143         // we know we have xkb already
0144         auto xkb = input()->keyboard()->xkb();
0145         xkb->setConfig(kwinApp()->kxkbConfig());
0146         xkb->reconfigure();
0147     }
0148 #endif
0149 }
0150 
0151 bool X11StandalonePlatform::requiresCompositing() const
0152 {
0153     return false;
0154 }
0155 
0156 bool X11StandalonePlatform::openGLCompositingIsBroken() const
0157 {
0158     const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString()));
0159     return KConfigGroup(kwinApp()->config(), "Compositing").readEntry(unsafeKey, false);
0160 }
0161 
0162 QString X11StandalonePlatform::compositingNotPossibleReason() const
0163 {
0164     // first off, check whether we figured that we'll crash on detection because of a buggy driver
0165     KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing");
0166     const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString()));
0167     if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") &&
0168         gl_workaround_group.readEntry(unsafeKey, false))
0169         return i18n("<b>OpenGL compositing (the default) has crashed KWin in the past.</b><br>"
0170                     "This was most likely due to a driver bug."
0171                     "<p>If you think that you have meanwhile upgraded to a stable driver,<br>"
0172                     "you can reset this protection but <b>be aware that this might result in an immediate crash!</b></p>"
0173                     "<p>Alternatively, you might want to use the XRender backend instead.</p>");
0174 
0175     if (!Xcb::Extensions::self()->isCompositeAvailable() || !Xcb::Extensions::self()->isDamageAvailable()) {
0176         return i18n("Required X extensions (XComposite and XDamage) are not available.");
0177     }
0178 #if !defined( KWIN_HAVE_XRENDER_COMPOSITING )
0179     if (!hasGlx())
0180         return i18n("GLX/OpenGL are not available and only OpenGL support is compiled.");
0181 #else
0182     if (!(hasGlx()
0183             || (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable()))) {
0184         return i18n("GLX/OpenGL and XRender/XFixes are not available.");
0185     }
0186 #endif
0187     return QString();
0188 }
0189 
0190 bool X11StandalonePlatform::compositingPossible() const
0191 {
0192     // first off, check whether we figured that we'll crash on detection because of a buggy driver
0193     KConfigGroup gl_workaround_group(kwinApp()->config(), "Compositing");
0194     const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString()));
0195     if (gl_workaround_group.readEntry("Backend", "OpenGL") == QLatin1String("OpenGL") &&
0196         gl_workaround_group.readEntry(unsafeKey, false))
0197         return false;
0198 
0199 
0200     if (!Xcb::Extensions::self()->isCompositeAvailable()) {
0201         qCDebug(KWIN_CORE) << "No composite extension available";
0202         return false;
0203     }
0204     if (!Xcb::Extensions::self()->isDamageAvailable()) {
0205         qCDebug(KWIN_CORE) << "No damage extension available";
0206         return false;
0207     }
0208     if (hasGlx())
0209         return true;
0210 #ifdef KWIN_HAVE_XRENDER_COMPOSITING
0211     if (Xcb::Extensions::self()->isRenderAvailable() && Xcb::Extensions::self()->isFixesAvailable())
0212         return true;
0213 #endif
0214     if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
0215         return true;
0216     } else if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) {
0217         return true;
0218     }
0219     qCDebug(KWIN_CORE) << "No OpenGL or XRender/XFixes support";
0220     return false;
0221 }
0222 
0223 bool X11StandalonePlatform::hasGlx()
0224 {
0225     return Xcb::Extensions::self()->hasGlx();
0226 }
0227 
0228 void X11StandalonePlatform::createOpenGLSafePoint(OpenGLSafePoint safePoint)
0229 {
0230     const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString()));
0231     auto group = KConfigGroup(kwinApp()->config(), "Compositing");
0232     switch (safePoint) {
0233     case OpenGLSafePoint::PreInit:
0234         group.writeEntry(unsafeKey, true);
0235         group.sync();
0236         // Deliberately continue with PreFrame
0237         Q_FALLTHROUGH();
0238     case OpenGLSafePoint::PreFrame:
0239         if (m_openGLFreezeProtectionThread == nullptr) {
0240             Q_ASSERT(m_openGLFreezeProtection == nullptr);
0241             m_openGLFreezeProtectionThread = new QThread(this);
0242             m_openGLFreezeProtectionThread->setObjectName("FreezeDetector");
0243             m_openGLFreezeProtectionThread->start();
0244             m_openGLFreezeProtection = new QTimer;
0245             m_openGLFreezeProtection->setInterval(15000);
0246             m_openGLFreezeProtection->setSingleShot(true);
0247             m_openGLFreezeProtection->start();
0248             const QString configName = kwinApp()->config()->name();
0249             m_openGLFreezeProtection->moveToThread(m_openGLFreezeProtectionThread);
0250             connect(m_openGLFreezeProtection, &QTimer::timeout, m_openGLFreezeProtection,
0251                 [configName] {
0252                     const QString unsafeKey(QLatin1String("OpenGLIsUnsafe") + (kwinApp()->isX11MultiHead() ? QString::number(kwinApp()->x11ScreenNumber()) : QString()));
0253                     auto group = KConfigGroup(KSharedConfig::openConfig(configName), "Compositing");
0254                     group.writeEntry(unsafeKey, true);
0255                     group.sync();
0256                     KCrash::setDrKonqiEnabled(false);
0257                     qFatal("Freeze in OpenGL initialization detected");
0258                 }, Qt::DirectConnection);
0259         } else {
0260             Q_ASSERT(m_openGLFreezeProtection);
0261             QMetaObject::invokeMethod(m_openGLFreezeProtection, "start", Qt::QueuedConnection);
0262         }
0263         break;
0264     case OpenGLSafePoint::PostInit:
0265         group.writeEntry(unsafeKey, false);
0266         group.sync();
0267         // Deliberately continue with PostFrame
0268         Q_FALLTHROUGH();
0269     case OpenGLSafePoint::PostFrame:
0270         QMetaObject::invokeMethod(m_openGLFreezeProtection, "stop", Qt::QueuedConnection);
0271         break;
0272     case OpenGLSafePoint::PostLastGuardedFrame:
0273         m_openGLFreezeProtection->deleteLater();
0274         m_openGLFreezeProtection = nullptr;
0275         m_openGLFreezeProtectionThread->quit();
0276         m_openGLFreezeProtectionThread->wait();
0277         delete m_openGLFreezeProtectionThread;
0278         m_openGLFreezeProtectionThread = nullptr;
0279         break;
0280     }
0281 }
0282 
0283 PlatformCursorImage X11StandalonePlatform::cursorImage() const
0284 {
0285     auto c = kwinApp()->x11Connection();
0286     QScopedPointer<xcb_xfixes_get_cursor_image_reply_t, QScopedPointerPodDeleter> cursor(
0287         xcb_xfixes_get_cursor_image_reply(c,
0288                                           xcb_xfixes_get_cursor_image_unchecked(c),
0289                                           nullptr));
0290     if (cursor.isNull()) {
0291         return PlatformCursorImage();
0292     }
0293 
0294     QImage qcursorimg((uchar *) xcb_xfixes_get_cursor_image_cursor_image(cursor.data()), cursor->width, cursor->height,
0295                       QImage::Format_ARGB32_Premultiplied);
0296     // deep copy of image as the data is going to be freed
0297     return PlatformCursorImage(qcursorimg.copy(), QPoint(cursor->xhot, cursor->yhot));
0298 }
0299 
0300 void X11StandalonePlatform::doHideCursor()
0301 {
0302     xcb_xfixes_hide_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
0303 }
0304 
0305 void X11StandalonePlatform::doShowCursor()
0306 {
0307     xcb_xfixes_show_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
0308 }
0309 
0310 void X11StandalonePlatform::startInteractiveWindowSelection(std::function<void(KWin::Toplevel*)> callback, const QByteArray &cursorName)
0311 {
0312     if (m_windowSelector.isNull()) {
0313         m_windowSelector.reset(new WindowSelector);
0314     }
0315     m_windowSelector->start(callback, cursorName);
0316 }
0317 
0318 void X11StandalonePlatform::startInteractivePositionSelection(std::function<void (const QPoint &)> callback)
0319 {
0320     if (m_windowSelector.isNull()) {
0321         m_windowSelector.reset(new WindowSelector);
0322     }
0323     m_windowSelector->start(callback);
0324 }
0325 
0326 void X11StandalonePlatform::setupActionForGlobalAccel(QAction *action)
0327 {
0328     connect(action, &QAction::triggered, kwinApp(), [action] {
0329         QVariant timestamp = action->property("org.kde.kglobalaccel.activationTimestamp");
0330         bool ok = false;
0331         const quint32 t = timestamp.toULongLong(&ok);
0332         if (ok) {
0333             kwinApp()->setX11Time(t);
0334         }
0335     });
0336 }
0337 
0338 OverlayWindow *X11StandalonePlatform::createOverlayWindow()
0339 {
0340     return new OverlayWindowX11();
0341 }
0342 
0343 OutlineVisual *X11StandalonePlatform::createOutline(Outline *outline)
0344 {
0345     // first try composited Outline
0346     auto ret = Platform::createOutline(outline);
0347     if (!ret) {
0348         ret = new NonCompositedOutlineVisual(outline);
0349     }
0350     return ret;
0351 }
0352 
0353 Decoration::Renderer *X11StandalonePlatform::createDecorationRenderer(Decoration::DecoratedClientImpl *client)
0354 {
0355     auto renderer = Platform::createDecorationRenderer(client);
0356     if (!renderer) {
0357         renderer = new Decoration::X11Renderer(client);
0358     }
0359     return renderer;
0360 }
0361 
0362 void X11StandalonePlatform::invertScreen()
0363 {
0364     using namespace Xcb::RandR;
0365     bool succeeded = false;
0366 
0367     if (Xcb::Extensions::self()->isRandrAvailable()) {
0368         const auto active_client = workspace()->activeClient();
0369         ScreenResources res((active_client && active_client->window() != XCB_WINDOW_NONE) ? active_client->window() : rootWindow());
0370 
0371         if (!res.isNull()) {
0372             for (int j = 0; j < res->num_crtcs; ++j) {
0373                 auto crtc = res.crtcs()[j];
0374                 CrtcGamma gamma(crtc);
0375                 if (gamma.isNull()) {
0376                     continue;
0377                 }
0378                 if (gamma->size) {
0379                     qCDebug(KWIN_CORE) << "inverting screen using xcb_randr_set_crtc_gamma";
0380                     const int half = gamma->size / 2 + 1;
0381 
0382                     uint16_t *red = gamma.red();
0383                     uint16_t *green = gamma.green();
0384                     uint16_t *blue = gamma.blue();
0385                     for (int i = 0; i < half; ++i) {
0386                         auto invert = [&gamma, i](uint16_t *ramp) {
0387                             qSwap(ramp[i], ramp[gamma->size - 1 - i]);
0388                         };
0389                         invert(red);
0390                         invert(green);
0391                         invert(blue);
0392                     }
0393                     xcb_randr_set_crtc_gamma(connection(), crtc, gamma->size, red, green, blue);
0394                     succeeded = true;
0395                 }
0396             }
0397         }
0398     }
0399     if (!succeeded) {
0400         Platform::invertScreen();
0401     }
0402 }
0403 
0404 void X11StandalonePlatform::createEffectsHandler(Compositor *compositor, Scene *scene)
0405 {
0406     new EffectsHandlerImplX11(compositor, scene);
0407 }
0408 
0409 QVector<CompositingType> X11StandalonePlatform::supportedCompositors() const
0410 {
0411     QVector<CompositingType> compositors;
0412 #if HAVE_EPOXY_GLX
0413     compositors << OpenGLCompositing;
0414 #endif
0415 #ifdef KWIN_HAVE_XRENDER_COMPOSITING
0416     compositors << XRenderCompositing;
0417 #endif
0418     compositors << NoCompositing;
0419     return compositors;
0420 }
0421 
0422 void X11StandalonePlatform::initOutputs()
0423 {
0424     doUpdateOutputs<Xcb::RandR::ScreenResources>();
0425 }
0426 
0427 void X11StandalonePlatform::updateOutputs()
0428 {
0429     doUpdateOutputs<Xcb::RandR::CurrentResources>();
0430 }
0431 
0432 template <typename T>
0433 void X11StandalonePlatform::doUpdateOutputs()
0434 {
0435     auto fallback = [this]() {
0436         auto *o = new X11Output(this);
0437         o->setGammaRampSize(0);
0438         o->setRefreshRate(-1.0f);
0439         o->setName(QStringLiteral("Xinerama"));
0440         m_outputs << o;
0441     };
0442 
0443     // TODO: instead of resetting all outputs, check if new output is added/removed
0444     //       or still available and leave still available outputs in m_outputs
0445     //       untouched (like in DRM backend)
0446     qDeleteAll(m_outputs);
0447     m_outputs.clear();
0448 
0449     if (!Xcb::Extensions::self()->isRandrAvailable()) {
0450         fallback();
0451         return;
0452     }
0453     T resources(rootWindow());
0454     if (resources.isNull()) {
0455         fallback();
0456         return;
0457     }
0458     xcb_randr_crtc_t *crtcs = resources.crtcs();
0459     xcb_randr_mode_info_t *modes = resources.modes();
0460 
0461     QVector<Xcb::RandR::CrtcInfo> infos(resources->num_crtcs);
0462     for (int i = 0; i < resources->num_crtcs; ++i) {
0463         infos[i] = Xcb::RandR::CrtcInfo(crtcs[i], resources->config_timestamp);
0464     }
0465 
0466     for (int i = 0; i < resources->num_crtcs; ++i) {
0467         Xcb::RandR::CrtcInfo info(infos.at(i));
0468 
0469         xcb_randr_output_t *outputs = info.outputs();
0470         QVector<Xcb::RandR::OutputInfo> outputInfos(outputs ? resources->num_outputs : 0);
0471         if (outputs) {
0472             for (int i = 0; i < resources->num_outputs; ++i) {
0473                 outputInfos[i] = Xcb::RandR::OutputInfo(outputs[i], resources->config_timestamp);
0474             }
0475         }
0476 
0477         float refreshRate = -1.0f;
0478         for (int j = 0; j < resources->num_modes; ++j) {
0479             if (info->mode == modes[j].id) {
0480                 if (modes[j].htotal != 0 && modes[j].vtotal != 0) { // BUG 313996
0481                     // refresh rate calculation - WTF was wikipedia 1998 when I needed it?
0482                     int dotclock = modes[j].dot_clock,
0483                           vtotal = modes[j].vtotal;
0484                     if (modes[j].mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE)
0485                         dotclock *= 2;
0486                     if (modes[j].mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN)
0487                         vtotal *= 2;
0488                     refreshRate = dotclock/float(modes[j].htotal*vtotal);
0489                 }
0490                 break; // found mode
0491             }
0492         }
0493 
0494         const QRect geo = info.rect();
0495         if (geo.isValid()) {
0496             xcb_randr_crtc_t crtc = crtcs[i];
0497 
0498             // TODO: Perhaps the output has to save the inherited gamma ramp and
0499             // restore it during tear down. Currently neither standalone x11 nor
0500             // drm platform do this.
0501             Xcb::RandR::CrtcGamma gamma(crtc);
0502 
0503             auto *o = new X11Output(this);
0504             o->setCrtc(crtc);
0505             o->setGammaRampSize(gamma.isNull() ? 0 : gamma->size);
0506             o->setGeometry(geo);
0507             o->setRefreshRate(refreshRate * 1000);
0508 
0509             QString name;
0510             for (int j = 0; j < info->num_outputs; ++j) {
0511                 Xcb::RandR::OutputInfo outputInfo(outputInfos.at(j));
0512                 if (crtc == outputInfo->crtc) {
0513                     name = outputInfo.name();
0514                     break;
0515                 }
0516             }
0517             o->setName(name);
0518             m_outputs << o;
0519         }
0520     }
0521 
0522     if (m_outputs.isEmpty()) {
0523         fallback();
0524     }
0525 }
0526 
0527 Outputs X11StandalonePlatform::outputs() const
0528 {
0529     return m_outputs;
0530 }
0531 
0532 Outputs X11StandalonePlatform::enabledOutputs() const
0533 {
0534     return m_outputs;
0535 }
0536 
0537 }