You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
310 lines
8.4 KiB
310 lines
8.4 KiB
/* |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
#include "selection_source.h" |
|
#include "selection.h" |
|
#include "transfer.h" |
|
|
|
#include "atoms.h" |
|
#include "wayland_server.h" |
|
|
|
#include <KWayland/Client/connection_thread.h> |
|
#include <KWayland/Client/datadevicemanager.h> |
|
#include <KWayland/Client/datadevice.h> |
|
#include <KWayland/Client/datasource.h> |
|
|
|
#include <KWaylandServer/datadevice_interface.h> |
|
#include <KWaylandServer/datasource_interface.h> |
|
#include <KWaylandServer/seat_interface.h> |
|
|
|
#include <unistd.h> |
|
|
|
#include <xwayland_logging.h> |
|
|
|
namespace KWin |
|
{ |
|
namespace Xwl |
|
{ |
|
|
|
SelectionSource::SelectionSource(Selection *selection) |
|
: QObject(selection) |
|
, m_selection(selection) |
|
, m_window(selection->window()) |
|
{ |
|
} |
|
|
|
WlSource::WlSource(Selection *selection) |
|
: SelectionSource(selection) |
|
{ |
|
} |
|
|
|
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<xcb_atom_t> 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_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<QString, xcb_atom_t>; |
|
|
|
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_atom_t *>(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
|
|
|