/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qxcbbackingstore.h" #include "qxcbconnection.h" #include "qxcbscreen.h" #include "qxcbwindow.h" #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QXcbShmImage : public QXcbObject { public: QXcbShmImage(QXcbScreen *connection, const QSize &size, uint depth, QImage::Format format); ~QXcbShmImage() { destroy(); } QImage *image() { return &m_qimage; } QSize size() const { return m_qimage.size(); } void put(xcb_window_t window, const QPoint &dst, const QRect &source); void preparePaint(const QRegion ®ion); private: void destroy(); xcb_shm_segment_info_t m_shm_info; xcb_image_t *m_xcb_image; QImage m_qimage; xcb_gcontext_t m_gc; xcb_window_t m_gc_window; QRegion m_dirty; }; QXcbShmImage::QXcbShmImage(QXcbScreen *screen, const QSize &size, uint depth, QImage::Format format) : QXcbObject(screen->connection()) , m_gc(0) , m_gc_window(0) { Q_XCB_NOOP(connection()); m_xcb_image = xcb_image_create_native(xcb_connection(), size.width(), size.height(), XCB_IMAGE_FORMAT_Z_PIXMAP, depth, 0, ~0, 0); const int segmentSize = m_xcb_image->stride * m_xcb_image->height; if (!segmentSize) return; int id = shmget(IPC_PRIVATE, segmentSize, IPC_CREAT | 0600); if (id == -1) qWarning("QXcbShmImage: shmget() failed (%d) for size %d (%dx%d)", errno, segmentSize, size.width(), size.height()); else m_shm_info.shmid = id; m_shm_info.shmaddr = m_xcb_image->data = (quint8 *)shmat (m_shm_info.shmid, 0, 0); m_shm_info.shmseg = xcb_generate_id(xcb_connection()); const xcb_query_extension_reply_t *shm_reply = xcb_get_extension_data(xcb_connection(), &xcb_shm_id); bool shm_present = shm_reply != NULL && shm_reply->present; xcb_generic_error_t *error = NULL; if (shm_present) error = xcb_request_check(xcb_connection(), xcb_shm_attach_checked(xcb_connection(), m_shm_info.shmseg, m_shm_info.shmid, false)); if (!shm_present || error) { free(error); shmdt(m_shm_info.shmaddr); shmctl(m_shm_info.shmid, IPC_RMID, 0); m_shm_info.shmaddr = 0; m_xcb_image->data = (uint8_t *)malloc(segmentSize); } else { if (shmctl(m_shm_info.shmid, IPC_RMID, 0) == -1) qWarning() << "QXcbBackingStore: Error while marking the shared memory segment to be destroyed"; } m_qimage = QImage( (uchar*) m_xcb_image->data, m_xcb_image->width, m_xcb_image->height, m_xcb_image->stride, format); } void QXcbShmImage::destroy() { const int segmentSize = m_xcb_image ? (m_xcb_image->stride * m_xcb_image->height) : 0; if (segmentSize && m_shm_info.shmaddr) Q_XCB_CALL(xcb_shm_detach(xcb_connection(), m_shm_info.shmseg)); xcb_image_destroy(m_xcb_image); if (segmentSize) { if (m_shm_info.shmaddr) { shmdt(m_shm_info.shmaddr); shmctl(m_shm_info.shmid, IPC_RMID, 0); } else { free(m_xcb_image->data); } } if (m_gc) Q_XCB_CALL(xcb_free_gc(xcb_connection(), m_gc)); } void QXcbShmImage::put(xcb_window_t window, const QPoint &target, const QRect &source) { Q_XCB_NOOP(connection()); if (m_gc_window != window) { if (m_gc) Q_XCB_CALL(xcb_free_gc(xcb_connection(), m_gc)); m_gc = xcb_generate_id(xcb_connection()); Q_XCB_CALL(xcb_create_gc(xcb_connection(), m_gc, window, 0, 0)); m_gc_window = window; } Q_XCB_NOOP(connection()); if (m_shm_info.shmaddr) { xcb_image_shm_put(xcb_connection(), window, m_gc, m_xcb_image, m_shm_info, source.x(), source.y(), target.x(), target.y(), source.width(), source.height(), false); } else { // If we upload the whole image in a single chunk, the result might be // larger than the server's maximum request size and stuff breaks. // To work around that, we upload the image in chunks where each chunk // is small enough for a single request. int src_x = source.x(); int src_y = source.y(); int target_x = target.x(); int target_y = target.y(); int width = source.width(); int height = source.height(); // We must make sure that each request is not larger than max_req_size. // Each request takes req_size + m_xcb_image->stride * height bytes. uint32_t max_req_size = xcb_get_maximum_request_length(xcb_connection()); uint32_t req_size = sizeof(xcb_put_image_request_t); int rows_per_put = (max_req_size - req_size) / m_xcb_image->stride; // This assert could trigger if a single row has more pixels than fit in // a single PutImage request. However, max_req_size is guaranteed to be // at least 16384 bytes. That should be enough for quite large images. Q_ASSERT(rows_per_put > 0); while (height > 0) { int rows = std::min(height, rows_per_put); xcb_image_t *subimage = xcb_image_subimage(m_xcb_image, src_x, src_y, width, rows, 0, 0, 0); xcb_image_put(xcb_connection(), window, m_gc, subimage, target_x, target_y, 0); xcb_image_destroy(subimage); src_y += rows; target_y += rows; height -= rows; } } Q_XCB_NOOP(connection()); m_dirty = m_dirty | source; xcb_flush(xcb_connection()); Q_XCB_NOOP(connection()); } void QXcbShmImage::preparePaint(const QRegion ®ion) { // to prevent X from reading from the image region while we're writing to it if (m_dirty.intersects(region)) { connection()->sync(); m_dirty = QRegion(); } } QXcbBackingStore::QXcbBackingStore(QWindow *window) : QPlatformBackingStore(window) , m_image(0) , m_syncingResize(false) { QXcbScreen *screen = static_cast(window->screen()->handle()); setConnection(screen->connection()); } QXcbBackingStore::~QXcbBackingStore() { delete m_image; } QPaintDevice *QXcbBackingStore::paintDevice() { return m_image ? m_image->image() : 0; } void QXcbBackingStore::beginPaint(const QRegion ®ion) { if (!m_image) return; m_image->preparePaint(region); if (m_image->image()->hasAlphaChannel()) { QPainter p(m_image->image()); p.setCompositionMode(QPainter::CompositionMode_Source); const QVector rects = region.rects(); const QColor blank = Qt::transparent; for (QVector::const_iterator it = rects.begin(); it != rects.end(); ++it) { p.fillRect(*it, blank); } } } void QXcbBackingStore::endPaint(const QRegion &) { } void QXcbBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) { if (!m_image || m_image->size().isEmpty()) return; QSize imageSize = m_image->size(); QRegion clipped = region; clipped &= QRect(0, 0, window->width(), window->height()); clipped &= QRect(0, 0, imageSize.width(), imageSize.height()).translated(-offset); QRect bounds = clipped.boundingRect(); if (bounds.isNull()) return; Q_XCB_NOOP(connection()); QXcbWindow *platformWindow = static_cast(window->handle()); QVector rects = clipped.rects(); for (int i = 0; i < rects.size(); ++i) m_image->put(platformWindow->xcb_window(), rects.at(i).topLeft(), rects.at(i).translated(offset)); Q_XCB_NOOP(connection()); if (m_syncingResize) { xcb_flush(xcb_connection()); connection()->sync(); m_syncingResize = false; platformWindow->updateSyncRequestCounter(); } } void QXcbBackingStore::resize(const QSize &size, const QRegion &) { if (m_image && size == m_image->size()) return; Q_XCB_NOOP(connection()); QXcbScreen *screen = static_cast(window()->screen()->handle()); QPlatformWindow *pw = window()->handle(); if (!pw) { window()->create(); pw = window()->handle(); } QXcbWindow* win = static_cast(pw); delete m_image; m_image = new QXcbShmImage(screen, size, win->depth(), win->imageFormat()); Q_XCB_NOOP(connection()); m_syncingResize = true; } extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset); bool QXcbBackingStore::scroll(const QRegion &area, int dx, int dy) { if (!m_image || m_image->image()->isNull()) return false; m_image->preparePaint(area); const QVector rects = area.rects(); for (int i = 0; i < rects.size(); ++i) qt_scrollRectInImage(*m_image->image(), rects.at(i), QPoint(dx, dy)); return true; } QT_END_NAMESPACE