kwin/xwl/drag_x.cpp
Vlad Zahorodnii 62a7db7028 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 17:48:21 +03:00

550 lines
17 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright 2019 Roman Gilg <subdiff@gmail.com>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#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>
#include <KWayland/Server/datasource_interface.h>
#include <KWayland/Server/seat_interface.h>
#include <KWayland/Server/surface_interface.h>
#include <QMouseEvent>
#include <QTimer>
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(), [this, eventTime](QPair<xcb_timestamp_t, bool> req) {
return req.first == eventTime;
});
if (it == m_dataRequests.end()) {
// transfer finished for a different drag
return;
}
Q_ASSERT(!(*it).second);
(*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<xcb_timestamp_t, bool>(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(), &KWayland::Server::DataDeviceManagerInterface::dataSourceCreated, this,
[this, dc](KWayland::Server::DataSourceInterface *dsi) {
Q_ASSERT(dsi);
if (dsi->client() != waylandServer()->internalConnection()) {
return;
}
QObject::disconnect(*dc);
delete dc;
connect(dsi, &KWayland::Server::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<AbstractClient*>(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<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)
{
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<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();
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<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)
{
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_atom_t *>(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<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;
xcb_client_message_data_t data = {0};
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;
}
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