/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2016 Martin Gräßlin SPDX-License-Identifier: GPL-2.0-or-later */ #include "x11_standalone_backend.h" #include "config-kwin.h" #include "atoms.h" #include "core/session.h" #include "x11_standalone_cursor.h" #include "x11_standalone_edge.h" #include "x11_standalone_placeholderoutput.h" #include "x11_standalone_windowselector.h" #if HAVE_GLX #include "x11_standalone_glx_backend.h" #endif #if HAVE_X11_XINPUT #include "x11_standalone_xinputintegration.h" #endif #include "core/renderloop.h" #include "keyboard_input.h" #include "opengl/egldisplay.h" #include "options.h" #include "utils/c_ptr.h" #include "utils/edid.h" #include "utils/xcbutils.h" #include "window.h" #include "workspace.h" #include "x11_standalone_effects.h" #include "x11_standalone_egl_backend.h" #include "x11_standalone_keyboard.h" #include "x11_standalone_logging.h" #include "x11_standalone_non_composited_outline.h" #include "x11_standalone_output.h" #include "x11_standalone_screenedges_filter.h" #include "xkb.h" #include "../common/kwinxrenderutils.h" #include #include #include #include #include #include namespace KWin { class XrandrEventFilter : public X11EventFilter { public: explicit XrandrEventFilter(X11StandaloneBackend *backend); bool event(xcb_generic_event_t *event) override; private: X11StandaloneBackend *m_backend; }; XrandrEventFilter::XrandrEventFilter(X11StandaloneBackend *backend) : X11EventFilter(Xcb::Extensions::self()->randrNotifyEvent()) , m_backend(backend) { } bool XrandrEventFilter::event(xcb_generic_event_t *event) { Q_ASSERT((event->response_type & ~0x80) == Xcb::Extensions::self()->randrNotifyEvent()); // let's try to gather a few XRandR events, unlikely that there is just one m_backend->scheduleUpdateOutputs(); // update default screen auto *xrrEvent = reinterpret_cast(event); xcb_screen_t *screen = Xcb::defaultScreen(); if (xrrEvent->rotation & (XCB_RANDR_ROTATION_ROTATE_90 | XCB_RANDR_ROTATION_ROTATE_270)) { screen->width_in_pixels = xrrEvent->height; screen->height_in_pixels = xrrEvent->width; screen->width_in_millimeters = xrrEvent->mheight; screen->height_in_millimeters = xrrEvent->mwidth; } else { screen->width_in_pixels = xrrEvent->width; screen->height_in_pixels = xrrEvent->height; screen->width_in_millimeters = xrrEvent->mwidth; screen->height_in_millimeters = xrrEvent->mheight; } return false; } X11StandaloneBackend::X11StandaloneBackend(QObject *parent) : OutputBackend(parent) , m_updateOutputsTimer(std::make_unique()) , m_x11Display(QX11Info::display()) , m_renderLoop(std::make_unique(nullptr)) { #if HAVE_X11_XINPUT if (!qEnvironmentVariableIsSet("KWIN_NO_XI2")) { m_xinputIntegration = std::make_unique(m_x11Display, this); m_xinputIntegration->init(); if (!m_xinputIntegration->hasXinput()) { m_xinputIntegration.reset(); } else { connect(kwinApp(), &Application::workspaceCreated, m_xinputIntegration.get(), &XInputIntegration::startListening); } } #endif m_updateOutputsTimer->setSingleShot(true); connect(m_updateOutputsTimer.get(), &QTimer::timeout, this, &X11StandaloneBackend::updateOutputs); m_keyboard = std::make_unique(); } X11StandaloneBackend::~X11StandaloneBackend() { m_eglDisplay.reset(); XRenderUtils::cleanup(); } ::Display *X11StandaloneBackend::display() const { return m_x11Display; } xcb_connection_t *X11StandaloneBackend::connection() const { return kwinApp()->x11Connection(); } xcb_window_t X11StandaloneBackend::rootWindow() const { return kwinApp()->x11RootWindow(); } bool X11StandaloneBackend::initialize() { if (!QX11Info::isPlatformX11()) { return false; } XRenderUtils::init(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); initOutputs(); if (Xcb::Extensions::self()->isRandrAvailable()) { m_randrEventFilter = std::make_unique(this); } connect(Cursors::self(), &Cursors::hiddenChanged, this, &X11StandaloneBackend::updateCursor); return true; } std::unique_ptr X11StandaloneBackend::createOpenGLBackend() { switch (options->glPlatformInterface()) { #if HAVE_GLX case GlxPlatformInterface: if (hasGlx()) { return std::make_unique(m_x11Display, this); } else { qCWarning(KWIN_X11STANDALONE) << "Glx not available, trying EGL instead."; // no break, needs fall-through Q_FALLTHROUGH(); } #endif case EglPlatformInterface: return std::make_unique(m_x11Display, this); default: // no backend available return nullptr; } } std::unique_ptr X11StandaloneBackend::createScreenEdge(ScreenEdges *edges) { if (!m_screenEdgesFilter) { m_screenEdgesFilter = std::make_unique(); } return std::make_unique(edges); } std::unique_ptr X11StandaloneBackend::createPlatformCursor() { #if HAVE_X11_XINPUT auto c = std::make_unique(m_xinputIntegration != nullptr); if (m_xinputIntegration) { m_xinputIntegration->setCursor(c.get()); // we know we have xkb already auto xkb = input()->keyboard()->xkb(); xkb->setConfig(kwinApp()->kxkbConfig()); xkb->reconfigure(); } return c; #else return std::make_unique(false); #endif } bool X11StandaloneBackend::hasGlx() { return Xcb::Extensions::self()->hasGlx(); } PlatformCursorImage X11StandaloneBackend::cursorImage() const { auto c = kwinApp()->x11Connection(); UniqueCPtr cursor( xcb_xfixes_get_cursor_image_reply(c, xcb_xfixes_get_cursor_image_unchecked(c), nullptr)); if (!cursor) { return PlatformCursorImage(); } QImage qcursorimg((uchar *)xcb_xfixes_get_cursor_image_cursor_image(cursor.get()), cursor->width, cursor->height, QImage::Format_ARGB32_Premultiplied); // deep copy of image as the data is going to be freed return PlatformCursorImage(qcursorimg.copy(), QPoint(cursor->xhot, cursor->yhot)); } void X11StandaloneBackend::updateCursor() { if (Cursors::self()->isCursorHidden()) { xcb_xfixes_hide_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } else { xcb_xfixes_show_cursor(kwinApp()->x11Connection(), kwinApp()->x11RootWindow()); } } void X11StandaloneBackend::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { if (!m_windowSelector) { m_windowSelector = std::make_unique(); } m_windowSelector->start(callback, cursorName); } void X11StandaloneBackend::startInteractivePositionSelection(std::function callback) { if (!m_windowSelector) { m_windowSelector = std::make_unique(); } m_windowSelector->start(callback); } std::unique_ptr X11StandaloneBackend::createOutline(Outline *outline) { return std::make_unique(outline); } void X11StandaloneBackend::createEffectsHandler(Compositor *compositor, WorkspaceScene *scene) { new EffectsHandlerX11(compositor, scene); } QList X11StandaloneBackend::supportedCompositors() const { QList compositors; #if HAVE_GLX compositors << OpenGLCompositing; #endif compositors << NoCompositing; return compositors; } void X11StandaloneBackend::initOutputs() { doUpdateOutputs(); updateRefreshRate(); } void X11StandaloneBackend::scheduleUpdateOutputs() { m_updateOutputsTimer->start(); } void X11StandaloneBackend::updateOutputs() { doUpdateOutputs(); updateRefreshRate(); } template void X11StandaloneBackend::doUpdateOutputs() { QList changed; QList added; QList removed = m_outputs; if (Xcb::Extensions::self()->isRandrAvailable()) { T resources(rootWindow()); if (!resources.isNull()) { std::span crtcs(resources.crtcs(), resources->num_crtcs); for (auto crtc : crtcs) { Xcb::RandR::CrtcInfo info(crtc, resources->config_timestamp); const QRect geometry = info.rect(); if (!geometry.isValid()) { continue; } float refreshRate = -1.0f; for (auto mode : std::span(resources.modes(), resources->num_modes)) { if (info->mode == mode.id) { if (mode.htotal != 0 && mode.vtotal != 0) { // BUG 313996 // refresh rate calculation - WTF was wikipedia 1998 when I needed it? int dotclock = mode.dot_clock, vtotal = mode.vtotal; if (mode.mode_flags & XCB_RANDR_MODE_FLAG_INTERLACE) { dotclock *= 2; } if (mode.mode_flags & XCB_RANDR_MODE_FLAG_DOUBLE_SCAN) { vtotal *= 2; } refreshRate = dotclock / float(mode.htotal * vtotal); } break; // found mode } } for (auto xcbOutput : std::span(info.outputs(), info->num_outputs)) { Xcb::RandR::OutputInfo outputInfo(xcbOutput, resources->config_timestamp); if (outputInfo->crtc != crtc) { continue; } X11Output *output = findX11Output(outputInfo.name()); if (output) { changed.append(output); removed.removeOne(output); } else { output = new X11Output(this); added.append(output); } // TODO: Perhaps the output has to save the inherited gamma ramp and // restore it during tear down. Currently neither standalone x11 nor // drm platform do this. Xcb::RandR::CrtcGamma gamma(crtc); output->setRenderLoop(m_renderLoop.get()); output->setCrtc(crtc); output->setGammaRampSize(gamma.isNull() ? 0 : gamma->size); auto it = std::find(crtcs.begin(), crtcs.end(), crtc); int crtcIndex = std::distance(crtcs.begin(), it); output->setXineramaNumber(crtcIndex); QSize physicalSize(outputInfo->mm_width, outputInfo->mm_height); switch (info->rotation) { case XCB_RANDR_ROTATION_ROTATE_0: case XCB_RANDR_ROTATION_ROTATE_180: break; case XCB_RANDR_ROTATION_ROTATE_90: case XCB_RANDR_ROTATION_ROTATE_270: physicalSize.transpose(); break; case XCB_RANDR_ROTATION_REFLECT_X: case XCB_RANDR_ROTATION_REFLECT_Y: break; } X11Output::Information information{ .name = outputInfo.name(), .physicalSize = physicalSize, }; auto edidProperty = Xcb::RandR::OutputProperty(xcbOutput, atoms->edid, XCB_ATOM_INTEGER, 0, 100, false, false); bool ok; if (auto data = edidProperty.toByteArray(&ok); ok && !data.isEmpty()) { if (auto edid = Edid(data, edidProperty.data()->num_items); edid.isValid()) { information.manufacturer = edid.manufacturerString(); information.model = edid.monitorName(); information.serialNumber = edid.serialNumber(); information.edid = edid; } } auto mode = std::make_shared(geometry.size(), refreshRate * 1000); X11Output::State state = output->m_state; state.modes = {mode}; state.currentMode = mode; state.position = geometry.topLeft(); output->setInformation(information); output->setState(state); break; } } } } // The workspace handles having no outputs poorly. If the last output is about to be // removed, create a dummy output to avoid crashing. if (changed.isEmpty() && added.isEmpty()) { auto dummyOutput = new X11PlaceholderOutput(this); m_outputs << dummyOutput; Q_EMIT outputAdded(dummyOutput); dummyOutput->updateEnabled(true); } // Process new outputs. Note new outputs must be introduced before removing any other outputs. for (Output *output : std::as_const(added)) { m_outputs.append(output); Q_EMIT outputAdded(output); if (auto placeholderOutput = qobject_cast(output)) { placeholderOutput->updateEnabled(true); } else if (auto nativeOutput = qobject_cast(output)) { nativeOutput->updateEnabled(true); } } // Outputs have to be removed last to avoid the case where there are no enabled outputs. for (Output *output : std::as_const(removed)) { m_outputs.removeOne(output); if (auto placeholderOutput = qobject_cast(output)) { placeholderOutput->updateEnabled(false); } else if (auto nativeOutput = qobject_cast(output)) { nativeOutput->updateEnabled(false); } Q_EMIT outputRemoved(output); output->unref(); } // Make sure that the position of an output in m_outputs matches its xinerama index, there // are X11 protocols that use xinerama indices to identify outputs. std::sort(m_outputs.begin(), m_outputs.end(), [](const Output *a, const Output *b) { const auto xa = qobject_cast(a); if (!xa) { return false; } const auto xb = qobject_cast(b); if (!xb) { return true; } return xa->xineramaNumber() < xb->xineramaNumber(); }); Q_EMIT outputsQueried(); } X11Output *X11StandaloneBackend::findX11Output(const QString &name) const { for (Output *output : m_outputs) { if (output->name() == name) { return qobject_cast(output); } } return nullptr; } Outputs X11StandaloneBackend::outputs() const { return m_outputs; } X11Keyboard *X11StandaloneBackend::keyboard() const { return m_keyboard.get(); } RenderLoop *X11StandaloneBackend::renderLoop() const { return m_renderLoop.get(); } static bool refreshRate_compare(const Output *first, const Output *smallest) { return first->refreshRate() < smallest->refreshRate(); } static int currentRefreshRate() { static const int refreshRate = qEnvironmentVariableIntValue("KWIN_X11_REFRESH_RATE"); if (refreshRate) { return refreshRate; } const QList outputs = kwinApp()->outputBackend()->outputs(); if (outputs.isEmpty()) { return 60000; } static const QString syncDisplayDevice = qEnvironmentVariable("__GL_SYNC_DISPLAY_DEVICE"); if (!syncDisplayDevice.isEmpty()) { for (const Output *output : outputs) { if (output->name() == syncDisplayDevice) { return output->refreshRate(); } } } auto syncIt = std::min_element(outputs.begin(), outputs.end(), refreshRate_compare); return (*syncIt)->refreshRate(); } void X11StandaloneBackend::updateRefreshRate() { int refreshRate = currentRefreshRate(); if (refreshRate <= 0) { qCWarning(KWIN_X11STANDALONE) << "Bogus refresh rate" << refreshRate; refreshRate = 60000; } m_renderLoop->setRefreshRate(refreshRate); } void X11StandaloneBackend::setEglDisplay(std::unique_ptr &&display) { m_eglDisplay = std::move(display); } EglDisplay *X11StandaloneBackend::sceneEglDisplayObject() const { return m_eglDisplay.get(); } } #include "moc_x11_standalone_backend.cpp"