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"