File indexing completed on 2024-11-10 04:56:37

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "x11_windowed_output.h"
0010 #include "../common/kwinxrenderutils.h"
0011 #include "x11_windowed_backend.h"
0012 #include "x11_windowed_logging.h"
0013 
0014 #include <config-kwin.h>
0015 
0016 #include "compositor.h"
0017 #include "core/graphicsbuffer.h"
0018 #include "core/outputlayer.h"
0019 #include "core/renderbackend.h"
0020 #include "core/renderloop_p.h"
0021 
0022 #include <NETWM>
0023 
0024 #if HAVE_X11_XINPUT
0025 #include <X11/extensions/XInput2.h>
0026 #endif
0027 
0028 #include <QIcon>
0029 #include <QPainter>
0030 
0031 #include <drm_fourcc.h>
0032 #include <xcb/dri3.h>
0033 #include <xcb/shm.h>
0034 
0035 namespace KWin
0036 {
0037 
0038 X11WindowedBuffer::X11WindowedBuffer(X11WindowedOutput *output, xcb_pixmap_t pixmap, GraphicsBuffer *graphicsBuffer)
0039     : m_output(output)
0040     , m_buffer(graphicsBuffer)
0041     , m_pixmap(pixmap)
0042 {
0043     connect(graphicsBuffer, &GraphicsBuffer::destroyed, this, &X11WindowedBuffer::defunct);
0044 }
0045 
0046 X11WindowedBuffer::~X11WindowedBuffer()
0047 {
0048     m_buffer->disconnect(this);
0049     xcb_free_pixmap(m_output->backend()->connection(), m_pixmap);
0050     unlock();
0051 }
0052 
0053 GraphicsBuffer *X11WindowedBuffer::buffer() const
0054 {
0055     return m_buffer;
0056 }
0057 
0058 xcb_pixmap_t X11WindowedBuffer::pixmap() const
0059 {
0060     return m_pixmap;
0061 }
0062 
0063 void X11WindowedBuffer::lock()
0064 {
0065     if (!m_locked) {
0066         m_locked = true;
0067         m_buffer->ref();
0068     }
0069 }
0070 
0071 void X11WindowedBuffer::unlock()
0072 {
0073     if (m_locked) {
0074         m_locked = false;
0075         m_buffer->unref();
0076     }
0077 }
0078 
0079 X11WindowedCursor::X11WindowedCursor(X11WindowedOutput *output)
0080     : m_output(output)
0081 {
0082 }
0083 
0084 X11WindowedCursor::~X11WindowedCursor()
0085 {
0086     if (m_handle != XCB_CURSOR_NONE) {
0087         xcb_free_cursor(m_output->backend()->connection(), m_handle);
0088         m_handle = XCB_CURSOR_NONE;
0089     }
0090 }
0091 
0092 void X11WindowedCursor::update(const QImage &image, const QPointF &hotspot)
0093 {
0094     X11WindowedBackend *backend = m_output->backend();
0095 
0096     xcb_connection_t *connection = backend->connection();
0097     xcb_pixmap_t pix = XCB_PIXMAP_NONE;
0098     xcb_gcontext_t gc = XCB_NONE;
0099     xcb_cursor_t cid = XCB_CURSOR_NONE;
0100 
0101     if (!image.isNull()) {
0102         pix = xcb_generate_id(connection);
0103         gc = xcb_generate_id(connection);
0104         cid = xcb_generate_id(connection);
0105 
0106         // right now on X we only have one scale between all screens, and we know we will have at least one screen
0107         const qreal outputScale = 1;
0108         const QSize targetSize = image.size() * outputScale / image.devicePixelRatio();
0109         const QImage img = image.scaled(targetSize, Qt::KeepAspectRatio).convertedTo(QImage::Format_ARGB32_Premultiplied);
0110 
0111         xcb_create_pixmap(connection, 32, pix, backend->screen()->root, img.width(), img.height());
0112         xcb_create_gc(connection, gc, pix, 0, nullptr);
0113 
0114         xcb_put_image(connection, XCB_IMAGE_FORMAT_Z_PIXMAP, pix, gc, img.width(), img.height(), 0, 0, 0, 32, img.sizeInBytes(), img.constBits());
0115 
0116         XRenderPicture pic(pix, 32);
0117         xcb_render_create_cursor(connection, cid, pic, qRound(hotspot.x() * outputScale), qRound(hotspot.y() * outputScale));
0118     }
0119 
0120     xcb_change_window_attributes(connection, m_output->window(), XCB_CW_CURSOR, &cid);
0121 
0122     if (pix) {
0123         xcb_free_pixmap(connection, pix);
0124     }
0125     if (gc) {
0126         xcb_free_gc(connection, gc);
0127     }
0128 
0129     if (m_handle) {
0130         xcb_free_cursor(connection, m_handle);
0131     }
0132     m_handle = cid;
0133     xcb_flush(connection);
0134 }
0135 
0136 X11WindowedOutput::X11WindowedOutput(X11WindowedBackend *backend)
0137     : Output(backend)
0138     , m_renderLoop(std::make_unique<RenderLoop>(this))
0139     , m_backend(backend)
0140 {
0141     m_window = xcb_generate_id(m_backend->connection());
0142 
0143     static int identifier = -1;
0144     identifier++;
0145     setInformation(Information{
0146         .name = QStringLiteral("X11-%1").arg(identifier),
0147     });
0148 }
0149 
0150 X11WindowedOutput::~X11WindowedOutput()
0151 {
0152     m_buffers.clear();
0153 
0154     xcb_present_select_input(m_backend->connection(), m_presentEvent, m_window, 0);
0155     xcb_unmap_window(m_backend->connection(), m_window);
0156     xcb_destroy_window(m_backend->connection(), m_window);
0157     xcb_flush(m_backend->connection());
0158 }
0159 
0160 QRegion X11WindowedOutput::exposedArea() const
0161 {
0162     return m_exposedArea;
0163 }
0164 
0165 void X11WindowedOutput::addExposedArea(const QRect &rect)
0166 {
0167     m_exposedArea += rect;
0168 }
0169 
0170 void X11WindowedOutput::clearExposedArea()
0171 {
0172     m_exposedArea = QRegion();
0173 }
0174 
0175 RenderLoop *X11WindowedOutput::renderLoop() const
0176 {
0177     return m_renderLoop.get();
0178 }
0179 
0180 X11WindowedBackend *X11WindowedOutput::backend() const
0181 {
0182     return m_backend;
0183 }
0184 
0185 X11WindowedCursor *X11WindowedOutput::cursor() const
0186 {
0187     return m_cursor.get();
0188 }
0189 
0190 xcb_window_t X11WindowedOutput::window() const
0191 {
0192     return m_window;
0193 }
0194 
0195 int X11WindowedOutput::depth() const
0196 {
0197     return m_backend->screen()->root_depth;
0198 }
0199 
0200 QPoint X11WindowedOutput::hostPosition() const
0201 {
0202     return m_hostPosition;
0203 }
0204 
0205 void X11WindowedOutput::init(const QSize &pixelSize, qreal scale)
0206 {
0207     const int refreshRate = 60000; // TODO: get refresh rate via randr
0208     m_renderLoop->setRefreshRate(refreshRate);
0209 
0210     auto mode = std::make_shared<OutputMode>(pixelSize, m_renderLoop->refreshRate());
0211 
0212     State initialState;
0213     initialState.modes = {mode};
0214     initialState.currentMode = mode;
0215     initialState.scale = scale;
0216     setState(initialState);
0217 
0218     const uint32_t eventMask = XCB_EVENT_MASK_KEY_PRESS
0219         | XCB_EVENT_MASK_KEY_RELEASE
0220         | XCB_EVENT_MASK_BUTTON_PRESS
0221         | XCB_EVENT_MASK_BUTTON_RELEASE
0222         | XCB_EVENT_MASK_POINTER_MOTION
0223         | XCB_EVENT_MASK_ENTER_WINDOW
0224         | XCB_EVENT_MASK_LEAVE_WINDOW
0225         | XCB_EVENT_MASK_STRUCTURE_NOTIFY
0226         | XCB_EVENT_MASK_EXPOSURE;
0227 
0228     const uint32_t values[] = {
0229         m_backend->screen()->black_pixel,
0230         eventMask,
0231     };
0232 
0233     uint32_t valueMask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
0234 
0235     xcb_create_window(m_backend->connection(),
0236                       XCB_COPY_FROM_PARENT,
0237                       m_window,
0238                       m_backend->screen()->root,
0239                       0, 0,
0240                       pixelSize.width(), pixelSize.height(),
0241                       0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT,
0242                       valueMask, values);
0243 
0244     // select xinput 2 events
0245     initXInputForWindow();
0246 
0247     const uint32_t presentEventMask = XCB_PRESENT_EVENT_MASK_IDLE_NOTIFY | XCB_PRESENT_EVENT_MASK_COMPLETE_NOTIFY;
0248     m_presentEvent = xcb_generate_id(m_backend->connection());
0249     xcb_present_select_input(m_backend->connection(), m_presentEvent, m_window, presentEventMask);
0250 
0251     m_winInfo = std::make_unique<NETWinInfo>(m_backend->connection(), m_window, m_backend->screen()->root,
0252                                              NET::WMWindowType, NET::Properties2());
0253 
0254     m_winInfo->setWindowType(NET::Normal);
0255     m_winInfo->setPid(QCoreApplication::applicationPid());
0256     QIcon windowIcon = QIcon::fromTheme(QStringLiteral("kwin"));
0257     auto addIcon = [&windowIcon, this](const QSize &size) {
0258         if (windowIcon.actualSize(size) != size) {
0259             return;
0260         }
0261         NETIcon icon;
0262         QImage windowImage = windowIcon.pixmap(size).toImage();
0263         icon.data = windowImage.bits();
0264         icon.size.width = size.width();
0265         icon.size.height = size.height();
0266         m_winInfo->setIcon(icon, false);
0267     };
0268     addIcon(QSize(16, 16));
0269     addIcon(QSize(32, 32));
0270     addIcon(QSize(48, 48));
0271 
0272     m_cursor = std::make_unique<X11WindowedCursor>(this);
0273 
0274     xcb_map_window(m_backend->connection(), m_window);
0275 }
0276 
0277 void X11WindowedOutput::initXInputForWindow()
0278 {
0279     if (!m_backend->hasXInput()) {
0280         return;
0281     }
0282 #if HAVE_X11_XINPUT
0283     XIEventMask evmasks[1];
0284     unsigned char mask1[XIMaskLen(XI_LASTEVENT)];
0285 
0286     memset(mask1, 0, sizeof(mask1));
0287     XISetMask(mask1, XI_TouchBegin);
0288     XISetMask(mask1, XI_TouchUpdate);
0289     XISetMask(mask1, XI_TouchOwnership);
0290     XISetMask(mask1, XI_TouchEnd);
0291     evmasks[0].deviceid = XIAllMasterDevices;
0292     evmasks[0].mask_len = sizeof(mask1);
0293     evmasks[0].mask = mask1;
0294     XISelectEvents(m_backend->display(), m_window, evmasks, 1);
0295 #endif
0296 }
0297 
0298 void X11WindowedOutput::resize(const QSize &pixelSize)
0299 {
0300     auto mode = std::make_shared<OutputMode>(pixelSize, m_renderLoop->refreshRate());
0301 
0302     State next = m_state;
0303     next.modes = {mode};
0304     next.currentMode = mode;
0305     setState(next);
0306 }
0307 
0308 void X11WindowedOutput::handlePresentCompleteNotify(xcb_present_complete_notify_event_t *event)
0309 {
0310     std::chrono::microseconds timestamp(event->ust);
0311     m_frame->presented(std::chrono::nanoseconds(1'000'000'000'000 / refreshRate()), timestamp, Compositor::self()->backend()->primaryLayer(this)->queryRenderTime(), PresentationMode::VSync);
0312     m_frame.reset();
0313 }
0314 
0315 void X11WindowedOutput::handlePresentIdleNotify(xcb_present_idle_notify_event_t *event)
0316 {
0317     for (auto &[graphicsBuffer, x11Buffer] : m_buffers) {
0318         if (x11Buffer->pixmap() == event->pixmap) {
0319             x11Buffer->unlock();
0320             return;
0321         }
0322     }
0323 }
0324 
0325 void X11WindowedOutput::setWindowTitle(const QString &title)
0326 {
0327     m_winInfo->setName(title.toUtf8().constData());
0328 }
0329 
0330 QPoint X11WindowedOutput::internalPosition() const
0331 {
0332     return geometry().topLeft();
0333 }
0334 
0335 void X11WindowedOutput::setHostPosition(const QPoint &pos)
0336 {
0337     m_hostPosition = pos;
0338 }
0339 
0340 QPointF X11WindowedOutput::mapFromGlobal(const QPointF &pos) const
0341 {
0342     return (pos - hostPosition() + internalPosition()) / scale();
0343 }
0344 
0345 bool X11WindowedOutput::updateCursorLayer()
0346 {
0347     const auto layer = Compositor::self()->backend()->cursorLayer(this);
0348     if (layer->isEnabled()) {
0349         xcb_xfixes_show_cursor(m_backend->connection(), m_window);
0350         // the cursor layers update the image on their own already
0351     } else {
0352         xcb_xfixes_hide_cursor(m_backend->connection(), m_window);
0353     }
0354     return true;
0355 }
0356 
0357 void X11WindowedOutput::updateEnabled(bool enabled)
0358 {
0359     State next = m_state;
0360     next.enabled = enabled;
0361     setState(next);
0362 }
0363 
0364 xcb_pixmap_t X11WindowedOutput::importDmaBufBuffer(const DmaBufAttributes *attributes)
0365 {
0366     uint8_t depth;
0367     uint8_t bpp;
0368     switch (attributes->format) {
0369     case DRM_FORMAT_ARGB8888:
0370         depth = 32;
0371         bpp = 32;
0372         break;
0373     case DRM_FORMAT_XRGB8888:
0374         depth = 24;
0375         bpp = 32;
0376         break;
0377     default:
0378         qCWarning(KWIN_X11WINDOWED) << "Cannot import a buffer with unsupported format";
0379         return XCB_PIXMAP_NONE;
0380     }
0381 
0382     xcb_pixmap_t pixmap = xcb_generate_id(m_backend->connection());
0383     if (m_backend->driMajorVersion() >= 1 || m_backend->driMinorVersion() >= 2) {
0384         // xcb_dri3_pixmap_from_buffers() takes the ownership of the file descriptors.
0385         int fds[4] = {
0386             attributes->fd[0].duplicate().take(),
0387             attributes->fd[1].duplicate().take(),
0388             attributes->fd[2].duplicate().take(),
0389             attributes->fd[3].duplicate().take(),
0390         };
0391         xcb_dri3_pixmap_from_buffers(m_backend->connection(), pixmap, m_window, attributes->planeCount,
0392                                      attributes->width, attributes->height,
0393                                      attributes->pitch[0], attributes->offset[0],
0394                                      attributes->pitch[1], attributes->offset[1],
0395                                      attributes->pitch[2], attributes->offset[2],
0396                                      attributes->pitch[3], attributes->offset[3],
0397                                      depth, bpp, attributes->modifier, fds);
0398     } else {
0399         // xcb_dri3_pixmap_from_buffer() takes the ownership of the file descriptor.
0400         xcb_dri3_pixmap_from_buffer(m_backend->connection(), pixmap, m_window,
0401                                     attributes->height * attributes->pitch[0], attributes->width, attributes->height,
0402                                     attributes->pitch[0], depth, bpp, attributes->fd[0].duplicate().take());
0403     }
0404 
0405     return pixmap;
0406 }
0407 
0408 xcb_pixmap_t X11WindowedOutput::importShmBuffer(const ShmAttributes *attributes)
0409 {
0410     // xcb_shm_attach_fd() takes the ownership of the passed shm file descriptor.
0411     FileDescriptor poolFileDescriptor = attributes->fd.duplicate();
0412     if (!poolFileDescriptor.isValid()) {
0413         qCWarning(KWIN_X11WINDOWED) << "Failed to duplicate shm file descriptor";
0414         return XCB_PIXMAP_NONE;
0415     }
0416 
0417     xcb_shm_seg_t segment = xcb_generate_id(m_backend->connection());
0418     xcb_shm_attach_fd(m_backend->connection(), segment, poolFileDescriptor.take(), 0);
0419 
0420     xcb_pixmap_t pixmap = xcb_generate_id(m_backend->connection());
0421     xcb_shm_create_pixmap(m_backend->connection(), pixmap, m_window, attributes->size.width(), attributes->size.height(), depth(), segment, 0);
0422     xcb_shm_detach(m_backend->connection(), segment);
0423 
0424     return pixmap;
0425 }
0426 
0427 xcb_pixmap_t X11WindowedOutput::importBuffer(GraphicsBuffer *graphicsBuffer)
0428 {
0429     std::unique_ptr<X11WindowedBuffer> &x11Buffer = m_buffers[graphicsBuffer];
0430     if (!x11Buffer) {
0431         xcb_pixmap_t pixmap = XCB_PIXMAP_NONE;
0432         if (const DmaBufAttributes *attributes = graphicsBuffer->dmabufAttributes()) {
0433             pixmap = importDmaBufBuffer(attributes);
0434         } else if (const ShmAttributes *attributes = graphicsBuffer->shmAttributes()) {
0435             pixmap = importShmBuffer(attributes);
0436         }
0437         if (pixmap == XCB_PIXMAP_NONE) {
0438             return XCB_PIXMAP_NONE;
0439         }
0440 
0441         x11Buffer = std::make_unique<X11WindowedBuffer>(this, pixmap, graphicsBuffer);
0442         connect(x11Buffer.get(), &X11WindowedBuffer::defunct, this, [this, graphicsBuffer]() {
0443             m_buffers.erase(graphicsBuffer);
0444         });
0445     }
0446 
0447     x11Buffer->lock();
0448     return x11Buffer->pixmap();
0449 }
0450 
0451 void X11WindowedOutput::framePending(const std::shared_ptr<OutputFrame> &frame)
0452 {
0453     m_frame = frame;
0454 }
0455 
0456 } // namespace KWin
0457 
0458 #include "moc_x11_windowed_output.cpp"