| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtWaylandCompositor module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL$ |
| ** 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 The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 or (at your option) any later version |
| ** approved by the KDE Free Qt Foundation. The licenses are as published by |
| ** the Free Software Foundation and appearing in the file LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwldatadevicemanager_p.h" |
| |
| #include <QtWaylandCompositor/QWaylandCompositor> |
| |
| #include <QtWaylandCompositor/private/qwaylandcompositor_p.h> |
| #include <QtWaylandCompositor/private/qwaylandseat_p.h> |
| #include "qwldatadevice_p.h" |
| #include "qwldatasource_p.h" |
| #include "qwldataoffer_p.h" |
| #include "qwaylandmimehelper_p.h" |
| |
| #include <QtCore/QDebug> |
| #include <QtCore/QSocketNotifier> |
| #include <fcntl.h> |
| #include <QtCore/private/qcore_unix_p.h> |
| #include <QtCore/QFile> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace QtWayland { |
| |
| DataDeviceManager::DataDeviceManager(QWaylandCompositor *compositor) |
| : wl_data_device_manager(compositor->display(), 1) |
| , m_compositor(compositor) |
| { |
| } |
| |
| void DataDeviceManager::setCurrentSelectionSource(DataSource *source) |
| { |
| if (m_current_selection_source && source |
| && m_current_selection_source->time() > source->time()) { |
| qDebug() << "Trying to set older selection"; |
| return; |
| } |
| |
| m_compositorOwnsSelection = false; |
| |
| finishReadFromClient(); |
| |
| m_current_selection_source = source; |
| if (source) |
| source->setManager(this); |
| |
| // When retained selection is enabled, the compositor will query all the data from the client. |
| // This makes it possible to |
| // 1. supply the selection after the offering client is gone |
| // 2. make it possible for the compositor to participate in copy-paste |
| // The downside is decreased performance, therefore this mode has to be enabled |
| // explicitly in the compositors. |
| if (source && m_compositor->retainedSelectionEnabled()) { |
| m_retainedData.clear(); |
| m_retainedReadIndex = 0; |
| retain(); |
| } |
| } |
| |
| void DataDeviceManager::sourceDestroyed(DataSource *source) |
| { |
| if (m_current_selection_source == source) |
| finishReadFromClient(); |
| } |
| |
| void DataDeviceManager::retain() |
| { |
| QList<QString> offers = m_current_selection_source->mimeTypes(); |
| finishReadFromClient(); |
| if (m_retainedReadIndex >= offers.count()) { |
| QWaylandCompositorPrivate::get(m_compositor)->feedRetainedSelectionData(&m_retainedData); |
| return; |
| } |
| QString mimeType = offers.at(m_retainedReadIndex); |
| m_retainedReadBuf.clear(); |
| int fd[2]; |
| if (pipe(fd) == -1) { |
| qWarning("Clipboard: Failed to create pipe"); |
| return; |
| } |
| fcntl(fd[0], F_SETFL, fcntl(fd[0], F_GETFL, 0) | O_NONBLOCK); |
| m_current_selection_source->send(mimeType, fd[1]); |
| m_retainedReadNotifier = new QSocketNotifier(fd[0], QSocketNotifier::Read, this); |
| connect(m_retainedReadNotifier, &QSocketNotifier::activated, this, &DataDeviceManager::readFromClient); |
| } |
| |
| void DataDeviceManager::finishReadFromClient(bool exhausted) |
| { |
| Q_UNUSED(exhausted); |
| if (m_retainedReadNotifier) { |
| if (exhausted) { |
| int fd = m_retainedReadNotifier->socket(); |
| delete m_retainedReadNotifier; |
| close(fd); |
| } else { |
| // Do not close the handle or destroy the read notifier here |
| // or else clients may SIGPIPE. |
| m_obsoleteRetainedReadNotifiers.append(m_retainedReadNotifier); |
| } |
| m_retainedReadNotifier = nullptr; |
| } |
| } |
| |
| void DataDeviceManager::readFromClient(int fd) |
| { |
| static char buf[4096]; |
| int obsCount = m_obsoleteRetainedReadNotifiers.count(); |
| for (int i = 0; i < obsCount; ++i) { |
| QSocketNotifier *sn = m_obsoleteRetainedReadNotifiers.at(i); |
| if (sn->socket() == fd) { |
| // Read and drop the data, stopping to read and closing the handle |
| // is not yet safe because that could kill the client with SIGPIPE |
| // when it still tries to write. |
| int n; |
| do { |
| n = QT_READ(fd, buf, sizeof buf); |
| } while (n > 0); |
| if (n != -1 || (errno != EAGAIN && errno != EWOULDBLOCK)) { |
| m_obsoleteRetainedReadNotifiers.removeAt(i); |
| delete sn; |
| close(fd); |
| } |
| return; |
| } |
| } |
| int n = QT_READ(fd, buf, sizeof buf); |
| if (n <= 0) { |
| if (n != -1 || (errno != EAGAIN && errno != EWOULDBLOCK)) { |
| finishReadFromClient(true); |
| QList<QString> offers = m_current_selection_source->mimeTypes(); |
| QString mimeType = offers.at(m_retainedReadIndex); |
| m_retainedData.setData(mimeType, m_retainedReadBuf); |
| ++m_retainedReadIndex; |
| retain(); |
| } |
| } else { |
| m_retainedReadBuf.append(buf, n); |
| } |
| } |
| |
| DataSource *DataDeviceManager::currentSelectionSource() |
| { |
| return m_current_selection_source; |
| } |
| |
| struct wl_display *DataDeviceManager::display() const |
| { |
| return m_compositor->display(); |
| } |
| |
| void DataDeviceManager::overrideSelection(const QMimeData &mimeData) |
| { |
| const QStringList formats = mimeData.formats(); |
| if (formats.isEmpty()) |
| return; |
| |
| m_retainedData.clear(); |
| for (const QString &format : formats) |
| m_retainedData.setData(format, mimeData.data(format)); |
| |
| QWaylandCompositorPrivate::get(m_compositor)->feedRetainedSelectionData(&m_retainedData); |
| |
| m_compositorOwnsSelection = true; |
| |
| QWaylandSeat *dev = m_compositor->defaultSeat(); |
| QWaylandSurface *focusSurface = dev->keyboardFocus(); |
| if (focusSurface) |
| offerFromCompositorToClient( |
| QWaylandSeatPrivate::get(dev)->dataDevice()->resourceMap().value(focusSurface->waylandClient())->handle); |
| } |
| |
| bool DataDeviceManager::offerFromCompositorToClient(wl_resource *clientDataDeviceResource) |
| { |
| if (!m_compositorOwnsSelection) |
| return false; |
| |
| wl_client *client = wl_resource_get_client(clientDataDeviceResource); |
| //qDebug("compositor offers %d types to %p", m_retainedData.formats().count(), client); |
| |
| struct wl_resource *selectionOffer = |
| wl_resource_create(client, &wl_data_offer_interface, -1, 0); |
| wl_resource_set_implementation(selectionOffer, &compositor_offer_interface, this, nullptr); |
| wl_data_device_send_data_offer(clientDataDeviceResource, selectionOffer); |
| const auto formats = m_retainedData.formats(); |
| for (const QString &format : formats) { |
| QByteArray ba = format.toLatin1(); |
| wl_data_offer_send_offer(selectionOffer, ba.constData()); |
| } |
| wl_data_device_send_selection(clientDataDeviceResource, selectionOffer); |
| |
| return true; |
| } |
| |
| void DataDeviceManager::offerRetainedSelection(wl_resource *clientDataDeviceResource) |
| { |
| if (m_retainedData.formats().isEmpty()) |
| return; |
| |
| m_compositorOwnsSelection = true; |
| offerFromCompositorToClient(clientDataDeviceResource); |
| } |
| |
| void DataDeviceManager::data_device_manager_create_data_source(Resource *resource, uint32_t id) |
| { |
| new DataSource(resource->client(), id, m_compositor->currentTimeMsecs()); |
| } |
| |
| void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, struct ::wl_resource *seat) |
| { |
| QWaylandSeat *input_device = QWaylandSeat::fromSeatResource(seat); |
| QWaylandSeatPrivate::get(input_device)->clientRequestedDataDevice(this, resource->client(), id); |
| } |
| |
| void DataDeviceManager::comp_accept(wl_client *, wl_resource *, uint32_t, const char *) |
| { |
| } |
| |
| void DataDeviceManager::comp_receive(wl_client *client, wl_resource *resource, const char *mime_type, int32_t fd) |
| { |
| Q_UNUSED(client); |
| DataDeviceManager *self = static_cast<DataDeviceManager *>(wl_resource_get_user_data(resource)); |
| //qDebug("client %p wants data for type %s from compositor", client, mime_type); |
| QByteArray content = QWaylandMimeHelper::getByteArray(&self->m_retainedData, QString::fromLatin1(mime_type)); |
| if (!content.isEmpty()) { |
| QFile f; |
| if (f.open(fd, QIODevice::WriteOnly)) |
| f.write(content); |
| } |
| close(fd); |
| } |
| |
| void DataDeviceManager::comp_destroy(wl_client *, wl_resource *) |
| { |
| } |
| |
| QT_WARNING_DISABLE_GCC("-Wmissing-field-initializers") |
| QT_WARNING_DISABLE_CLANG("-Wmissing-field-initializers") |
| |
| const struct wl_data_offer_interface DataDeviceManager::compositor_offer_interface = { |
| DataDeviceManager::comp_accept, |
| DataDeviceManager::comp_receive, |
| DataDeviceManager::comp_destroy |
| }; |
| |
| } //namespace |
| |
| QT_END_NAMESPACE |