xcb: Add support for XdndActionList property

This allows to pass and receive possible drop actions from other
processes, including GTK applications.

Fixes: QTBUG-75744
Change-Id: I944edc6fa00f8801a25912e70eb104a647a9fc0e
Reviewed-by: JiDe Zhang <zccrs@live.com>
Reviewed-by: Liang Qi <liang.qi@qt.io>
bb10
Alexander Volkov 2020-03-16 20:54:10 +03:00
parent cb509f3aee
commit cc1d891b8e
4 changed files with 118 additions and 7 deletions

View File

@ -182,6 +182,7 @@ static const char *xcb_atomnames = {
"XdndActionCopy\0"
"XdndActionLink\0"
"XdndActionMove\0"
"XdndActionAsk\0"
"XdndActionPrivate\0"
// Xkb

View File

@ -183,6 +183,7 @@ public:
XdndActionCopy,
XdndActionLink,
XdndActionMove,
XdndActionAsk,
XdndActionPrivate,
// Xkb

View File

@ -216,6 +216,22 @@ void QXcbDrag::endDrag()
initiatorWindow.clear();
}
Qt::DropAction QXcbDrag::defaultAction(Qt::DropActions possibleActions, Qt::KeyboardModifiers modifiers) const
{
if (currentDrag() || drop_actions.isEmpty())
return QBasicDrag::defaultAction(possibleActions, modifiers);
return toDropAction(drop_actions.first());
}
void QXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
{
if (event->window != xdnd_dragsource || event->atom != atom(QXcbAtom::XdndActionList))
return;
readActionList();
}
static
bool windowInteractsWithPosition(xcb_connection_t *connection, const QPoint & pos, xcb_window_t w, xcb_shape_sk_t shapeType)
{
@ -470,16 +486,20 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
move.data.data32[1] = 0; // flags
move.data.data32[2] = (globalPos.x() << 16) + globalPos.y();
move.data.data32[3] = connection()->time();
move.data.data32[4] = toXdndAction(defaultAction(currentDrag()->supportedActions(), mods));
const auto supportedActions = currentDrag()->supportedActions();
const auto requestedAction = defaultAction(supportedActions, mods);
move.data.data32[4] = toXdndAction(requestedAction);
qCDebug(lcQpaXDnd) << "sending XdndPosition to target:" << target;
source_time = connection()->time();
if (w)
if (w) {
handle_xdnd_position(w, &move, b, mods);
else
} else {
setActionList(requestedAction, supportedActions);
xcb_send_event(xcb_connection(), false, proxy_target, XCB_EVENT_MASK_NO_EVENT, (const char *)&move);
}
}
static const bool isUnity = qgetenv("XDG_CURRENT_DESKTOP").toLower() == "unity";
@ -560,6 +580,16 @@ Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
return Qt::CopyAction;
}
Qt::DropActions QXcbDrag::toDropActions(const QVector<xcb_atom_t> &atoms) const
{
Qt::DropActions actions;
for (const auto actionAtom : atoms) {
if (actionAtom != atom(QXcbAtom::XdndActionAsk))
actions |= toDropAction(actionAtom);
}
return actions;
}
xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
{
switch (a) {
@ -577,6 +607,60 @@ xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
}
}
void QXcbDrag::readActionList()
{
drop_actions.clear();
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
atom(QXcbAtom::XdndActionList), XCB_ATOM_ATOM,
0, 1024);
if (reply && reply->type != XCB_NONE && reply->format == 32) {
int length = xcb_get_property_value_length(reply.get()) / 4;
xcb_atom_t *atoms = (xcb_atom_t *)xcb_get_property_value(reply.get());
for (int i = 0; i < length; ++i)
drop_actions.append(atoms[i]);
}
}
void QXcbDrag::setActionList(Qt::DropAction requestedAction, Qt::DropActions supportedActions)
{
#ifndef QT_NO_CLIPBOARD
QVector<xcb_atom_t> actions;
if (requestedAction != Qt::IgnoreAction)
actions.append(toXdndAction(requestedAction));
auto checkAppend = [this, requestedAction, supportedActions, &actions](Qt::DropAction action) {
if (requestedAction != action && supportedActions & action)
actions.append(toXdndAction(action));
};
checkAppend(Qt::CopyAction);
checkAppend(Qt::MoveAction);
checkAppend(Qt::LinkAction);
if (current_actions != actions) {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->clipboard()->owner(),
atom(QXcbAtom::XdndActionList),
XCB_ATOM_ATOM, 32, actions.size(), actions.constData());
current_actions = actions;
}
#endif
}
void QXcbDrag::startListeningForActionListChanges()
{
connection()->addWindowEventListener(xdnd_dragsource, this);
const uint32_t event_mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask);
}
void QXcbDrag::stopListeningForActionListChanges()
{
const uint32_t event_mask[] = { XCB_EVENT_MASK_NO_EVENT };
xcb_change_window_attributes(xcb_connection(), xdnd_dragsource, XCB_CW_EVENT_MASK, event_mask);
connection()->removeWindowEventListener(xdnd_dragsource);
}
int QXcbDrag::findTransactionByWindow(xcb_window_t window)
{
int at = -1;
@ -657,6 +741,9 @@ void QXcbDrag::handleEnter(QPlatformWindow *, const xcb_client_message_event_t *
return;
xdnd_dragsource = event->data.data32[0];
startListeningForActionListChanges();
readActionList();
if (!proxy)
proxy = xdndProxy(connection(), xdnd_dragsource);
current_proxy_target = proxy ? proxy : xdnd_dragsource;
@ -723,7 +810,9 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message
supported_actions = currentDrag()->supportedActions();
} else {
dropData = m_dropData;
supported_actions = Qt::DropActions(toDropAction(e->data.data32[4]));
supported_actions = toDropActions(drop_actions);
if (e->data.data32[4] != atom(QXcbAtom::XdndActionAsk))
supported_actions |= Qt::DropActions(toDropAction(e->data.data32[4]));
}
auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
@ -867,8 +956,10 @@ void QXcbDrag::handleLeave(QPlatformWindow *w, const xcb_client_message_event_t
// If the target receives XdndLeave, it frees any cached data and forgets the whole incident.
qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndLeave";
if (!currentWindow || w != currentWindow.data()->handle())
if (!currentWindow || w != currentWindow.data()->handle()) {
stopListeningForActionListChanges();
return; // sanity
}
// ###
// if (checkEmbedded(current_embedding_widget, event)) {
@ -883,6 +974,8 @@ void QXcbDrag::handleLeave(QPlatformWindow *w, const xcb_client_message_event_t
event->data.data32[0], xdnd_dragsource);
}
stopListeningForActionListChanges();
QWindowSystemInterface::handleDrag(w->window(), nullptr, QPoint(), Qt::IgnoreAction, 0, 0);
}
@ -929,6 +1022,7 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
qCDebug(lcQpaXDnd) << "target:" << event->window << "received XdndDrop";
if (!currentWindow) {
stopListeningForActionListChanges();
xdnd_dragsource = 0;
return; // sanity
}
@ -951,7 +1045,7 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
supported_drop_actions = Qt::DropActions(l[4]);
} else {
dropData = m_dropData;
supported_drop_actions = accepted_drop_action;
supported_drop_actions = accepted_drop_action | toDropActions(drop_actions);
}
if (!dropData)
@ -986,6 +1080,8 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
xcb_send_event(xcb_connection(), false, current_proxy_target,
XCB_EVENT_MASK_NO_EVENT, (char *)&finished);
stopListeningForActionListChanges();
dropped = true;
}

