diff --git a/xwl/clipboard.cpp b/xwl/clipboard.cpp index d64dd1b95..4a51ff339 100644 --- a/xwl/clipboard.cpp +++ b/xwl/clipboard.cpp @@ -1,192 +1,196 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "clipboard.h" #include "databridge.h" #include "selection_source.h" #include "transfer.h" #include "xwayland.h" #include "x11client.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include namespace KWin { namespace Xwl { Clipboard::Clipboard(xcb_atom_t atom, QObject *parent) : Selection(atom, parent) { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); const uint32_t clipboardValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE }; xcb_create_window(xcbConn, XCB_COPY_FROM_PARENT, window(), kwinApp()->x11RootWindow(), 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, Xwayland::self()->xcbScreen()->root_visual, XCB_CW_EVENT_MASK, clipboardValues); registerXfixes(); xcb_flush(xcbConn); connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::selectionChanged, this, &Clipboard::wlSelectionChanged); + + connect(DataBridge::self()->dataDeviceIface(), &KWaylandServer::DataDeviceInterface::selectionChanged, this, [](KWaylandServer::DataSourceInterface *selection) { + waylandServer()->seat()->setSelection(selection); + }); + + connect(DataBridge::self()->dataDeviceIface(), &KWaylandServer::DataDeviceInterface::selectionCleared, this, []() { + waylandServer()->seat()->setSelection(nullptr); + }); } -void Clipboard::wlSelectionChanged(KWaylandServer::DataDeviceInterface *ddi) +void Clipboard::wlSelectionChanged(KWaylandServer::AbstractDataSource *dsi) { - if (ddi && ddi != DataBridge::self()->dataDeviceIface()) { + if (dsi && dsi->client() != DataBridge::self()->dataDeviceIface()->client()->client()) { // Wayland native client provides new selection if (!m_checkConnection) { m_checkConnection = connect(workspace(), &Workspace::clientActivated, this, [this](AbstractClient *ac) { Q_UNUSED(ac); checkWlSource(); }); } // remove previous source so checkWlSource() can create a new one setWlSource(nullptr); } checkWlSource(); } void Clipboard::checkWlSource() { - auto ddi = waylandServer()->seat()->selection(); + auto dsi = waylandServer()->seat()->selection(); auto removeSource = [this] { if (wlSource()) { setWlSource(nullptr); ownSelection(false); } }; // Wayland source gets created when: // - the Wl selection exists, // - its source is not Xwayland, // - a client is active, // - this client is an Xwayland one. // // Otherwise the Wayland source gets destroyed to shield // against snooping X clients. - if (!ddi || DataBridge::self()->dataDeviceIface() == ddi) { + if (!dsi || (DataBridge::self()->dataDeviceIface()->client()->client() == dsi->client())) { // Xwayland source or no source disconnect(m_checkConnection); m_checkConnection = QMetaObject::Connection(); removeSource(); return; } if (!workspace()->activeClient() || !workspace()->activeClient()->inherits("KWin::X11Client")) { // no active client or active client is Wayland native removeSource(); return; } // Xwayland client is active and we need a Wayland source if (wlSource()) { // source already exists, nothing more to do return; } - auto *wls = new WlSource(this, ddi); + auto *wls = new WlSource(this); setWlSource(wls); - auto *dsi = ddi->selection(); if (dsi) { wls->setDataSourceIface(dsi); } - connect(ddi, &KWaylandServer::DataDeviceInterface::selectionChanged, - wls, &WlSource::setDataSourceIface); ownSelection(true); } void Clipboard::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) { createX11Source(nullptr); const AbstractClient *client = workspace()->activeClient(); if (!qobject_cast(client)) { // clipboard is only allowed to be acquired when Xwayland has focus // TODO: can we make this stronger (window id comparison)? return; } createX11Source(event); if (X11Source *source = x11Source()) { source->getTargets(); } } void Clipboard::x11OffersChanged(const QStringList &added, const QStringList &removed) { X11Source *source = x11Source(); if (!source) { return; } const Mimes offers = source->offers(); if (!offers.isEmpty()) { if (!source->dataSource() || !removed.isEmpty()) { // create new Wl DataSource if there is none or when types // were removed (Wl Data Sources can only add types) KWayland::Client::DataDeviceManager *dataDeviceManager = waylandServer()->internalDataDeviceManager(); KWayland::Client::DataSource *dataSource = dataDeviceManager->createDataSource(source); // also offers directly the currently available types source->setDataSource(dataSource); DataBridge::self()->dataDevice()->setSelection(0, dataSource); - waylandServer()->seat()->setSelection(DataBridge::self()->dataDeviceIface()); } else if (auto *dataSource = source->dataSource()) { for (const QString &mime : added) { dataSource->offer(mime); } } } else { - waylandServer()->seat()->setSelection(nullptr); + DataBridge::self()->dataDevice()->setSelection(0); } waylandServer()->internalClientConection()->flush(); waylandServer()->dispatch(); } } // namespace Xwl } // namespace KWin diff --git a/xwl/clipboard.h b/xwl/clipboard.h index 45aa6ef9c..2cd16881b 100644 --- a/xwl/clipboard.h +++ b/xwl/clipboard.h @@ -1,67 +1,67 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_XWL_CLIPBOARD #define KWIN_XWL_CLIPBOARD #include "selection.h" namespace KWaylandServer { -class DataDeviceInterface; +class AbstractDataSource; } namespace KWin { namespace Xwl { /** * Represents the X clipboard, which is on Wayland side just called * @e selection. */ class Clipboard : public Selection { Q_OBJECT public: Clipboard(xcb_atom_t atom, QObject *parent); private: void doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) override; void x11OffersChanged(const QStringList &added, const QStringList &removed) override; /** * React to Wl selection change. */ - void wlSelectionChanged(KWaylandServer::DataDeviceInterface *ddi); + void wlSelectionChanged(KWaylandServer::AbstractDataSource *dsi); /** * Check the current state of the selection and if a source needs * to be created or destroyed. */ void checkWlSource(); QMetaObject::Connection m_checkConnection; Q_DISABLE_COPY(Clipboard) }; } // namespace Xwl } // namespace KWin #endif diff --git a/xwl/dnd.cpp b/xwl/dnd.cpp index c97f7ebe6..4b74ab5c3 100644 --- a/xwl/dnd.cpp +++ b/xwl/dnd.cpp @@ -1,229 +1,230 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "dnd.h" #include "databridge.h" #include "drag_wl.h" #include "drag_x.h" #include "selection_source.h" #include "abstract_client.h" #include "atoms.h" #include "wayland_server.h" #include "workspace.h" #include "xwayland.h" #include #include #include #include +#include #include #include namespace KWin { namespace Xwl { // version of DnD support in X const static uint32_t s_version = 5; uint32_t Dnd::version() { return s_version; } Dnd::Dnd(xcb_atom_t atom, QObject *parent) : Selection(atom, parent) { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); const uint32_t dndValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE }; xcb_create_window(xcbConn, XCB_COPY_FROM_PARENT, window(), kwinApp()->x11RootWindow(), 0, 0, 8192, 8192, // TODO: get current screen size and connect to changes 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, Xwayland::self()->xcbScreen()->root_visual, XCB_CW_EVENT_MASK, dndValues); registerXfixes(); xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, window(), atoms->xdnd_aware, XCB_ATOM_ATOM, 32, 1, &s_version); xcb_flush(xcbConn); connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragStarted, this, &Dnd::startDrag); connect(waylandServer()->seat(), &KWaylandServer::SeatInterface::dragEnded, this, &Dnd::endDrag); const auto *comp = waylandServer()->compositor(); m_surface = waylandServer()->internalCompositor()->createSurface(this); m_surface->setInputRegion(nullptr); m_surface->commit(KWayland::Client::Surface::CommitFlag::None); auto *dc = new QMetaObject::Connection(); *dc = connect(comp, &KWaylandServer::CompositorInterface::surfaceCreated, this, [this, dc](KWaylandServer::SurfaceInterface *si) { // TODO: how to make sure that it is the iface of m_surface? if (m_surfaceIface || si->client() != waylandServer()->internalConnection()) { return; } QObject::disconnect(*dc); delete dc; m_surfaceIface = si; connect(workspace(), &Workspace::clientActivated, this, [this](AbstractClient *ac) { if (!ac || !ac->inherits("KWin::X11Client")) { return; } auto *surface = ac->surface(); if (surface) { surface->setDataProxy(m_surfaceIface); } else { auto *dc = new QMetaObject::Connection(); *dc = connect(ac, &AbstractClient::surfaceChanged, this, [this, ac, dc] { if (auto *surface = ac->surface()) { surface->setDataProxy(m_surfaceIface); QObject::disconnect(*dc); delete dc; } } ); } }); } ); waylandServer()->dispatch(); } void Dnd::doHandleXfixesNotify(xcb_xfixes_selection_notify_event_t *event) { if (qobject_cast(m_currentDrag)) { // X drag is in progress, rogue X client took over the selection. return; } if (m_currentDrag) { // Wl drag is in progress - don't overwrite by rogue X client, // get it back instead! ownSelection(true); return; } createX11Source(nullptr); const auto *seat = waylandServer()->seat(); auto *originSurface = seat->focusedPointerSurface(); if (!originSurface) { return; } if (originSurface->client() != waylandServer()->xWaylandConnection()) { // focused surface client is not Xwayland - do not allow drag to start // TODO: can we make this stronger (window id comparison)? return; } if (!seat->isPointerButtonPressed(Qt::LeftButton)) { // we only allow drags to be started on (left) pointer button being // pressed for now return; } createX11Source(event); X11Source *source = x11Source(); if (!source) { return; } DataBridge::self()->dataDeviceIface()->updateProxy(originSurface); m_currentDrag = new XToWlDrag(source); } void Dnd::x11OffersChanged(const QStringList &added, const QStringList &removed) { Q_UNUSED(added); Q_UNUSED(removed); // TODO: handled internally } bool Dnd::handleClientMessage(xcb_client_message_event_t *event) { for (Drag *drag : m_oldDrags) { if (drag->handleClientMessage(event)) { return true; } } if (m_currentDrag && m_currentDrag->handleClientMessage(event)) { return true; } return false; } DragEventReply Dnd::dragMoveFilter(Toplevel *target, const QPoint &pos) { // This filter only is used when a drag is in process. Q_ASSERT(m_currentDrag); return m_currentDrag->moveFilter(target, pos); } void Dnd::startDrag() { auto *ddi = waylandServer()->seat()->dragSource(); if (ddi == DataBridge::self()->dataDeviceIface()) { // X to Wl drag, started by us, is in progress. Q_ASSERT(m_currentDrag); return; } // There can only ever be one Wl native drag at the same time. Q_ASSERT(!m_currentDrag); // New Wl to X drag, init drag and Wl source. m_currentDrag = new WlToXDrag(); - auto source = new WlSource(this, ddi); + auto source = new WlSource(this); source->setDataSourceIface(ddi->dragSource()); setWlSource(source); ownSelection(true); } void Dnd::endDrag() { Q_ASSERT(m_currentDrag); if (m_currentDrag->end()) { delete m_currentDrag; } else { connect(m_currentDrag, &Drag::finish, this, &Dnd::clearOldDrag); m_oldDrags << m_currentDrag; } m_currentDrag = nullptr; } void Dnd::clearOldDrag(Drag *drag) { m_oldDrags.removeOne(drag); delete drag; } } // namespace Xwl } // namespace KWin diff --git a/xwl/drag_x.cpp b/xwl/drag_x.cpp index 722c06394..85571d707 100644 --- a/xwl/drag_x.cpp +++ b/xwl/drag_x.cpp @@ -1,549 +1,549 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "drag_x.h" #include "databridge.h" #include "dnd.h" #include "selection_source.h" #include "xwayland.h" #include "abstract_client.h" #include "atoms.h" #include "wayland_server.h" #include "workspace.h" #include #include #include #include #include #include #include namespace KWin { namespace Xwl { static QStringList atomToMimeTypes(xcb_atom_t atom) { QStringList mimeTypes; if (atom == atoms->utf8_string) { mimeTypes << QString::fromLatin1("text/plain;charset=utf-8"); } else if (atom == atoms->text) { mimeTypes << QString::fromLatin1("text/plain"); } else if (atom == atoms->uri_list || atom == atoms->netscape_url || atom == atoms->moz_url) { // We identify netscape and moz format as less detailed formats text/uri-list, // text/x-uri and accept the information loss. mimeTypes << QString::fromLatin1("text/uri-list") << QString::fromLatin1("text/x-uri"); } else { mimeTypes << Selection::atomName(atom); } return mimeTypes; } XToWlDrag::XToWlDrag(X11Source *source) : m_source(source) { connect(DataBridge::self()->dnd(), &Dnd::transferFinished, this, [this](xcb_timestamp_t eventTime) { // we use this mechanism, because the finished call is not // reliable done by Wayland clients auto it = std::find_if(m_dataRequests.begin(), m_dataRequests.end(), [eventTime](const QPair &req) { return req.first == eventTime && req.second == false; }); if (it == m_dataRequests.end()) { // transfer finished for a different drag return; } (*it).second = true; checkForFinished(); }); connect(source, &X11Source::transferReady, this, [this](xcb_atom_t target, qint32 fd) { Q_UNUSED(target); Q_UNUSED(fd); m_dataRequests << QPair(m_source->timestamp(), false); }); auto *ddm = waylandServer()->internalDataDeviceManager(); m_dataSource = ddm->createDataSource(this); connect(m_dataSource, &KWayland::Client::DataSource::dragAndDropPerformed, this, [this] { m_performed = true; if (m_visit) { connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) { Q_UNUSED(visit); checkForFinished(); }); QTimer::singleShot(2000, this, [this]{ if (!m_visit->entered() || !m_visit->dropHandled()) { // X client timed out Q_EMIT finish(this); } else if (m_dataRequests.size() == 0) { // Wl client timed out m_visit->sendFinished(); Q_EMIT finish(this); } }); } checkForFinished(); }); connect(m_dataSource, &KWayland::Client::DataSource::dragAndDropFinished, this, [this] { // this call is not reliably initiated by Wayland clients checkForFinished(); }); // source does _not_ take ownership of m_dataSource source->setDataSource(m_dataSource); auto *dc = new QMetaObject::Connection(); *dc = connect(waylandServer()->dataDeviceManager(), &KWaylandServer::DataDeviceManagerInterface::dataSourceCreated, this, [this, dc](KWaylandServer::DataSourceInterface *dsi) { Q_ASSERT(dsi); - if (dsi->client() != waylandServer()->internalConnection()) { + if (dsi->client() != waylandServer()->internalConnection()->client()) { return; } QObject::disconnect(*dc); delete dc; connect(dsi, &KWaylandServer::DataSourceInterface::mimeTypeOffered, this, &XToWlDrag::offerCallback); } ); // Start drag with serial of last left pointer button press. // This means X to Wl drags can only be executed with the left pointer button being pressed. // For touch and (maybe) other pointer button drags we have to revisit this. // // Until then we accept the restriction for Xwayland clients. DataBridge::self()->dataDevice()->startDrag(waylandServer()->seat()->pointerButtonSerial(Qt::LeftButton), m_dataSource, DataBridge::self()->dnd()->surface()); waylandServer()->dispatch(); } XToWlDrag::~XToWlDrag() { delete m_dataSource; m_dataSource = nullptr; } DragEventReply XToWlDrag::moveFilter(Toplevel *target, const QPoint &pos) { Q_UNUSED(pos); auto *seat = waylandServer()->seat(); if (m_visit && m_visit->target() == target) { // still same Wl target, wait for X events return DragEventReply::Ignore; } if (m_visit) { if (m_visit->leave()) { delete m_visit; } else { connect(m_visit, &WlVisit::finish, this, [this](WlVisit *visit) { m_oldVisits.removeOne(visit); delete visit; }); m_oldVisits << m_visit; } } const bool hasCurrent = m_visit; m_visit = nullptr; if (!target || !target->surface() || target->surface()->client() == waylandServer()->xWaylandConnection()) { // currently there is no target or target is an Xwayland window // handled here and by X directly if (AbstractClient *ac = qobject_cast(target)) { if (workspace()->activeClient() != ac) { workspace()->activateClient(ac); } } if (hasCurrent) { // last received enter event is now void, // wait for the next one seat->setDragTarget(nullptr); } return DragEventReply::Ignore; } // new Wl native target auto *ac = static_cast(target); m_visit = new WlVisit(ac, this); connect(m_visit, &WlVisit::offersReceived, this, &XToWlDrag::setOffers); return DragEventReply::Ignore; } bool XToWlDrag::handleClientMessage(xcb_client_message_event_t *event) { for (auto *visit : m_oldVisits) { if (visit->handleClientMessage(event)) { return true; } } if (m_visit && m_visit->handleClientMessage(event)) { return true; } return false; } void XToWlDrag::setDragAndDropAction(DnDAction action) { m_dataSource->setDragAndDropActions(action); } DnDAction XToWlDrag::selectedDragAndDropAction() { // Take the last received action only from before the drag was performed, // because the action gets reset as soon as the drag is performed // (this seems to be a bug in KWayland -> TODO). if (!m_performed) { m_lastSelectedDragAndDropAction = m_dataSource->selectedDragAndDropAction(); } return m_lastSelectedDragAndDropAction; } void XToWlDrag::setOffers(const Mimes &offers) { m_source->setOffers(offers); if (offers.isEmpty()) { // There are no offers, so just directly set the drag target, // no transfer possible anyways. setDragTarget(); return; } if (m_offers == offers) { // offers had been set already by a previous visit // Wl side is already configured setDragTarget(); return; } // TODO: make sure that offers are not changed in between visits m_offersPending = m_offers = offers; for (const auto mimePair : offers) { m_dataSource->offer(mimePair.first); } } using Mime = QPair; void XToWlDrag::offerCallback(const QString &mime) { m_offersPending.erase(std::remove_if(m_offersPending.begin(), m_offersPending.end(), [mime](const Mime &m) { return m.first == mime; })); if (m_offersPending.isEmpty() && m_visit && m_visit->entered()) { setDragTarget(); } } void XToWlDrag::setDragTarget() { auto *ac = m_visit->target(); workspace()->activateClient(ac); waylandServer()->seat()->setDragTarget(ac->surface(), ac->inputTransformation()); } bool XToWlDrag::checkForFinished() { if (!m_visit) { // not dropped above Wl native target Q_EMIT finish(this); return true; } if (!m_visit->finished()) { return false; } if (m_dataRequests.size() == 0) { // need to wait for first data request return false; } const bool transfersFinished = std::all_of(m_dataRequests.begin(), m_dataRequests.end(), [](QPair req) { return req.second; }); if (transfersFinished) { m_visit->sendFinished(); Q_EMIT finish(this); } return transfersFinished; } WlVisit::WlVisit(AbstractClient *target, XToWlDrag *drag) : QObject(drag), m_target(target), m_drag(drag) { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); m_window = xcb_generate_id(xcbConn); DataBridge::self()->dnd()->overwriteRequestorWindow(m_window); const uint32_t dndValues[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE }; xcb_create_window(xcbConn, XCB_COPY_FROM_PARENT, m_window, kwinApp()->x11RootWindow(), 0, 0, 8192, 8192, // TODO: get current screen size and connect to changes 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, Xwayland::self()->xcbScreen()->root_visual, XCB_CW_EVENT_MASK, dndValues); uint32_t version = Dnd::version(); xcb_change_property(xcbConn, XCB_PROP_MODE_REPLACE, m_window, atoms->xdnd_aware, XCB_ATOM_ATOM, 32, 1, &version); xcb_map_window(xcbConn, m_window); workspace()->addManualOverlay(m_window); workspace()->updateStackingOrder(true); xcb_flush(xcbConn); m_mapped = true; } WlVisit::~WlVisit() { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); xcb_destroy_window(xcbConn, m_window); xcb_flush(xcbConn); } bool WlVisit::leave() { DataBridge::self()->dnd()->overwriteRequestorWindow(XCB_WINDOW_NONE); unmapProxyWindow(); return m_finished; } bool WlVisit::handleClientMessage(xcb_client_message_event_t *event) { if (event->window != m_window) { // different window return false; } if (event->type == atoms->xdnd_enter) { return handleEnter(event); } else if (event->type == atoms->xdnd_position) { return handlePosition(event); } else if (event->type == atoms->xdnd_drop) { return handleDrop(event); } else if (event->type == atoms->xdnd_leave) { return handleLeave(event); } return false; } static bool hasMimeName(const Mimes &mimes, const QString &name) { return std::any_of(mimes.begin(), mimes.end(), [name](const Mime &m) { return m.first == name; }); } bool WlVisit::handleEnter(xcb_client_message_event_t *event) { if (m_entered) { // a drag already entered return true; } m_entered = true; xcb_client_message_data_t *data = &event->data; m_srcWindow = data->data32[0]; m_version = data->data32[1] >> 24; // get types Mimes offers; if (!(data->data32[1] & 1)) { // message has only max 3 types (which are directly in data) for (size_t i = 0; i < 3; i++) { xcb_atom_t mimeAtom = data->data32[2 + i]; const auto mimeStrings = atomToMimeTypes(mimeAtom); for (const auto mime : mimeStrings ) { if (!hasMimeName(offers, mime)) { offers << Mime(mime, mimeAtom); } } } } else { // more than 3 types -> in window property getMimesFromWinProperty(offers); } Q_EMIT offersReceived(offers); return true; } void WlVisit::getMimesFromWinProperty(Mimes &offers) { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); auto cookie = xcb_get_property(xcbConn, 0, m_srcWindow, atoms->xdnd_type_list, XCB_GET_PROPERTY_TYPE_ANY, 0, 0x1fffffff); auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr); if (reply == nullptr) { return; } if (reply->type != XCB_ATOM_ATOM || reply->value_len == 0) { // invalid reply value free(reply); return; } xcb_atom_t *mimeAtoms = static_cast(xcb_get_property_value(reply)); for (size_t i = 0; i < reply->value_len; ++i) { const auto mimeStrings = atomToMimeTypes(mimeAtoms[i]); for (const auto mime : mimeStrings) { if (!hasMimeName(offers, mime)) { offers << Mime(mime, mimeAtoms[i]); } } } free(reply); } bool WlVisit::handlePosition(xcb_client_message_event_t *event) { xcb_client_message_data_t *data = &event->data; m_srcWindow = data->data32[0]; if (!m_target) { // not over Wl window at the moment m_action = DnDAction::None; m_actionAtom = XCB_ATOM_NONE; sendStatus(); return true; } const uint32_t pos = data->data32[2]; Q_UNUSED(pos); const xcb_timestamp_t timestamp = data->data32[3]; m_drag->x11Source()->setTimestamp(timestamp); xcb_atom_t actionAtom = m_version > 1 ? data->data32[4] : atoms->xdnd_action_copy; auto action = Drag::atomToClientAction(actionAtom); if (action == DnDAction::None) { // copy action is always possible in XDND action = DnDAction::Copy; actionAtom = atoms->xdnd_action_copy; } if (m_action != action) { m_action = action; m_actionAtom = actionAtom; m_drag->setDragAndDropAction(m_action); } sendStatus(); return true; } bool WlVisit::handleDrop(xcb_client_message_event_t *event) { m_dropHandled = true; xcb_client_message_data_t *data = &event->data; m_srcWindow = data->data32[0]; const xcb_timestamp_t timestamp = data->data32[2]; m_drag->x11Source()->setTimestamp(timestamp); // we do nothing more here, the drop is being processed // through the X11Source object doFinish(); return true; } void WlVisit::doFinish() { m_finished = true; unmapProxyWindow(); Q_EMIT finish(this); } bool WlVisit::handleLeave(xcb_client_message_event_t *event) { m_entered = false; xcb_client_message_data_t *data = &event->data; m_srcWindow = data->data32[0]; doFinish(); return true; } void WlVisit::sendStatus() { // receive position events uint32_t flags = 1 << 1; if (targetAcceptsAction()) { // accept the drop flags |= (1 << 0); } xcb_client_message_data_t data = {0}; data.data32[0] = m_window; data.data32[1] = flags; data.data32[4] = flags & (1 << 0) ? m_actionAtom : static_cast(XCB_ATOM_NONE); Drag::sendClientMessage(m_srcWindow, atoms->xdnd_status, &data); } void WlVisit::sendFinished() { const bool accepted = m_entered && m_action != DnDAction::None; xcb_client_message_data_t data = {0}; data.data32[0] = m_window; data.data32[1] = accepted; data.data32[2] = accepted ? m_actionAtom : static_cast(XCB_ATOM_NONE); Drag::sendClientMessage(m_srcWindow, atoms->xdnd_finished, &data); } bool WlVisit::targetAcceptsAction() const { if (m_action == DnDAction::None) { return false; } const auto selAction = m_drag->selectedDragAndDropAction(); return selAction == m_action || selAction == DnDAction::Copy; } void WlVisit::unmapProxyWindow() { if (!m_mapped) { return; } xcb_connection_t *xcbConn = kwinApp()->x11Connection(); xcb_unmap_window(xcbConn, m_window); workspace()->removeManualOverlay(m_window); workspace()->updateStackingOrder(true); xcb_flush(xcbConn); m_mapped = false; } } // namespace Xwl } // namespace KWin diff --git a/xwl/selection_source.cpp b/xwl/selection_source.cpp index 96cfa0491..6ab16dd5f 100644 --- a/xwl/selection_source.cpp +++ b/xwl/selection_source.cpp @@ -1,323 +1,321 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "selection_source.h" #include "selection.h" #include "transfer.h" #include "atoms.h" #include "wayland_server.h" #include #include #include #include #include #include #include #include #include namespace KWin { namespace Xwl { SelectionSource::SelectionSource(Selection *selection) : QObject(selection) , m_selection(selection) , m_window(selection->window()) { } -WlSource::WlSource(Selection *selection, KWaylandServer::DataDeviceInterface *ddi) +WlSource::WlSource(Selection *selection) : SelectionSource(selection) - , m_ddi(ddi) { - Q_ASSERT(ddi); } -void WlSource::setDataSourceIface(KWaylandServer::DataSourceInterface *dsi) +void WlSource::setDataSourceIface(KWaylandServer::AbstractDataSource *dsi) { if (m_dsi == dsi) { return; } for (const auto &mime : dsi->mimeTypes()) { m_offers << mime; } m_offerConnection = connect(dsi, &KWaylandServer::DataSourceInterface::mimeTypeOffered, this, &WlSource::receiveOffer); m_dsi = dsi; } void WlSource::receiveOffer(const QString &mime) { m_offers << mime; } void WlSource::sendSelectionNotify(xcb_selection_request_event_t *event, bool success) { Selection::sendSelectionNotify(event, success); } bool WlSource::handleSelectionRequest(xcb_selection_request_event_t *event) { if (event->target == atoms->targets) { sendTargets(event); } else if (event->target == atoms->timestamp) { sendTimestamp(event); } else if (event->target == atoms->delete_atom) { sendSelectionNotify(event, true); } else { // try to send mime data if (!checkStartTransfer(event)) { sendSelectionNotify(event, false); } } return true; } void WlSource::sendTargets(xcb_selection_request_event_t *event) { QVector targets; targets.resize(m_offers.size() + 2); targets[0] = atoms->timestamp; targets[1] = atoms->targets; size_t cnt = 2; for (const auto mime : m_offers) { targets[cnt] = Selection::mimeTypeToAtom(mime); cnt++; } xcb_change_property(kwinApp()->x11Connection(), XCB_PROP_MODE_REPLACE, event->requestor, event->property, XCB_ATOM_ATOM, 32, cnt, targets.data()); sendSelectionNotify(event, true); } void WlSource::sendTimestamp(xcb_selection_request_event_t *event) { const xcb_timestamp_t time = timestamp(); xcb_change_property(kwinApp()->x11Connection(), XCB_PROP_MODE_REPLACE, event->requestor, event->property, XCB_ATOM_INTEGER, 32, 1, &time); sendSelectionNotify(event, true); } bool WlSource::checkStartTransfer(xcb_selection_request_event_t *event) { // check interfaces available - if (!m_ddi || !m_dsi) { + if (!m_dsi) { return false; } const auto targets = Selection::atomToMimeTypes(event->target); if (targets.isEmpty()) { qCDebug(KWIN_XWL) << "Unknown selection atom. Ignoring request."; return false; } const auto firstTarget = targets[0]; auto cmp = [firstTarget](const QString &b) { if (firstTarget == "text/uri-list") { // Wayland sources might announce the old mime or the new standard return firstTarget == b || b == "text/x-uri"; } return firstTarget == b; }; // check supported mimes const auto offers = m_dsi->mimeTypes(); const auto mimeIt = std::find_if(offers.begin(), offers.end(), cmp); if (mimeIt == offers.end()) { // Requested Mime not supported. Not sending selection. return false; } int p[2]; if (pipe(p) == -1) { qCWarning(KWIN_XWL) << "Pipe failed. Not sending selection."; return false; } m_dsi->requestData(*mimeIt, p[1]); waylandServer()->dispatch(); Q_EMIT transferReady(new xcb_selection_request_event_t(*event), p[0]); return true; } X11Source::X11Source(Selection *selection, xcb_xfixes_selection_notify_event_t *event) : SelectionSource(selection) , m_owner(event->owner) { setTimestamp(event->timestamp); } void X11Source::getTargets() { xcb_connection_t *xcbConn = kwinApp()->x11Connection(); /* will lead to a selection request event for the new owner */ xcb_convert_selection(xcbConn, window(), selection()->atom(), atoms->targets, atoms->wl_selection, timestamp()); xcb_flush(xcbConn); } using Mime = QPair; void X11Source::handleTargets() { // receive targets xcb_connection_t *xcbConn = kwinApp()->x11Connection(); xcb_get_property_cookie_t cookie = xcb_get_property(xcbConn, 1, window(), atoms->wl_selection, XCB_GET_PROPERTY_TYPE_ANY, 0, 4096 ); auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr); if (!reply) { return; } if (reply->type != XCB_ATOM_ATOM) { free(reply); return; } QStringList added; QStringList removed; Mimes all; xcb_atom_t *value = static_cast(xcb_get_property_value(reply)); for (uint32_t i = 0; i < reply->value_len; i++) { if (value[i] == XCB_ATOM_NONE) { continue; } const auto mimeStrings = Selection::atomToMimeTypes(value[i]); if (mimeStrings.isEmpty()) { // TODO: this should never happen? assert? continue; } const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(), [value, i](const Mime &mime) { return mime.second == value[i]; } ); auto mimePair = Mime(mimeStrings[0], value[i]); if (mimeIt == m_offers.end()) { added << mimePair.first; } else { m_offers.removeAll(mimePair); } all << mimePair; } // all left in m_offers are not in the updated targets for (const auto mimePair : m_offers) { removed << mimePair.first; } m_offers = all; if (!added.isEmpty() || !removed.isEmpty()) { Q_EMIT offersChanged(added, removed); } free(reply); } void X11Source::setDataSource(KWayland::Client::DataSource *dataSource) { Q_ASSERT(dataSource); if (m_dataSource) { delete m_dataSource; } m_dataSource = dataSource; for (const Mime &offer : m_offers) { dataSource->offer(offer.first); } connect(dataSource, &KWayland::Client::DataSource::sendDataRequested, this, &X11Source::startTransfer); } void X11Source::setOffers(const Mimes &offers) { // TODO: share code with handleTargets and emit signals accordingly? m_offers = offers; } bool X11Source::handleSelectionNotify(xcb_selection_notify_event_t *event) { if (event->requestor != window()) { return false; } if (event->selection != selection()->atom()) { return false; } if (event->property == XCB_ATOM_NONE) { qCWarning(KWIN_XWL) << "Incoming X selection conversion failed"; return true; } if (event->target == atoms->targets) { handleTargets(); return true; } return false; } void X11Source::startTransfer(const QString &mimeName, qint32 fd) { const auto mimeIt = std::find_if(m_offers.begin(), m_offers.end(), [mimeName](const Mime &mime) { return mime.first == mimeName; } ); if (mimeIt == m_offers.end()) { qCDebug(KWIN_XWL) << "Sending X11 clipboard to Wayland failed: unsupported MIME."; close(fd); return; } Q_EMIT transferReady((*mimeIt).second, fd); } } // namespace Xwl } // namespace KWin diff --git a/xwl/selection_source.h b/xwl/selection_source.h index 46af9ea92..b2651e4aa 100644 --- a/xwl/selection_source.h +++ b/xwl/selection_source.h @@ -1,175 +1,175 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright 2019 Roman Gilg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef KWIN_XWL_SELECTION_SOURCE #define KWIN_XWL_SELECTION_SOURCE #include #include #include class QSocketNotifier; struct xcb_selection_request_event_t; struct xcb_xfixes_selection_notify_event_t; namespace KWayland { namespace Client { class DataSource; } } namespace KWaylandServer { class DataDeviceInterface; class DataSourceInterface; +class AbstractDataSource; } namespace KWin { namespace Xwl { class Selection; /** * Base class representing a data source. */ class SelectionSource : public QObject { Q_OBJECT public: SelectionSource(Selection *selection); xcb_timestamp_t timestamp() const { return m_timestamp; } void setTimestamp(xcb_timestamp_t time) { m_timestamp = time; } protected: Selection *selection() const { return m_selection; } void setWindow(xcb_window_t window) { m_window = window; } xcb_window_t window() const { return m_window; } private: xcb_timestamp_t m_timestamp = XCB_CURRENT_TIME; Selection *m_selection; xcb_window_t m_window; Q_DISABLE_COPY(SelectionSource) }; /** * Representing a Wayland native data source. */ class WlSource : public SelectionSource { Q_OBJECT public: - WlSource(Selection *selection, KWaylandServer::DataDeviceInterface *ddi); - void setDataSourceIface(KWaylandServer::DataSourceInterface *dsi); + WlSource(Selection *selection); + void setDataSourceIface(KWaylandServer::AbstractDataSource *dsi); bool handleSelectionRequest(xcb_selection_request_event_t *event); void sendTargets(xcb_selection_request_event_t *event); void sendTimestamp(xcb_selection_request_event_t *event); void receiveOffer(const QString &mime); void sendSelectionNotify(xcb_selection_request_event_t *event, bool success); Q_SIGNALS: void transferReady(xcb_selection_request_event_t *event, qint32 fd); private: bool checkStartTransfer(xcb_selection_request_event_t *event); - KWaylandServer::DataDeviceInterface *m_ddi = nullptr; - KWaylandServer::DataSourceInterface *m_dsi = nullptr; + KWaylandServer::AbstractDataSource *m_dsi = nullptr; QVector m_offers; QMetaObject::Connection m_offerConnection; Q_DISABLE_COPY(WlSource) }; using Mimes = QVector >; /** * Representing an X data source. */ class X11Source : public SelectionSource { Q_OBJECT public: X11Source(Selection *selection, xcb_xfixes_selection_notify_event_t *event); /** * @param ds must exist. * * X11Source does not take ownership of it in general, but if the function * is called again, it will delete the previous data source. */ void setDataSource(KWayland::Client::DataSource *dataSource); KWayland::Client::DataSource *dataSource() const { return m_dataSource; } void getTargets(); Mimes offers() const { return m_offers; } void setOffers(const Mimes &offers); bool handleSelectionNotify(xcb_selection_notify_event_t *event); void setRequestor(xcb_window_t window) { setWindow(window); } Q_SIGNALS: void offersChanged(const QStringList &added, const QStringList &removed); void transferReady(xcb_atom_t target, qint32 fd); private: void handleTargets(); void startTransfer(const QString &mimeName, qint32 fd); xcb_window_t m_owner; KWayland::Client::DataSource *m_dataSource = nullptr; Mimes m_offers; Q_DISABLE_COPY(X11Source) }; } // namespace Xwl } // namespace KWin #endif