katie/src/gui/kernel/qdnd_x11.cpp
Ivailo Monev 9ebc979ae2 make some X11 extensions optional as was in stock Qt4
this makes it possible to build without tricks on Slitaz for an example (or
at least the GUI component, I have yet to test everything) where only the
bare minimum is installed/available

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2016-07-10 02:53:30 +03:00

2119 lines
68 KiB
C++

/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qplatformdefs.h"
#include "qapplication.h"
#include "qabstracteventdispatcher.h"
#ifndef QT_NO_DRAGANDDROP
#include "qwidget.h"
#include "qpainter.h"
#include "qpixmap.h"
#include "qbitmap.h"
#include "qdesktopwidget.h"
#include "qevent.h"
#include "qiodevice.h"
#include "qpointer.h"
#include "qcursor.h"
#include "qelapsedtimer.h"
#include "qvariant.h"
#include "qvector.h"
#include "qurl.h"
#include "qdebug.h"
#include "qimagewriter.h"
#include "qbuffer.h"
#include "qtextcodec.h"
#include "qdnd_p.h"
#include "qapplication_p.h"
#include "qt_x11_p.h"
#include "qx11info_x11.h"
#include "qwidget_p.h"
#include "qcursor_p.h"
#ifndef QT_NO_XFIXES
#include <X11/extensions/Xfixes.h>
#endif
QT_BEGIN_NAMESPACE
// #define DND_DEBUG
#ifdef DND_DEBUG
#define DEBUG qDebug
#else
#define DEBUG if(0) qDebug
#endif
#ifdef DND_DEBUG
#define DNDDEBUG qDebug()
#else
#define DNDDEBUG if(0) qDebug()
#endif
static int findXdndDropTransactionByWindow(Window window)
{
int at = -1;
for (int i = 0; i < X11->dndDropTransactions.count(); ++i) {
const QXdndDropTransaction &t = X11->dndDropTransactions.at(i);
if (t.target == window || t.proxy_target == window) {
at = i;
break;
}
}
return at;
}
static int findXdndDropTransactionByTime(Time timestamp)
{
int at = -1;
for (int i = 0; i < X11->dndDropTransactions.count(); ++i) {
const QXdndDropTransaction &t = X11->dndDropTransactions.at(i);
if (t.timestamp == timestamp) {
at = i;
break;
}
}
return at;
}
// timer used to discard old XdndDrop transactions
static int transaction_expiry_timer = -1;
enum { XdndDropTransactionTimeout = 5000 }; // 5 seconds
static void restartXdndDropExpiryTimer()
{
if (transaction_expiry_timer != -1)
QDragManager::self()->killTimer(transaction_expiry_timer);
transaction_expiry_timer = QDragManager::self()->startTimer(XdndDropTransactionTimeout);
}
// find an ancestor with XdndAware on it
static Window findXdndAwareParent(Window window)
{
Window target = 0;
forever {
// check if window has XdndAware
Atom type = 0;
int f;
unsigned long n, a;
unsigned char *data = 0;
if (XGetWindowProperty(X11->display, window, ATOM(XdndAware), 0, 0, False,
AnyPropertyType, &type, &f,&n,&a,&data) == Success) {
if (data)
XFree(data);
if (type) {
target = window;
break;
}
}
// try window's parent
Window root;
Window parent;
Window *children;
uint unused;
if (!XQueryTree(X11->display, window, &root, &parent, &children, &unused))
break;
if (children)
XFree(children);
if (window == root)
break;
window = parent;
}
return target;
}
// and all this stuff is copied -into- qapp_x11.cpp
static void handle_xdnd_position(QWidget *, const XEvent *, bool);
static void handle_xdnd_status(QWidget * w, const XEvent * xe, bool /*passive*/);
const int xdnd_version = 5;
static Qt::DropAction xdndaction_to_qtaction(Atom atom)
{
if (atom == ATOM(XdndActionCopy) || atom == 0)
return Qt::CopyAction;
if (atom == ATOM(XdndActionLink))
return Qt::LinkAction;
if (atom == ATOM(XdndActionMove))
return Qt::MoveAction;
return Qt::CopyAction;
}
static int qtaction_to_xdndaction(Qt::DropAction a)
{
switch (a) {
case Qt::CopyAction:
return ATOM(XdndActionCopy);
case Qt::LinkAction:
return ATOM(XdndActionLink);
case Qt::MoveAction:
case Qt::TargetMoveAction:
return ATOM(XdndActionMove);
case Qt::IgnoreAction:
return XNone;
default:
return ATOM(XdndActionCopy);
}
}
// clean up the stuff used.
static void qt_xdnd_cleanup();
static void qt_xdnd_send_leave();
// real variables:
// xid of current drag source
static Atom qt_xdnd_dragsource_xid = 0;
// the types in this drop. 100 is no good, but at least it's big.
const int qt_xdnd_max_type = 100;
static Atom qt_xdnd_types[qt_xdnd_max_type + 1];
// timer used when target wants "continuous" move messages (eg. scroll)
static int heartbeat = -1;
// rectangle in which the answer will be the same
static QRect qt_xdnd_source_sameanswer;
// top-level window we sent position to last.
static Window qt_xdnd_current_target;
// window to send events to (always valid if qt_xdnd_current_target)
static Window qt_xdnd_current_proxy_target;
static Time qt_xdnd_source_current_time;
// widget we forwarded position to last, and local position
static QPointer<QWidget> qt_xdnd_current_widget;
static QPoint qt_xdnd_current_position;
// timestamp from the XdndPosition and XdndDrop
static Time qt_xdnd_target_current_time;
// screen number containing the pointer... -1 means default
static int qt_xdnd_current_screen = -1;
// state of dragging... true if dragging, false if not
bool qt_xdnd_dragging = false;
static bool waiting_for_status = false;
// used to preset each new QDragMoveEvent
static Qt::DropAction last_target_accepted_action = Qt::IgnoreAction;
// Shift/Ctrl handling, and final drop status
static Qt::DropAction global_accepted_action = Qt::CopyAction;
static Qt::DropActions possible_actions = Qt::IgnoreAction;
// for embedding only
static QWidget* current_embedding_widget = 0;
static XEvent last_enter_event;
// cursors
static QCursor *noDropCursor = 0;
static QCursor *moveCursor = 0;
static QCursor *copyCursor = 0;
static QCursor *linkCursor = 0;
static QPixmap *defaultPm = 0;
static const int default_pm_hotx = -2;
static const int default_pm_hoty = -16;
static const char* const default_pm[] = {
"13 9 3 1",
". c None",
" c #000000",
"X c #FFFFFF",
"X X X X X X X",
" X X X X X X ",
"X ......... X",
" X.........X ",
"X ......... X",
" X.........X ",
"X ......... X",
" X X X X X X ",
"X X X X X X X"
};
class QShapedPixmapWidget : public QWidget
{
Q_OBJECT
public:
QShapedPixmapWidget(QWidget* w) :
QWidget(w,
Qt::Tool | Qt::FramelessWindowHint
| Qt::X11BypassWindowManagerHint
| Qt::BypassGraphicsProxyWidget)
{
setAttribute(Qt::WA_X11NetWmWindowTypeDND);
}
void setPixmap(const QPixmap &pm)
{
QBitmap mask = pm.mask();
if (!mask.isNull()) {
setMask(mask);
} else {
clearMask();
}
resize(pm.width(),pm.height());
pixmap = pm;
update();
}
QPoint pm_hot;
protected:
QPixmap pixmap;
void paintEvent(QPaintEvent*)
{
QPainter p(this);
p.drawPixmap(0, 0, pixmap);
}
};
#include "moc_qdnd_x11.cpp"
struct XdndData {
QShapedPixmapWidget *deco;
QWidget* desktop_proxy;
};
static XdndData xdnd_data = { 0, 0 };
class QExtraWidget : public QWidget
{
Q_DECLARE_PRIVATE(QWidget)
public:
inline QWExtra* extraData();
inline QTLWExtra* topData();
};
inline QWExtra* QExtraWidget::extraData() { return d_func()->extraData(); }
inline QTLWExtra* QExtraWidget::topData() { return d_func()->topData(); }
static WId xdndProxy(WId w)
{
Atom type = XNone;
int f;
unsigned long n, a;
unsigned char *retval = 0;
XGetWindowProperty(X11->display, w, ATOM(XdndProxy), 0, 1, False,
XA_WINDOW, &type, &f,&n,&a,&retval);
WId *proxy_id_ptr = (WId *)retval;
WId proxy_id = 0;
if (type == XA_WINDOW && proxy_id_ptr) {
proxy_id = *proxy_id_ptr;
XFree(proxy_id_ptr);
proxy_id_ptr = 0;
// Already exists. Real?
X11->ignoreBadwindow();
XGetWindowProperty(X11->display, proxy_id, ATOM(XdndProxy), 0, 1, False,
XA_WINDOW, &type, &f,&n,&a,&retval);
proxy_id_ptr = (WId *)retval;
if (X11->badwindow() || type != XA_WINDOW || !proxy_id_ptr || *proxy_id_ptr != proxy_id)
// Bogus - we will overwrite.
proxy_id = 0;
}
if (proxy_id_ptr)
XFree(proxy_id_ptr);
return proxy_id;
}
static bool xdndEnable(QWidget* w, bool on)
{
DNDDEBUG << "xdndEnable" << w << on;
if (on) {
QWidget * xdnd_widget = 0;
if ((w->windowType() == Qt::Desktop)) {
if (xdnd_data.desktop_proxy) // *WE* already have one.
return false;
// As per Xdnd4, use XdndProxy
XGrabServer(X11->display);
Q_ASSERT(w->testAttribute(Qt::WA_WState_Created));
WId proxy_id = xdndProxy(w->effectiveWinId());
if (!proxy_id) {
xdnd_widget = xdnd_data.desktop_proxy = new QWidget;
proxy_id = xdnd_data.desktop_proxy->effectiveWinId();
XChangeProperty (X11->display, w->effectiveWinId(), ATOM(XdndProxy),
XA_WINDOW, 32, PropModeReplace, (unsigned char *)&proxy_id, 1);
XChangeProperty (X11->display, proxy_id, ATOM(XdndProxy),
XA_WINDOW, 32, PropModeReplace, (unsigned char *)&proxy_id, 1);
}
XUngrabServer(X11->display);
} else {
xdnd_widget = w->window();
}
if (xdnd_widget) {
DNDDEBUG << "setting XdndAware for" << xdnd_widget << xdnd_widget->effectiveWinId();
Atom atm = (Atom)xdnd_version;
Q_ASSERT(xdnd_widget->testAttribute(Qt::WA_WState_Created));
XChangeProperty(X11->display, xdnd_widget->effectiveWinId(), ATOM(XdndAware),
XA_ATOM, 32, PropModeReplace, (unsigned char *)&atm, 1);
return true;
} else {
return false;
}
} else {
if ((w->windowType() == Qt::Desktop)) {
XDeleteProperty(X11->display, w->internalWinId(), ATOM(XdndProxy));
delete xdnd_data.desktop_proxy;
xdnd_data.desktop_proxy = 0;
} else {
DNDDEBUG << "not deleting XDndAware";
}
return true;
}
}
QByteArray QX11Data::xdndAtomToString(Atom a)
{
if (!a) return 0;
if (a == XA_STRING || a == ATOM(UTF8_STRING)) {
return "text/plain"; // some Xdnd clients are dumb
}
char *atom = XGetAtomName(display, a);
QByteArray result = atom;
XFree(atom);
return result;
}
Atom QX11Data::xdndStringToAtom(const char *mimeType)
{
if (!mimeType || !*mimeType)
return 0;
return XInternAtom(display, mimeType, False);
}
//$$$
QString QX11Data::xdndMimeAtomToString(Atom a)
{
QString atomName;
if (a) {
char *atom = XGetAtomName(display, a);
atomName = QString::fromLatin1(atom);
XFree(atom);
}
return atomName;
}
//$$$
Atom QX11Data::xdndMimeStringToAtom(const QString &mimeType)
{
if (mimeType.isEmpty())
return 0;
return XInternAtom(display, mimeType.toLatin1().constData(), False);
}
//$$$ replace ccxdndAtomToString()
QStringList QX11Data::xdndMimeFormatsForAtom(Atom a)
{
QStringList formats;
if (a) {
QString atomName = xdndMimeAtomToString(a);
formats.append(atomName);
// special cases for string type
if (a == ATOM(UTF8_STRING) || a == XA_STRING
|| a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT))
formats.append(QLatin1String("text/plain"));
// special cases for uris
if (atomName == QLatin1String("text/x-moz-url"))
formats.append(QLatin1String("text/uri-list"));
// special case for images
if (a == XA_PIXMAP)
formats.append(QLatin1String("image/ppm"));
}
return formats;
}
//$$$
bool QX11Data::xdndMimeDataForAtom(Atom a, QMimeData *mimeData, QByteArray *data, Atom *atomFormat, int *dataFormat)
{
bool ret = false;
*atomFormat = a;
*dataFormat = 8;
QString atomName = xdndMimeAtomToString(a);
if (QInternalMimeData::hasFormatHelper(atomName, mimeData)) {
*data = QInternalMimeData::renderDataHelper(atomName, mimeData);
if (atomName == QLatin1String("application/x-color"))
*dataFormat = 16;
ret = true;
} else {
if ((a == ATOM(UTF8_STRING) || a == XA_STRING
|| a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT))
&& QInternalMimeData::hasFormatHelper(QLatin1String("text/plain"), mimeData)) {
if (a == ATOM(UTF8_STRING)){
*data = QInternalMimeData::renderDataHelper(QLatin1String("text/plain"), mimeData);
ret = true;
} else if (a == XA_STRING) {
*data = QString::fromUtf8(QInternalMimeData::renderDataHelper(
QLatin1String("text/plain"), mimeData)).toLocal8Bit();
ret = true;
} else if (a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT)) {
// the ICCCM states that TEXT and COMPOUND_TEXT are in the
// encoding of choice, so we choose the encoding of the locale
QByteArray strData = QString::fromUtf8(QInternalMimeData::renderDataHelper(
QLatin1String("text/plain"), mimeData)).toLocal8Bit();
char *list[] = { strData.data(), NULL };
XICCEncodingStyle style = (a == ATOM(COMPOUND_TEXT))
? XCompoundTextStyle : XStdICCTextStyle;
XTextProperty textprop;
if (list[0] != NULL
&& XmbTextListToTextProperty(X11->display, list, 1, style,
&textprop) == Success) {
*atomFormat = textprop.encoding;
*dataFormat = textprop.format;
*data = QByteArray((const char *) textprop.value, textprop.nitems * textprop.format / 8);
ret = true;
DEBUG(" textprop type %lx\n"
" textprop name '%s'\n"
" format %d\n"
" %ld items\n"
" %d bytes\n",
textprop.encoding,
X11->xdndMimeAtomToString(textprop.encoding).toLatin1().data(),
textprop.format, textprop.nitems, data->size());
XFree(textprop.value);
}
}
} else if (atomName == QLatin1String("text/x-moz-url") &&
QInternalMimeData::hasFormatHelper(QLatin1String("text/uri-list"), mimeData)) {
QByteArray uri = QInternalMimeData::renderDataHelper(
QLatin1String("text/uri-list"), mimeData).split('\n').first();
QString mozUri = QString::fromLatin1(uri, uri.size());
mozUri += QLatin1Char('\n');
*data = QByteArray(reinterpret_cast<const char *>(mozUri.utf16()), mozUri.length() * 2);
ret = true;
} else if ((a == XA_PIXMAP || a == XA_BITMAP) && mimeData->hasImage()) {
QPixmap pm = qvariant_cast<QPixmap>(mimeData->imageData());
if (a == XA_BITMAP && pm.depth() != 1) {
QImage img = pm.toImage();
img = img.convertToFormat(QImage::Format_MonoLSB);
pm = QPixmap::fromImage(img);
}
QDragManager *dm = QDragManager::self();
if (dm) {
Pixmap handle = pm.handle();
*data = QByteArray((const char *) &handle, sizeof(Pixmap));
dm->xdndMimeTransferedPixmap[dm->xdndMimeTransferedPixmapIndex] = pm;
dm->xdndMimeTransferedPixmapIndex =
(dm->xdndMimeTransferedPixmapIndex + 1) % 2;
ret = true;
}
} else {
DEBUG("QClipboard: xdndMimeDataForAtom(): converting to type '%s' is not supported", qPrintable(atomName));
}
}
return ret && data != 0;
}
//$$$
QList<Atom> QX11Data::xdndMimeAtomsForFormat(const QString &format)
{
QList<Atom> atoms;
atoms.append(xdndMimeStringToAtom(format));
// special cases for strings
if (format == QLatin1String("text/plain")) {
atoms.append(ATOM(UTF8_STRING));
atoms.append(XA_STRING);
atoms.append(ATOM(TEXT));
atoms.append(ATOM(COMPOUND_TEXT));
}
// special cases for uris
if (format == QLatin1String("text/uri-list")) {
atoms.append(xdndMimeStringToAtom(QLatin1String("text/x-moz-url")));
}
//special cases for images
if (format == QLatin1String("image/ppm"))
atoms.append(XA_PIXMAP);
if (format == QLatin1String("image/pbm"))
atoms.append(XA_BITMAP);
return atoms;
}
//$$$
QVariant QX11Data::xdndMimeConvertToFormat(Atom a, const QByteArray &data, const QString &format, QVariant::Type requestedType, const QByteArray &encoding)
{
QString atomName = xdndMimeAtomToString(a);
if (atomName == format)
return data;
if (!encoding.isEmpty()
&& atomName == format + QLatin1String(";charset=") + QString::fromLatin1(encoding)) {
if (requestedType == QVariant::String) {
QTextCodec *codec = QTextCodec::codecForName(encoding);
if (codec)
return codec->toUnicode(data);
}
return data;
}
// special cases for string types
if (format == QLatin1String("text/plain")) {
if (a == ATOM(UTF8_STRING))
return QString::fromUtf8(data);
if (a == XA_STRING)
return QString::fromLatin1(data);
if (a == ATOM(TEXT) || a == ATOM(COMPOUND_TEXT))
// #### might be wrong for COMPUND_TEXT
return QString::fromLocal8Bit(data, data.size());
}
// special case for uri types
if (format == QLatin1String("text/uri-list")) {
if (atomName == QLatin1String("text/x-moz-url")) {
// we expect this as utf16 <url><space><title>
// the first part is a url that should only contain ascci char
// so it should be safe to check that the second char is 0
// to verify that it is utf16
if (data.size() > 1 && data.at(1) == 0)
return QString::fromRawData((const QChar *)data.constData(),
data.size() / 2).split(QLatin1Char('\n')).first().toLatin1();
}
}
// special cas for images
if (format == QLatin1String("image/ppm")) {
if (a == XA_PIXMAP && data.size() == sizeof(Pixmap)) {
Pixmap xpm = *((Pixmap*)data.data());
if (!xpm)
return QByteArray();
QPixmap qpm = QPixmap::fromX11Pixmap(xpm);
QImageWriter imageWriter;
imageWriter.setFormat("PPMRAW");
QImage imageToWrite = qpm.toImage();
QBuffer buf;
buf.open(QIODevice::WriteOnly);
imageWriter.setDevice(&buf);
imageWriter.write(imageToWrite);
return buf.buffer();
}
}
return QVariant();
}
//$$$ middle of xdndObtainData
Atom QX11Data::xdndMimeAtomForFormat(const QString &format, QVariant::Type requestedType, const QList<Atom> &atoms, QByteArray *encoding)
{
encoding->clear();
// find matches for string types
if (format == QLatin1String("text/plain")) {
if (atoms.contains(ATOM(UTF8_STRING)))
return ATOM(UTF8_STRING);
if (atoms.contains(ATOM(COMPOUND_TEXT)))
return ATOM(COMPOUND_TEXT);
if (atoms.contains(ATOM(TEXT)))
return ATOM(TEXT);
if (atoms.contains(XA_STRING))
return XA_STRING;
}
// find matches for uri types
if (format == QLatin1String("text/uri-list")) {
Atom a = xdndMimeStringToAtom(format);
if (a && atoms.contains(a))
return a;
a = xdndMimeStringToAtom(QLatin1String("text/x-moz-url"));
if (a && atoms.contains(a))
return a;
}
// find match for image
if (format == QLatin1String("image/ppm")) {
if (atoms.contains(XA_PIXMAP))
return XA_PIXMAP;
}
// for string/text requests try to use a format with a well-defined charset
// first to avoid encoding problems
if (requestedType == QVariant::String
&& format.startsWith(QLatin1String("text/"))
&& !format.contains(QLatin1String("charset="))) {
QString formatWithCharset = format;
formatWithCharset.append(QLatin1String(";charset=utf-8"));
Atom a = xdndMimeStringToAtom(formatWithCharset);
if (a && atoms.contains(a)) {
*encoding = "utf-8";
return a;
}
}
Atom a = xdndMimeStringToAtom(format);
if (a && atoms.contains(a))
return a;
return 0;
}
void QX11Data::xdndSetup() {
QCursorData::initialize();
qAddPostRoutine(qt_xdnd_cleanup);
}
void qt_xdnd_cleanup()
{
delete noDropCursor;
noDropCursor = 0;
delete copyCursor;
copyCursor = 0;
delete moveCursor;
moveCursor = 0;
delete linkCursor;
linkCursor = 0;
delete defaultPm;
defaultPm = 0;
delete xdnd_data.desktop_proxy;
xdnd_data.desktop_proxy = 0;
delete xdnd_data.deco;
xdnd_data.deco = 0;
}
static QWidget *find_child(QWidget *tlw, QPoint & p)
{
QWidget *widget = tlw;
p = widget->mapFromGlobal(p);
bool done = false;
while (!done) {
done = true;
if (((QExtraWidget*)widget)->extraData() &&
((QExtraWidget*)widget)->extraData()->xDndProxy != 0)
break; // stop searching for widgets under the mouse cursor if found widget is a proxy.
QObjectList children = widget->children();
if (!children.isEmpty()) {
for(int i = children.size(); i > 0;) {
--i;
QWidget *w = qobject_cast<QWidget *>(children.at(i));
if (!w)
continue;
if (w->testAttribute(Qt::WA_TransparentForMouseEvents))
continue;
if (w->isVisible() &&
w->geometry().contains(p) &&
!w->isWindow()) {
widget = w;
done = false;
p = widget->mapFromParent(p);
break;
}
}
}
}
return widget;
}
static bool checkEmbedded(QWidget* w, const XEvent* xe)
{
if (!w)
return false;
if (current_embedding_widget != 0 && current_embedding_widget != w) {
qt_xdnd_current_target = ((QExtraWidget*)current_embedding_widget)->extraData()->xDndProxy;
qt_xdnd_current_proxy_target = qt_xdnd_current_target;
qt_xdnd_send_leave();
qt_xdnd_current_target = 0;
qt_xdnd_current_proxy_target = 0;
current_embedding_widget = 0;
}
QWExtra* extra = ((QExtraWidget*)w)->extraData();
if (extra && extra->xDndProxy != 0) {
if (current_embedding_widget != w) {
last_enter_event.xany.window = extra->xDndProxy;
XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, &last_enter_event);
current_embedding_widget = w;
}
((XEvent*)xe)->xany.window = extra->xDndProxy;
XSendEvent(X11->display, extra->xDndProxy, False, NoEventMask, (XEvent*)xe);
if (qt_xdnd_current_widget != w) {
qt_xdnd_current_widget = w;
}
return true;
}
current_embedding_widget = 0;
return false;
}
void QX11Data::xdndHandleEnter(QWidget *, const XEvent * xe, bool /*passive*/)
{
motifdnd_active = false;
last_enter_event.xclient = xe->xclient;
const long *l = xe->xclient.data.l;
int version = (int)(((unsigned long)(l[1])) >> 24);
if (version > xdnd_version)
return;
qt_xdnd_dragsource_xid = l[0];
int j = 0;
if (l[1] & 1) {
// get the types from XdndTypeList
Atom type = XNone;
int f;
unsigned long n, a;
unsigned char *retval = 0;
XGetWindowProperty(X11->display, qt_xdnd_dragsource_xid, ATOM(XdndTypelist), 0,
qt_xdnd_max_type, False, XA_ATOM, &type, &f,&n,&a,&retval);
if (retval) {
Atom *data = (Atom *)retval;
for (; j<qt_xdnd_max_type && j < (int)n; j++) {
qt_xdnd_types[j] = data[j];
}
XFree((uchar*)data);
}
} else {
// get the types from the message
int i;
for(i=2; i < 5; i++) {
qt_xdnd_types[j++] = l[i];
}
}
qt_xdnd_types[j] = 0;
}
static void handle_xdnd_position(QWidget *w, const XEvent * xe, bool passive)
{
const unsigned long *l = (const unsigned long *)xe->xclient.data.l;
QPoint p((l[2] & 0xffff0000) >> 16, l[2] & 0x0000ffff);
QWidget * c = find_child(w, p); // changes p to to c-local coordinates
if (!passive && checkEmbedded(c, xe))
return;
if (!c || (!c->acceptDrops() && (c->windowType() == Qt::Desktop)))
return;
if (l[0] != qt_xdnd_dragsource_xid) {
DEBUG("xdnd drag position from unexpected source (%08lx not %08lx)", l[0], qt_xdnd_dragsource_xid);
return;
}
// timestamp from the source
if (l[3] != 0) {
// Some X server/client combination swallow the first 32 bit and
// interpret a set bit 31 as negative sign.
qt_xdnd_target_current_time = X11->userTime =
((sizeof(Time) == 8 && xe->xclient.data.l[3] < 0)
? uint(l[3])
: l[3]);
}
QDragManager *manager = QDragManager::self();
QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData;
XClientMessageEvent response;
response.type = ClientMessage;
response.window = qt_xdnd_dragsource_xid;
response.format = 32;
response.message_type = ATOM(XdndStatus);
response.data.l[0] = w->effectiveWinId();
response.data.l[1] = 0; // flags
response.data.l[2] = 0; // x, y
response.data.l[3] = 0; // w, h
response.data.l[4] = 0; // action
if (!passive) { // otherwise just reject
while (c && !c->acceptDrops() && !c->isWindow()) {
p = c->mapToParent(p);
c = c->parentWidget();
}
QWidget *target_widget = c && c->acceptDrops() ? c : 0;
QRect answerRect(c->mapToGlobal(p), QSize(1,1));
if (manager->object) {
possible_actions = manager->dragPrivate()->possible_actions;
} else {
possible_actions = Qt::DropActions(xdndaction_to_qtaction(l[4]));
// possible_actions |= Qt::CopyAction;
}
QDragMoveEvent me(p, possible_actions, dropData, QApplication::mouseButtons(), QApplication::keyboardModifiers());
Qt::DropAction accepted_action = Qt::IgnoreAction;
if (target_widget != qt_xdnd_current_widget) {
if (qt_xdnd_current_widget) {
QDragLeaveEvent e;
QApplication::sendEvent(qt_xdnd_current_widget, &e);
}
if (qt_xdnd_current_widget != target_widget) {
qt_xdnd_current_widget = target_widget;
}
if (target_widget) {
qt_xdnd_current_position = p;
last_target_accepted_action = Qt::IgnoreAction;
QDragEnterEvent de(p, possible_actions, dropData, QApplication::mouseButtons(), QApplication::keyboardModifiers());
QApplication::sendEvent(target_widget, &de);
if (de.isAccepted() && de.dropAction() != Qt::IgnoreAction)
last_target_accepted_action = de.dropAction();
}
}
DEBUG() << "qt_handle_xdnd_position action=" << X11->xdndAtomToString(l[4]);
if (!target_widget) {
answerRect = QRect(p, QSize(1, 1));
} else {
qt_xdnd_current_widget = c;
qt_xdnd_current_position = p;
if (last_target_accepted_action != Qt::IgnoreAction) {
me.setDropAction(last_target_accepted_action);
me.accept();
}
QApplication::sendEvent(c, &me);
if (me.isAccepted()) {
response.data.l[1] = 1; // yes
accepted_action = me.dropAction();
last_target_accepted_action = accepted_action;
} else {
response.data.l[0] = 0;
last_target_accepted_action = Qt::IgnoreAction;
}
answerRect = me.answerRect().intersected(c->rect());
}
answerRect = QRect(c->mapToGlobal(answerRect.topLeft()), answerRect.size());
if (answerRect.left() < 0)
answerRect.setLeft(0);
if (answerRect.right() > 4096)
answerRect.setRight(4096);
if (answerRect.top() < 0)
answerRect.setTop(0);
if (answerRect.bottom() > 4096)
answerRect.setBottom(4096);
if (answerRect.width() < 0)
answerRect.setWidth(0);
if (answerRect.height() < 0)
answerRect.setHeight(0);
response.data.l[2] = (answerRect.x() << 16) + answerRect.y();
response.data.l[3] = (answerRect.width() << 16) + answerRect.height();
response.data.l[4] = qtaction_to_xdndaction(accepted_action);
}
// reset
qt_xdnd_target_current_time = CurrentTime;
QWidget * source = QWidget::find(qt_xdnd_dragsource_xid);
if (source && (source->windowType() == Qt::Desktop) && !source->acceptDrops())
source = 0;
DEBUG() << "sending XdndStatus";
if (source)
handle_xdnd_status(source, (const XEvent *)&response, passive);
else
XSendEvent(X11->display, qt_xdnd_dragsource_xid, False, NoEventMask, (XEvent*)&response);
}
static Bool xdnd_position_scanner(Display *, XEvent *event, XPointer)
{
if (event->type != ClientMessage)
return false;
XClientMessageEvent *ev = &event->xclient;
if (ev->message_type == ATOM(XdndPosition))
return true;
return false;
}
void QX11Data::xdndHandlePosition(QWidget * w, const XEvent * xe, bool passive)
{
DEBUG("xdndHandlePosition");
while (XCheckIfEvent(X11->display, (XEvent *)xe, xdnd_position_scanner, 0))
;
handle_xdnd_position(w, xe, passive);
}
static void handle_xdnd_status(QWidget *, const XEvent * xe, bool)
{
const unsigned long *l = (const unsigned long *)xe->xclient.data.l;
// ignore late status messages
if (l[0] && l[0] != qt_xdnd_current_proxy_target)
return;
Qt::DropAction newAction = (l[1] & 0x1) ? xdndaction_to_qtaction(l[4]) : Qt::IgnoreAction;
if ((int)(l[1] & 2) == 0) {
QPoint p((l[2] & 0xffff0000) >> 16, l[2] & 0x0000ffff);
QSize s((l[3] & 0xffff0000) >> 16, l[3] & 0x0000ffff);
qt_xdnd_source_sameanswer = QRect(p, s);
} else {
qt_xdnd_source_sameanswer = QRect();
}
QDragManager *manager = QDragManager::self();
manager->willDrop = (l[1] & 0x1);
if (global_accepted_action != newAction)
manager->emitActionChanged(newAction);
global_accepted_action = newAction;
manager->updateCursor();
waiting_for_status = false;
}
static Bool xdnd_status_scanner(Display *, XEvent *event, XPointer)
{
if (event->type != ClientMessage)
return false;
XClientMessageEvent *ev = &event->xclient;
if (ev->message_type == ATOM(XdndStatus))
return true;
return false;
}
void QX11Data::xdndHandleStatus(QWidget * w, const XEvent * xe, bool passive)
{
DEBUG("xdndHandleStatus");
while (XCheckIfEvent(X11->display, (XEvent *)xe, xdnd_status_scanner, 0))
;
handle_xdnd_status(w, xe, passive);
DEBUG("xdndHandleStatus end");
}
void QX11Data::xdndHandleLeave(QWidget *w, const XEvent * xe, bool /*passive*/)
{
DEBUG("xdnd leave");
if (!qt_xdnd_current_widget ||
w->window() != qt_xdnd_current_widget->window()) {
return; // sanity
}
if (checkEmbedded(current_embedding_widget, xe)) {
current_embedding_widget = 0;
qt_xdnd_current_widget = 0;
return;
}
const unsigned long *l = (const unsigned long *)xe->xclient.data.l;
QDragLeaveEvent e;
QApplication::sendEvent(qt_xdnd_current_widget, &e);
if (l[0] != qt_xdnd_dragsource_xid) {
// This often happens - leave other-process window quickly
DEBUG("xdnd drag leave from unexpected source (%08lx not %08lx", l[0], qt_xdnd_dragsource_xid);
qt_xdnd_current_widget = 0;
return;
}
qt_xdnd_dragsource_xid = 0;
qt_xdnd_types[0] = 0;
qt_xdnd_current_widget = 0;
}
void qt_xdnd_send_leave()
{
if (!qt_xdnd_current_target)
return;
QDragManager *manager = QDragManager::self();
XClientMessageEvent leave;
leave.type = ClientMessage;
leave.window = qt_xdnd_current_target;
leave.format = 32;
leave.message_type = ATOM(XdndLeave);
leave.data.l[0] = manager->dragPrivate()->source->effectiveWinId();
leave.data.l[1] = 0; // flags
leave.data.l[2] = 0; // x, y
leave.data.l[3] = 0; // w, h
leave.data.l[4] = 0; // just null
QWidget * w = QWidget::find(qt_xdnd_current_proxy_target);
if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops())
w = 0;
if (w)
X11->xdndHandleLeave(w, (const XEvent *)&leave, false);
else
XSendEvent(X11->display, qt_xdnd_current_proxy_target, False,
NoEventMask, (XEvent*)&leave);
// reset the drag manager state
manager->willDrop = false;
if (global_accepted_action != Qt::IgnoreAction)
manager->emitActionChanged(Qt::IgnoreAction);
global_accepted_action = Qt::IgnoreAction;
manager->updateCursor();
qt_xdnd_current_target = 0;
qt_xdnd_current_proxy_target = 0;
qt_xdnd_source_current_time = 0;
waiting_for_status = false;
}
void QX11Data::xdndHandleDrop(QWidget *, const XEvent * xe, bool passive)
{
DEBUG("xdndHandleDrop");
if (!qt_xdnd_current_widget) {
qt_xdnd_dragsource_xid = 0;
return; // sanity
}
if (!passive && checkEmbedded(qt_xdnd_current_widget, xe)){
current_embedding_widget = 0;
qt_xdnd_dragsource_xid = 0;
qt_xdnd_current_widget = 0;
return;
}
const unsigned long *l = (const unsigned long *)xe->xclient.data.l;
QDragManager *manager = QDragManager::self();
DEBUG("xdnd drop");
if (l[0] != qt_xdnd_dragsource_xid) {
DEBUG("xdnd drop from unexpected source (%08lx not %08lx", l[0], qt_xdnd_dragsource_xid);
return;
}
// update the "user time" from the timestamp in the event.
if (l[2] != 0) {
// Some X server/client combination swallow the first 32 bit and
// interpret a set bit 31 as negative sign.
qt_xdnd_target_current_time = X11->userTime =
((sizeof(Time) == 8 && xe->xclient.data.l[2] < 0)
? uint(l[2])
: l[2]);
}
if (!passive) {
// this could be a same-application drop, just proxied due to
// some XEMBEDding, so try to find the real QMimeData used
// based on the timestamp for this drop.
QMimeData *dropData = 0;
const int at = findXdndDropTransactionByTime(qt_xdnd_target_current_time);
if (at != -1) {
dropData = QDragManager::dragPrivate(X11->dndDropTransactions.at(at).object)->data;
// Can't use the source QMimeData if we need the image conversion code from xdndObtainData
if (dropData && dropData->hasImage())
dropData = 0;
}
// if we can't find it, then use the data in the drag manager
if (!dropData) {
if (manager->object && !manager->dragPrivate()->data->hasImage())
dropData = manager->dragPrivate()->data;
else
dropData = manager->dropData;
}
// Drop coming from another app? Update keyboard modifiers.
if (!qt_xdnd_dragging) {
QApplicationPrivate::modifier_buttons = QApplication::queryKeyboardModifiers();
}
QDropEvent de(qt_xdnd_current_position, possible_actions, dropData,
QApplication::mouseButtons(), QApplication::keyboardModifiers());
QApplication::sendEvent(qt_xdnd_current_widget, &de);
if (!de.isAccepted()) {
// Ignore a failed drag
global_accepted_action = Qt::IgnoreAction;
} else {
global_accepted_action = de.dropAction();
}
XClientMessageEvent finished;
finished.type = ClientMessage;
finished.window = qt_xdnd_dragsource_xid;
finished.format = 32;
finished.message_type = ATOM(XdndFinished);
DNDDEBUG << "xdndHandleDrop"
<< "qt_xdnd_current_widget" << qt_xdnd_current_widget
<< (qt_xdnd_current_widget ? qt_xdnd_current_widget->effectiveWinId() : 0)
<< "t_xdnd_current_widget->window()"
<< (qt_xdnd_current_widget ? qt_xdnd_current_widget->window() : 0)
<< (qt_xdnd_current_widget ? qt_xdnd_current_widget->window()->internalWinId() : 0);
finished.data.l[0] = qt_xdnd_current_widget?qt_xdnd_current_widget->window()->internalWinId():0;
finished.data.l[1] = de.isAccepted() ? 1 : 0; // flags
finished.data.l[2] = qtaction_to_xdndaction(global_accepted_action);
XSendEvent(X11->display, qt_xdnd_dragsource_xid, False,
NoEventMask, (XEvent*)&finished);
} else {
QDragLeaveEvent e;
QApplication::sendEvent(qt_xdnd_current_widget, &e);
}
qt_xdnd_dragsource_xid = 0;
qt_xdnd_current_widget = 0;
waiting_for_status = false;
// reset
qt_xdnd_target_current_time = CurrentTime;
}
void QX11Data::xdndHandleFinished(QWidget *, const XEvent * xe, bool passive)
{
DEBUG("xdndHandleFinished");
const unsigned long *l = (const unsigned long *)xe->xclient.data.l;
DNDDEBUG << "xdndHandleFinished, l[0]" << l[0]
<< "qt_xdnd_current_target" << qt_xdnd_current_target
<< "qt_xdnd_current_proxy_targe" << qt_xdnd_current_proxy_target;
if (l[0]) {
int at = findXdndDropTransactionByWindow(l[0]);
if (at != -1) {
restartXdndDropExpiryTimer();
QXdndDropTransaction t = X11->dndDropTransactions.takeAt(at);
QDragManager *manager = QDragManager::self();
Window target = qt_xdnd_current_target;
Window proxy_target = qt_xdnd_current_proxy_target;
QWidget *embedding_widget = current_embedding_widget;
QDrag *currentObject = manager->object;
qt_xdnd_current_target = t.target;
qt_xdnd_current_proxy_target = t.proxy_target;
current_embedding_widget = t.embedding_widget;
manager->object = t.object;
if (!passive)
(void) checkEmbedded(qt_xdnd_current_widget, xe);
current_embedding_widget = 0;
qt_xdnd_current_target = 0;
qt_xdnd_current_proxy_target = 0;
if (t.object)
t.object->deleteLater();
qt_xdnd_current_target = target;
qt_xdnd_current_proxy_target = proxy_target;
current_embedding_widget = embedding_widget;
manager->object = currentObject;
}
}
waiting_for_status = false;
}
void QDragManager::timerEvent(QTimerEvent* e)
{
if (e->timerId() == heartbeat && qt_xdnd_source_sameanswer.isNull()) {
move(QCursor::pos());
} else if (e->timerId() == transaction_expiry_timer) {
for (int i = 0; i < X11->dndDropTransactions.count(); ++i) {
const QXdndDropTransaction &t = X11->dndDropTransactions.at(i);
if (t.targetWidget) {
// dnd within the same process, don't delete these
continue;
}
t.object->deleteLater();
X11->dndDropTransactions.removeAt(i--);
}
killTimer(transaction_expiry_timer);
transaction_expiry_timer = -1;
}
}
bool QDragManager::eventFilter(QObject * o, QEvent * e)
{
if (beingCancelled) {
if (e->type() == QEvent::KeyRelease && ((QKeyEvent*)e)->key() == Qt::Key_Escape) {
qApp->removeEventFilter(this);
Q_ASSERT(object == 0);
beingCancelled = false;
eventLoop->exit();
return true; // block the key release
}
return false;
}
Q_ASSERT(object != 0);
if (!o->isWidgetType())
return false;
if (e->type() == QEvent::MouseMove) {
QMouseEvent* me = (QMouseEvent *)e;
move(me->globalPos());
return true;
} else if (e->type() == QEvent::MouseButtonRelease) {
DEBUG("pre drop");
qApp->removeEventFilter(this);
if (willDrop)
drop();
else
cancel();
DEBUG("drop, resetting object");
beingCancelled = false;
eventLoop->exit();
return true;
}
if (e->type() == QEvent::ShortcutOverride) {
// prevent accelerators from firing while dragging
e->accept();
return true;
}
if (e->type() == QEvent::KeyPress || e->type() == QEvent::KeyRelease) {
QKeyEvent *ke = ((QKeyEvent*)e);
if (ke->key() == Qt::Key_Escape && e->type() == QEvent::KeyPress) {
cancel();
qApp->removeEventFilter(this);
beingCancelled = false;
eventLoop->exit();
} else {
qt_xdnd_source_sameanswer = QRect(); // force move
move(QCursor::pos());
}
return true; // Eat all key events
}
// ### We bind modality to widgets, so we have to do this
// ### "manually".
// DnD is modal - eat all other interactive events
switch (e->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
case QEvent::MouseButtonDblClick:
case QEvent::MouseMove:
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::Wheel:
case QEvent::ShortcutOverride:
return true;
default:
return false;
}
}
void QDragManager::updateCursor()
{
if (!noDropCursor) {
#ifndef QT_NO_CURSOR
noDropCursor = new QCursor(Qt::ForbiddenCursor);
moveCursor = new QCursor(Qt::DragMoveCursor);
copyCursor = new QCursor(Qt::DragCopyCursor);
linkCursor = new QCursor(Qt::DragLinkCursor);
#endif
}
QCursor *c;
if (willDrop) {
if (global_accepted_action == Qt::CopyAction) {
c = copyCursor;
} else if (global_accepted_action == Qt::LinkAction) {
c = linkCursor;
} else {
c = moveCursor;
}
if (xdnd_data.deco) {
xdnd_data.deco->show();
xdnd_data.deco->raise();
}
} else {
c = noDropCursor;
//if (qt_xdnd_deco)
// qt_xdnd_deco->hide();
}
#ifndef QT_NO_CURSOR
if (c)
qApp->changeOverrideCursor(*c);
#endif
}
void QDragManager::cancel(bool deleteSource)
{
DEBUG("QDragManager::cancel");
Q_ASSERT(heartbeat != -1);
killTimer(heartbeat);
heartbeat = -1;
beingCancelled = true;
qt_xdnd_dragging = false;
if (qt_xdnd_current_target)
qt_xdnd_send_leave();
#ifndef QT_NO_CURSOR
if (restoreCursor) {
QApplication::restoreOverrideCursor();
restoreCursor = false;
}
#endif
if (deleteSource && object)
object->deleteLater();
object = 0;
delete xdnd_data.deco;
xdnd_data.deco = 0;
global_accepted_action = Qt::IgnoreAction;
}
#ifndef QT_NO_XSHAPE
static
bool windowInteractsWithPosition(const QPoint & pos, Window w, int shapeType)
{
int nrectanglesRet, dummyOrdering;
XRectangle *rectangles = XShapeGetRectangles(QX11Info::display(), w, shapeType, &nrectanglesRet, &dummyOrdering);
bool interacts = false;
if (rectangles) {
for (int i = 0; !interacts && i < nrectanglesRet; ++i)
interacts = QRect(rectangles[i].x, rectangles[i].y, rectangles[i].width, rectangles[i].height).contains(pos);
XFree(rectangles);
}
return interacts;
}
#endif
static
Window findRealWindow(const QPoint & pos, Window w, int md, bool ignoreNonXdndAwareWindows)
{
if (xdnd_data.deco && w == xdnd_data.deco->effectiveWinId())
return 0;
if (md) {
X11->ignoreBadwindow();
XWindowAttributes attr;
XGetWindowAttributes(X11->display, w, &attr);
if (X11->badwindow())
return 0;
if (attr.map_state == IsViewable
&& QRect(attr.x,attr.y,attr.width,attr.height).contains(pos)) {
bool windowContainsMouse = !ignoreNonXdndAwareWindows;
{
Atom type = XNone;
int f;
unsigned long n, a;
unsigned char *data;
XGetWindowProperty(X11->display, w, ATOM(XdndAware), 0, 0, False,
AnyPropertyType, &type, &f,&n,&a,&data);
if (data) XFree(data);
if (type) {
#ifdef QT_NO_XSHAPE
return w;
#else // !QT_NO_XSHAPE
const QPoint relPos = pos - QPoint(attr.x,attr.y);
// When ShapeInput and ShapeBounding are not set they return a single rectangle with the geometry of the window, this is why we
// need an && here so that in the case one is set and the other is not we still get the correct result.
#if defined(ShapeInput) && defined(ShapeBounding)
windowContainsMouse = windowInteractsWithPosition(relPos, w, ShapeInput) && windowInteractsWithPosition(relPos, w, ShapeBounding);
#elif defined(ShapeBounding)
windowContainsMouse = windowInteractsWithPosition(relPos, w, ShapeBounding);
#else
windowContainsMouse = true;
#endif
if (windowContainsMouse)
return w;
#endif // QT_NO_XSHAPE
}
}
Window r, p;
Window* c;
uint nc;
if (XQueryTree(X11->display, w, &r, &p, &c, &nc)) {
r=0;
for (uint i=nc; !r && i--;) {
r = findRealWindow(pos-QPoint(attr.x,attr.y),
c[i], md-1, ignoreNonXdndAwareWindows);
}
XFree(c);
if (r)
return r;
// We didn't find a client window! Just use the
// innermost window.
}
// No children!
if (!windowContainsMouse)
return 0;
else
return w;
}
}
return 0;
}
void QDragManager::move(const QPoint & globalPos)
{
#ifdef QT_NO_CURSOR
Q_UNUSED(globalPos);
return;
#else
DEBUG() << "QDragManager::move enter";
if (!object) {
// perhaps the target crashed?
return;
}
int screen = QCursor::x11Screen();
if ((qt_xdnd_current_screen == -1 && screen != X11->defaultScreen) || (screen != qt_xdnd_current_screen)) {
// recreate the pixmap on the new screen...
delete xdnd_data.deco;
QWidget* parent = object->source()->window()->x11Info().screen() == screen
? object->source()->window() : QApplication::desktop()->screen(screen);
xdnd_data.deco = new QShapedPixmapWidget(parent);
if (!QWidget::mouseGrabber()) {
updatePixmap();
xdnd_data.deco->grabMouse();
}
}
xdnd_data.deco->move(QCursor::pos() - xdnd_data.deco->pm_hot);
if (qt_xdnd_source_sameanswer.contains(globalPos) && qt_xdnd_source_sameanswer.isValid())
return;
qt_xdnd_current_screen = screen;
Window rootwin = QX11Info::appRootWindow(qt_xdnd_current_screen);
Window target = 0;
int lx = 0, ly = 0;
if (!XTranslateCoordinates(X11->display, rootwin, rootwin, globalPos.x(), globalPos.y(), &lx, &ly, &target))
// some weird error...
return;
if (target == rootwin) {
// Ok.
} else if (target) {
//me
Window src = rootwin;
while (target != 0) {
DNDDEBUG << "checking target for XdndAware" << QWidget::find(target) << target;
int lx2, ly2;
Window t;
// translate coordinates
if (!XTranslateCoordinates(X11->display, src, target, lx, ly, &lx2, &ly2, &t)) {
target = 0;
break;
}
lx = lx2;
ly = ly2;
src = target;
// check if it has XdndAware
Atom type = 0;
int f;
unsigned long n, a;
unsigned char *data = 0;
XGetWindowProperty(X11->display, target, ATOM(XdndAware), 0, 0, False,
AnyPropertyType, &type, &f,&n,&a,&data);
if (data)
XFree(data);
if (type) {
DNDDEBUG << "Found XdndAware on " << QWidget::find(target) << target;
break;
}
// find child at the coordinates
if (!XTranslateCoordinates(X11->display, src, src, lx, ly, &lx2, &ly2, &target)) {
target = 0;
break;
}
}
if (xdnd_data.deco && (!target || target == xdnd_data.deco->effectiveWinId())) {
DNDDEBUG << "need to find real window";
target = findRealWindow(globalPos, rootwin, 6, true);
if (target == 0)
target = findRealWindow(globalPos, rootwin, 6, false);
DNDDEBUG << "real window found" << QWidget::find(target) << target;
}
}
QWidget* w;
if (target) {
w = QWidget::find((WId)target);
if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops())
w = 0;
} else {
w = 0;
target = rootwin;
}
DNDDEBUG << "and the final target is " << QWidget::find(target) << target;
DNDDEBUG << "the widget w is" << w;
WId proxy_target = xdndProxy(target);
if (!proxy_target)
proxy_target = target;
int target_version = 1;
if (proxy_target) {
Atom type = XNone;
int r, f;
unsigned long n, a;
unsigned char *retval;
X11->ignoreBadwindow();
r = XGetWindowProperty(X11->display, proxy_target, ATOM(XdndAware), 0,
1, False, AnyPropertyType, &type, &f,&n,&a,&retval);
int *tv = (int *)retval;
if (r != Success || X11->badwindow()) {
target = 0;
} else {
target_version = qMin(xdnd_version,tv ? *tv : 1);
if (tv)
XFree(tv);
// if (!(!X11->badwindow() && type))
// target = 0;
}
}
if (target != qt_xdnd_current_target) {
if (qt_xdnd_current_target)
qt_xdnd_send_leave();
qt_xdnd_current_target = target;
qt_xdnd_current_proxy_target = proxy_target;
if (target) {
QVector<Atom> types;
int flags = target_version << 24;
QStringList fmts = QInternalMimeData::formatsHelper(dragPrivate()->data);
for (int i = 0; i < fmts.size(); ++i) {
QList<Atom> atoms = X11->xdndMimeAtomsForFormat(fmts.at(i));
for (int j = 0; j < atoms.size(); ++j) {
if (!types.contains(atoms.at(j)))
types.append(atoms.at(j));
}
}
if (types.size() > 3) {
XChangeProperty(X11->display,
dragPrivate()->source->effectiveWinId(), ATOM(XdndTypelist),
XA_ATOM, 32, PropModeReplace,
(unsigned char *)types.data(),
types.size());
flags |= 0x0001;
}
XClientMessageEvent enter;
enter.type = ClientMessage;
enter.window = target;
enter.format = 32;
enter.message_type = ATOM(XdndEnter);
enter.data.l[0] = dragPrivate()->source->effectiveWinId();
enter.data.l[1] = flags;
enter.data.l[2] = types.size()>0 ? types.at(0) : 0;
enter.data.l[3] = types.size()>1 ? types.at(1) : 0;
enter.data.l[4] = types.size()>2 ? types.at(2) : 0;
// provisionally set the rectangle to 5x5 pixels...
qt_xdnd_source_sameanswer = QRect(globalPos.x() - 2,
globalPos.y() -2 , 5, 5);
DEBUG("sending Xdnd enter");
if (w)
X11->xdndHandleEnter(w, (const XEvent *)&enter, false);
else if (target)
XSendEvent(X11->display, proxy_target, False, NoEventMask, (XEvent*)&enter);
waiting_for_status = false;
}
}
if (waiting_for_status)
return;
if (target) {
waiting_for_status = true;
XClientMessageEvent move;
move.type = ClientMessage;
move.window = target;
move.format = 32;
move.message_type = ATOM(XdndPosition);
move.window = target;
move.data.l[0] = dragPrivate()->source->effectiveWinId();
move.data.l[1] = 0; // flags
move.data.l[2] = (globalPos.x() << 16) + globalPos.y();
move.data.l[3] = X11->time;
move.data.l[4] = qtaction_to_xdndaction(defaultAction(dragPrivate()->possible_actions, QApplication::keyboardModifiers()));
DEBUG("sending Xdnd position");
qt_xdnd_source_current_time = X11->time;
if (w)
handle_xdnd_position(w, (const XEvent *)&move, false);
else
XSendEvent(X11->display, proxy_target, False, NoEventMask,
(XEvent*)&move);
} else {
if (willDrop) {
willDrop = false;
updateCursor();
}
}
DEBUG() << "QDragManager::move leave";
#endif
}
void QDragManager::drop()
{
Q_ASSERT(heartbeat != -1);
killTimer(heartbeat);
heartbeat = -1;
qt_xdnd_dragging = false;
if (!qt_xdnd_current_target)
return;
delete xdnd_data.deco;
xdnd_data.deco = 0;
XClientMessageEvent drop;
drop.type = ClientMessage;
drop.window = qt_xdnd_current_target;
drop.format = 32;
drop.message_type = ATOM(XdndDrop);
drop.data.l[0] = dragPrivate()->source->effectiveWinId();
drop.data.l[1] = 0; // flags
drop.data.l[2] = X11->time;
drop.data.l[3] = 0;
drop.data.l[4] = 0;
QWidget * w = QWidget::find(qt_xdnd_current_proxy_target);
if (w && (w->windowType() == Qt::Desktop) && !w->acceptDrops())
w = 0;
QXdndDropTransaction t = {
X11->time,
qt_xdnd_current_target,
qt_xdnd_current_proxy_target,
w,
current_embedding_widget,
object
};
X11->dndDropTransactions.append(t);
restartXdndDropExpiryTimer();
if (w)
X11->xdndHandleDrop(w, (const XEvent *)&drop, false);
else
XSendEvent(X11->display, qt_xdnd_current_proxy_target, False,
NoEventMask, (XEvent*)&drop);
qt_xdnd_current_target = 0;
qt_xdnd_current_proxy_target = 0;
qt_xdnd_source_current_time = 0;
current_embedding_widget = 0;
object = 0;
#ifndef QT_NO_CURSOR
if (restoreCursor) {
QApplication::restoreOverrideCursor();
restoreCursor = false;
}
#endif
}
bool QX11Data::xdndHandleBadwindow()
{
if (qt_xdnd_current_target) {
QDragManager *manager = QDragManager::self();
if (manager->object) {
qt_xdnd_current_target = 0;
qt_xdnd_current_proxy_target = 0;
manager->object->deleteLater();
manager->object = 0;
xdnd_data.deco->deleteLater(); //delay freeing to avoid crash QTBUG-19363
xdnd_data.deco = 0;
return true;
}
}
if (qt_xdnd_dragsource_xid) {
qt_xdnd_dragsource_xid = 0;
if (qt_xdnd_current_widget) {
QApplication::postEvent(qt_xdnd_current_widget, new QDragLeaveEvent);
qt_xdnd_current_widget = 0;
}
return true;
}
return false;
}
void QX11Data::xdndHandleSelectionRequest(const XSelectionRequestEvent * req)
{
if (!req)
return;
XEvent evt;
evt.xselection.type = SelectionNotify;
evt.xselection.display = req->display;
evt.xselection.requestor = req->requestor;
evt.xselection.selection = req->selection;
evt.xselection.target = XNone;
evt.xselection.property = XNone;
evt.xselection.time = req->time;
QDragManager *manager = QDragManager::self();
QDrag *currentObject = manager->object;
// which transaction do we use? (note: -2 means use current manager->object)
int at = -1;
// figure out which data the requestor is really interested in
if (manager->object && req->time == qt_xdnd_source_current_time) {
// requestor wants the current drag data
at = -2;
} else {
// if someone has requested data in response to XdndDrop, find the corresponding transaction. the
// spec says to call XConvertSelection() using the timestamp from the XdndDrop
at = findXdndDropTransactionByTime(req->time);
if (at == -1) {
// no dice, perhaps the client was nice enough to use the same window id in XConvertSelection()
// that we sent the XdndDrop event to.
at = findXdndDropTransactionByWindow(req->requestor);
}
if (at == -1 && req->time == CurrentTime) {
// previous Qt versions always requested the data on a child of the target window
// using CurrentTime... but it could be asking for either drop data or the current drag's data
Window target = findXdndAwareParent(req->requestor);
if (target) {
if (qt_xdnd_current_target && qt_xdnd_current_target == target)
at = -2;
else
at = findXdndDropTransactionByWindow(target);
}
}
}
if (at >= 0) {
restartXdndDropExpiryTimer();
// use the drag object from an XdndDrop tansaction
manager->object = X11->dndDropTransactions.at(at).object;
} else if (at != -2) {
// no transaction found, we'll have to reject the request
manager->object = 0;
}
if (manager->object) {
Atom atomFormat = req->target;
int dataFormat = 0;
QByteArray data;
if (X11->xdndMimeDataForAtom(req->target, manager->dragPrivate()->data,
&data, &atomFormat, &dataFormat)) {
int dataSize = data.size() / (dataFormat / 8);
XChangeProperty (X11->display, req->requestor, req->property,
atomFormat, dataFormat, PropModeReplace,
(unsigned char *)data.data(), dataSize);
evt.xselection.property = req->property;
evt.xselection.target = atomFormat;
}
}
// reset manager->object in case we modified it above
manager->object = currentObject;
// ### this can die if req->requestor crashes at the wrong
// ### moment
XSendEvent(X11->display, req->requestor, False, 0, &evt);
}
static QVariant xdndObtainData(const char *format, QVariant::Type requestedType)
{
QByteArray result;
QWidget* w;
QDragManager *manager = QDragManager::self();
if (qt_xdnd_dragsource_xid && manager->object &&
(w=QWidget::find(qt_xdnd_dragsource_xid))
&& (!(w->windowType() == Qt::Desktop) || w->acceptDrops()))
{
QDragPrivate * o = QDragManager::self()->dragPrivate();
const QString mimeType = QString::fromLatin1(format);
if (o->data->hasFormat(mimeType)) {
result = o->data->data(mimeType);
} else if (mimeType.startsWith(QLatin1String("image/")) && o->data->hasImage()) {
// ### duplicated from QInternalMimeData::renderDataHelper
QImage image = qvariant_cast<QImage>(o->data->imageData());
QBuffer buf(&result);
buf.open(QBuffer::WriteOnly);
image.save(&buf, mimeType.mid(mimeType.indexOf(QLatin1Char('/')) + 1).toLatin1().toUpper());
}
return result;
}
QList<Atom> atoms;
int i = 0;
while ((qt_xdnd_types[i])) {
atoms.append(qt_xdnd_types[i]);
++i;
}
QByteArray encoding;
Atom a = X11->xdndMimeAtomForFormat(QLatin1String(format), requestedType, atoms, &encoding);
if (!a)
return result;
if (XGetSelectionOwner(X11->display, ATOM(XdndSelection)) == XNone)
return result; // should never happen?
QWidget* tw = qt_xdnd_current_widget;
if (!qt_xdnd_current_widget || (qt_xdnd_current_widget->windowType() == Qt::Desktop))
tw = new QWidget;
XConvertSelection(X11->display, ATOM(XdndSelection), a, ATOM(XdndSelection), tw->effectiveWinId(),
qt_xdnd_target_current_time);
XFlush(X11->display);
XEvent xevent;
bool got=X11->clipboardWaitForEvent(tw->effectiveWinId(), SelectionNotify, &xevent, 5000);
if (got) {
Atom type;
if (X11->clipboardReadProperty(tw->effectiveWinId(), ATOM(XdndSelection), true, &result, 0, &type, 0)) {
if (type == ATOM(INCR)) {
int nbytes = result.size() >= 4 ? *((int*)result.data()) : 0;
result = X11->clipboardReadIncrementalProperty(tw->effectiveWinId(), ATOM(XdndSelection), nbytes, false);
} else if (type != a && type != XNone) {
DEBUG("Qt clipboard: unknown atom %ld", type);
}
}
}
if (!qt_xdnd_current_widget || (qt_xdnd_current_widget->windowType() == Qt::Desktop))
delete tw;
return X11->xdndMimeConvertToFormat(a, result, QLatin1String(format), requestedType, encoding);
}
/*
Enable drag and drop for widget w by installing the proper
properties on w's toplevel widget.
*/
bool QX11Data::dndEnable(QWidget* w, bool on)
{
w = w->window();
if (bool(((QExtraWidget*)w)->topData()->dnd) == on)
return true; // been there, done that
((QExtraWidget*)w)->topData()->dnd = on ? 1 : 0;
motifdndEnable(w, on);
return xdndEnable(w, on);
}
Qt::DropAction QDragManager::drag(QDrag * o)
{
if (object == o || !o || !o->d_func()->source)
return Qt::IgnoreAction;
if (object) {
cancel();
qApp->removeEventFilter(this);
beingCancelled = false;
}
if (object) {
// the last drag and drop operation hasn't finished, so we are going to wait
// for one second to see if it does... if the finish message comes after this,
// then we could still have problems, but this is highly unlikely
QApplication::flush();
QElapsedTimer timer;
timer.start();
do {
XEvent event;
// Pass the event through the event dispatcher filter so that applications
// which install an event filter on the dispatcher get to handle it first.
if (XCheckTypedEvent(X11->display, ClientMessage, &event) &&
!QAbstractEventDispatcher::instance()->filterEvent(&event))
qApp->x11ProcessEvent(&event);
// sleep 50 ms, so we don't use up CPU cycles all the time.
struct timeval usleep_tv;
usleep_tv.tv_sec = 0;
usleep_tv.tv_usec = 50000;
select(0, 0, 0, 0, &usleep_tv);
} while (object && timer.hasExpired(1000));
}
object = o;
object->d_func()->target = 0;
xdnd_data.deco = new QShapedPixmapWidget(object->source()->window());
willDrop = false;
updatePixmap();
qApp->installEventFilter(this);
XSetSelectionOwner(X11->display, ATOM(XdndSelection), dragPrivate()->source->window()->internalWinId(), X11->time);
global_accepted_action = Qt::CopyAction;
qt_xdnd_source_sameanswer = QRect();
#ifndef QT_NO_CURSOR
// set the override cursor (must be done here, since it is updated
// in the call to move() below)
qApp->setOverrideCursor(Qt::ArrowCursor);
restoreCursor = true;
#endif
move(QCursor::pos());
heartbeat = startTimer(200);
qt_xdnd_dragging = true;
if (!QWidget::mouseGrabber())
xdnd_data.deco->grabMouse();
eventLoop = new QEventLoop;
(void) eventLoop->exec();
delete eventLoop;
eventLoop = 0;
#ifndef QT_NO_CURSOR
if (restoreCursor) {
qApp->restoreOverrideCursor();
restoreCursor = false;
}
#endif
// delete cursors as they may be different next drag.
delete noDropCursor;
noDropCursor = 0;
delete copyCursor;
copyCursor = 0;
delete moveCursor;
moveCursor = 0;
delete linkCursor;
linkCursor = 0;
delete xdnd_data.deco;
xdnd_data.deco = 0;
if (heartbeat != -1)
killTimer(heartbeat);
heartbeat = -1;
qt_xdnd_current_screen = -1;
qt_xdnd_dragging = false;
return global_accepted_action;
// object persists until we get an xdnd_finish message
}
void QDragManager::updatePixmap()
{
if (xdnd_data.deco) {
QPixmap pm;
QPoint pm_hot(default_pm_hotx,default_pm_hoty);
if (object) {
pm = dragPrivate()->pixmap;
if (!pm.isNull())
pm_hot = dragPrivate()->hotspot;
}
if (pm.isNull()) {
if (!defaultPm)
defaultPm = new QPixmap(default_pm);
pm = *defaultPm;
}
xdnd_data.deco->pm_hot = pm_hot;
xdnd_data.deco->setPixmap(pm);
xdnd_data.deco->move(QCursor::pos()-pm_hot);
xdnd_data.deco->show();
}
}
QVariant QDropData::retrieveData_sys(const QString &mimetype, QVariant::Type requestedType) const
{
QByteArray mime = mimetype.toLatin1();
QVariant data = X11->motifdnd_active
? X11->motifdndObtainData(mime)
: xdndObtainData(mime, requestedType);
return data;
}
bool QDropData::hasFormat_sys(const QString &format) const
{
return formats().contains(format);
}
QStringList QDropData::formats_sys() const
{
QStringList formats;
if (X11->motifdnd_active) {
int i = 0;
QByteArray fmt;
while (!(fmt = X11->motifdndFormat(i)).isEmpty()) {
formats.append(QLatin1String(fmt));
++i;
}
} else {
int i = 0;
while ((qt_xdnd_types[i])) {
QStringList formatsForAtom = X11->xdndMimeFormatsForAtom(qt_xdnd_types[i]);
for (int j = 0; j < formatsForAtom.size(); ++j) {
if (!formats.contains(formatsForAtom.at(j)))
formats.append(formatsForAtom.at(j));
}
++i;
}
}
return formats;
}
QT_END_NAMESPACE
#endif // QT_NO_DRAGANDDROP