2020-08-02 22:22:19 +00:00
|
|
|
/*
|
|
|
|
KWin - the KDE window manager
|
|
|
|
This file is part of the KDE project.
|
2018-08-22 12:56:48 +00:00
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
|
2018-08-22 12:56:48 +00:00
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
*/
|
2018-08-22 12:56:48 +00:00
|
|
|
#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 <KWayland/Client/datadevice.h>
|
|
|
|
#include <KWayland/Client/datasource.h>
|
|
|
|
|
2020-04-29 15:18:41 +00:00
|
|
|
#include <KWaylandServer/datasource_interface.h>
|
|
|
|
#include <KWaylandServer/seat_interface.h>
|
|
|
|
#include <KWaylandServer/surface_interface.h>
|
2018-08-22 12:56:48 +00:00
|
|
|
|
|
|
|
#include <QMouseEvent>
|
|
|
|
#include <QTimer>
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
namespace KWin
|
|
|
|
{
|
|
|
|
namespace Xwl
|
|
|
|
{
|
2018-08-22 12:56:48 +00:00
|
|
|
|
2021-06-03 09:04:26 +00:00
|
|
|
using DnDAction = KWayland::Client::DataDeviceManager::DnDAction;
|
|
|
|
using DnDActions = KWayland::Client::DataDeviceManager::DnDActions;
|
|
|
|
|
|
|
|
static DnDAction atomToClientAction(xcb_atom_t atom)
|
|
|
|
{
|
|
|
|
if (atom == atoms->xdnd_action_copy) {
|
|
|
|
return DnDAction::Copy;
|
|
|
|
} else if (atom == atoms->xdnd_action_move) {
|
|
|
|
return DnDAction::Move;
|
|
|
|
} else if (atom == atoms->xdnd_action_ask) {
|
|
|
|
// we currently do not support it - need some test client first
|
|
|
|
return DnDAction::None;
|
|
|
|
// return DnDAction::Ask;
|
|
|
|
}
|
|
|
|
return DnDAction::None;
|
|
|
|
}
|
|
|
|
|
|
|
|
xcb_atom_t clientActionToAtom(DnDAction action)
|
|
|
|
{
|
|
|
|
if (action == DnDAction::Copy) {
|
|
|
|
return atoms->xdnd_action_copy;
|
|
|
|
} else if (action == DnDAction::Move) {
|
|
|
|
return atoms->xdnd_action_move;
|
|
|
|
} else if (action == DnDAction::Ask) {
|
|
|
|
// we currently do not support it - need some test client first
|
|
|
|
return XCB_ATOM_NONE;
|
|
|
|
// return atoms->xdnd_action_ask;
|
|
|
|
}
|
|
|
|
return XCB_ATOM_NONE;
|
|
|
|
}
|
|
|
|
|
2018-08-24 21:05:35 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-08-22 12:56:48 +00:00
|
|
|
XToWlDrag::XToWlDrag(X11Source *source)
|
2019-07-02 19:56:03 +00:00
|
|
|
: m_source(source)
|
2018-08-22 12:56:48 +00:00
|
|
|
{
|
|
|
|
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
|
2020-03-15 19:59:29 +00:00
|
|
|
auto it = std::find_if(m_dataRequests.begin(), m_dataRequests.end(), [eventTime](const QPair<xcb_timestamp_t, bool> &req) {
|
2020-04-06 17:10:03 +00:00
|
|
|
return req.first == eventTime && req.second == false;
|
2018-08-22 12:56:48 +00:00
|
|
|
});
|
|
|
|
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);
|
2019-07-02 19:56:03 +00:00
|
|
|
m_dataRequests << QPair<xcb_timestamp_t, bool>(m_source->timestamp(), false);
|
2018-08-22 12:56:48 +00:00
|
|
|
});
|
|
|
|
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();
|
2020-04-29 15:18:41 +00:00
|
|
|
*dc = connect(waylandServer()->dataDeviceManager(), &KWaylandServer::DataDeviceManagerInterface::dataSourceCreated, this,
|
|
|
|
[this, dc](KWaylandServer::DataSourceInterface *dsi) {
|
2018-08-22 12:56:48 +00:00
|
|
|
Q_ASSERT(dsi);
|
2020-05-26 08:55:54 +00:00
|
|
|
if (dsi->client() != waylandServer()->internalConnection()->client()) {
|
2018-08-22 12:56:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
QObject::disconnect(*dc);
|
|
|
|
delete dc;
|
2020-04-29 15:18:41 +00:00
|
|
|
connect(dsi, &KWaylandServer::DataSourceInterface::mimeTypeOffered, this, &XToWlDrag::offerCallback);
|
2018-08-22 12:56:48 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
DragEventReply XToWlDrag::moveFilter(Toplevel *target, const QPoint &pos)
|
2018-08-22 12:56:48 +00:00
|
|
|
{
|
|
|
|
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 (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<AbstractClient*>(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)
|
|
|
|
{
|
2021-05-13 16:41:39 +00:00
|
|
|
for (auto *visit : qAsConst(m_oldVisits)) {
|
2018-08-22 12:56:48 +00:00
|
|
|
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)
|
|
|
|
{
|
2019-07-02 19:56:03 +00:00
|
|
|
m_source->setOffers(offers);
|
2018-08-22 12:56:48 +00:00
|
|
|
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;
|
2020-08-26 17:24:02 +00:00
|
|
|
for (const auto &mimePair : offers) {
|
2018-08-22 12:56:48 +00:00
|
|
|
m_dataSource->offer(mimePair.first);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
using Mime = QPair<QString, xcb_atom_t>;
|
|
|
|
|
|
|
|
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();
|
2021-09-03 11:47:57 +00:00
|
|
|
|
|
|
|
workspace()->activateClient(ac);
|
|
|
|
auto seat = waylandServer()->seat();
|
|
|
|
auto dropTarget = seat->dropHandlerForSurface(ac->surface());
|
|
|
|
if (!dropTarget || !ac->surface()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
seat->setDragTarget(dropTarget, ac->surface(), ac->inputTransformation());
|
2018-08-22 12:56:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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<xcb_timestamp_t, bool> 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)
|
|
|
|
{
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_connection_t *xcbConn = kwinApp()->x11Connection();
|
2018-08-22 12:56:48 +00:00
|
|
|
|
|
|
|
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,
|
2020-07-28 07:58:35 +00:00
|
|
|
XCB_COPY_FROM_PARENT,
|
2018-08-22 12:56:48 +00:00
|
|
|
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);
|
2018-08-24 21:10:48 +00:00
|
|
|
workspace()->addManualOverlay(m_window);
|
|
|
|
workspace()->updateStackingOrder(true);
|
2018-08-22 12:56:48 +00:00
|
|
|
|
|
|
|
xcb_flush(xcbConn);
|
|
|
|
m_mapped = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
WlVisit::~WlVisit()
|
|
|
|
{
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_connection_t *xcbConn = kwinApp()->x11Connection();
|
2018-08-22 12:56:48 +00:00
|
|
|
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; });
|
|
|
|
}
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
bool WlVisit::handleEnter(xcb_client_message_event_t *event)
|
2018-08-22 12:56:48 +00:00
|
|
|
{
|
|
|
|
if (m_entered) {
|
|
|
|
// a drag already entered
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
m_entered = true;
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_client_message_data_t *data = &event->data;
|
2018-08-22 12:56:48 +00:00
|
|
|
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];
|
2018-08-24 21:05:35 +00:00
|
|
|
const auto mimeStrings = atomToMimeTypes(mimeAtom);
|
2020-08-26 17:24:02 +00:00
|
|
|
for (const auto &mime : mimeStrings ) {
|
2018-08-22 12:56:48 +00:00
|
|
|
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)
|
|
|
|
{
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_connection_t *xcbConn = kwinApp()->x11Connection();
|
2018-08-22 12:56:48 +00:00
|
|
|
auto cookie = xcb_get_property(xcbConn,
|
|
|
|
0,
|
|
|
|
m_srcWindow,
|
|
|
|
atoms->xdnd_type_list,
|
|
|
|
XCB_GET_PROPERTY_TYPE_ANY,
|
|
|
|
0, 0x1fffffff);
|
|
|
|
|
Use nullptr everywhere
Summary:
Because KWin is a very old project, we use three kinds of null pointer
literals: 0, NULL, and nullptr. Since C++11, it's recommended to use
nullptr keyword.
This change converts all usages of 0 and NULL literal to nullptr. Even
though it breaks git history, we need to do it in order to have consistent
code as well to ease code reviews (it's very tempting for some people to
add unrelated changes to their patches, e.g. converting NULL to nullptr).
Test Plan: Compiles.
Reviewers: #kwin, davidedmundson, romangg
Reviewed By: #kwin, davidedmundson, romangg
Subscribers: romangg, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D23618
2019-09-19 14:46:54 +00:00
|
|
|
auto *reply = xcb_get_property_reply(xcbConn, cookie, nullptr);
|
|
|
|
if (reply == nullptr) {
|
2018-08-22 12:56:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (reply->type != XCB_ATOM_ATOM || reply->value_len == 0) {
|
|
|
|
// invalid reply value
|
|
|
|
free(reply);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_atom_t *mimeAtoms = static_cast<xcb_atom_t *>(xcb_get_property_value(reply));
|
2018-08-22 12:56:48 +00:00
|
|
|
for (size_t i = 0; i < reply->value_len; ++i) {
|
2018-08-24 21:05:35 +00:00
|
|
|
const auto mimeStrings = atomToMimeTypes(mimeAtoms[i]);
|
2020-08-26 17:24:02 +00:00
|
|
|
for (const auto &mime : mimeStrings) {
|
2018-08-22 12:56:48 +00:00
|
|
|
if (!hasMimeName(offers, mime)) {
|
|
|
|
offers << Mime(mime, mimeAtoms[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
free(reply);
|
|
|
|
}
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
bool WlVisit::handlePosition(xcb_client_message_event_t *event)
|
2018-08-22 12:56:48 +00:00
|
|
|
{
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_client_message_data_t *data = &event->data;
|
2018-08-22 12:56:48 +00:00
|
|
|
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;
|
2021-06-03 09:04:26 +00:00
|
|
|
auto action = atomToClientAction(actionAtom);
|
2018-08-22 12:56:48 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
bool WlVisit::handleDrop(xcb_client_message_event_t *event)
|
2018-08-22 12:56:48 +00:00
|
|
|
{
|
|
|
|
m_dropHandled = true;
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_client_message_data_t *data = &event->data;
|
2018-08-22 12:56:48 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
bool WlVisit::handleLeave(xcb_client_message_event_t *event)
|
2018-08-22 12:56:48 +00:00
|
|
|
{
|
|
|
|
m_entered = false;
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_client_message_data_t *data = &event->data;
|
2018-08-22 12:56:48 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-08-26 17:24:02 +00:00
|
|
|
xcb_client_message_data_t data = {};
|
2018-08-22 12:56:48 +00:00
|
|
|
data.data32[0] = m_window;
|
|
|
|
data.data32[1] = flags;
|
|
|
|
data.data32[4] = flags & (1 << 0) ? m_actionAtom : static_cast<uint32_t>(XCB_ATOM_NONE);
|
|
|
|
Drag::sendClientMessage(m_srcWindow, atoms->xdnd_status, &data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void WlVisit::sendFinished()
|
|
|
|
{
|
|
|
|
const bool accepted = m_entered && m_action != DnDAction::None;
|
2020-08-26 17:24:02 +00:00
|
|
|
xcb_client_message_data_t data = {};
|
2018-08-22 12:56:48 +00:00
|
|
|
data.data32[0] = m_window;
|
|
|
|
data.data32[1] = accepted;
|
|
|
|
data.data32[2] = accepted ? m_actionAtom : static_cast<uint32_t>(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;
|
|
|
|
}
|
2019-07-02 19:56:03 +00:00
|
|
|
xcb_connection_t *xcbConn = kwinApp()->x11Connection();
|
2018-08-22 12:56:48 +00:00
|
|
|
xcb_unmap_window(xcbConn, m_window);
|
2018-08-24 21:10:48 +00:00
|
|
|
workspace()->removeManualOverlay(m_window);
|
|
|
|
workspace()->updateStackingOrder(true);
|
2018-08-22 12:56:48 +00:00
|
|
|
xcb_flush(xcbConn);
|
|
|
|
m_mapped = false;
|
|
|
|
}
|
|
|
|
|
2019-07-02 19:56:03 +00:00
|
|
|
} // namespace Xwl
|
|
|
|
} // namespace KWin
|