View File

@ -68,7 +68,7 @@ class QXcbScreen;
class QDrag;
class QShapedPixmapWindow;
class QXcbDrag : public QXcbObject, public QBasicDrag
class QXcbDrag : public QXcbObject, public QBasicDrag, public QXcbWindowEventListener
{
public:
QXcbDrag(QXcbConnection *c);
@ -82,6 +82,10 @@ public:
void drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) override;
void endDrag() override;
Qt::DropAction defaultAction(Qt::DropActions possibleActions, Qt::KeyboardModifiers modifiers) const override;
void handlePropertyNotifyEvent(const xcb_property_notify_event_t *event) override;
void handleEnter(QPlatformWindow *window, const xcb_client_message_event_t *event, xcb_window_t proxy = 0);
void handlePosition(QPlatformWindow *w, const xcb_client_message_event_t *event);
void handleLeave(QPlatformWindow *w, const xcb_client_message_event_t *event);
@ -114,8 +118,14 @@ private:
void send_leave();
Qt::DropAction toDropAction(xcb_atom_t atom) const;
Qt::DropActions toDropActions(const QVector<xcb_atom_t> &atoms) const;
xcb_atom_t toXdndAction(Qt::DropAction a) const;
void readActionList();
void setActionList(Qt::DropAction requestedAction, Qt::DropActions supportedActions);
void startListeningForActionListChanges();
void stopListeningForActionListChanges();
QPointer<QWindow> initiatorWindow;
QPointer<QWindow> currentWindow;
QPoint currentPosition;
@ -159,6 +169,9 @@ private:
QVector<xcb_atom_t> drag_types;
QVector<xcb_atom_t> current_actions;
QVector<xcb_atom_t> drop_actions;
struct Transaction
{
xcb_timestamp_t timestamp;