diff --git a/atoms.h b/atoms.h --- a/atoms.h +++ b/atoms.h @@ -79,6 +79,8 @@ Xcb::Atom utf8_string; Xcb::Atom text; Xcb::Atom uri_list; + Xcb::Atom netscape_url; + Xcb::Atom moz_url; Xcb::Atom wl_surface_id; Xcb::Atom kde_net_wm_appmenu_service_name; Xcb::Atom kde_net_wm_appmenu_object_path; diff --git a/atoms.cpp b/atoms.cpp --- a/atoms.cpp +++ b/atoms.cpp @@ -70,6 +70,8 @@ , utf8_string(QByteArrayLiteral("UTF8_STRING")) , text(QByteArrayLiteral("TEXT")) , uri_list(QByteArrayLiteral("text/uri-list")) + , netscape_url(QByteArrayLiteral("_NETSCAPE_URL")) + , moz_url(QByteArrayLiteral("text/x-moz-url")) , wl_surface_id(QByteArrayLiteral("WL_SURFACE_ID")) , kde_net_wm_appmenu_service_name(QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME")) , kde_net_wm_appmenu_object_path(QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH")) diff --git a/xwl/drag_x.cpp b/xwl/drag_x.cpp --- a/xwl/drag_x.cpp +++ b/xwl/drag_x.cpp @@ -42,6 +42,24 @@ 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_src(source) { @@ -365,7 +383,7 @@ // 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 = Selection::atomToMimeTypes(mimeAtom); + const auto mimeStrings = atomToMimeTypes(mimeAtom); for (const auto mime : mimeStrings ) { if (!hasMimeName(offers, mime)) { offers << Mime(mime, mimeAtom); @@ -403,7 +421,7 @@ xcb_atom_t *mimeAtoms = static_cast(xcb_get_property_value(reply)); for (size_t i = 0; i < reply->value_len; ++i) { - const auto mimeStrings = Selection::atomToMimeTypes(mimeAtoms[i]); + const auto mimeStrings = atomToMimeTypes(mimeAtoms[i]); for (const auto mime : mimeStrings ) { if (!hasMimeName(offers, mime)) { offers << Mime(mime, mimeAtoms[i]); diff --git a/xwl/selection.h b/xwl/selection.h --- a/xwl/selection.h +++ b/xwl/selection.h @@ -60,6 +60,7 @@ static xcb_atom_t mimeTypeToAtom(const QString &mimeType); static xcb_atom_t mimeTypeToAtomLiteral(const QString &mimeType); static QStringList atomToMimeTypes(xcb_atom_t atom); + static QString atomName(xcb_atom_t atom); static void sendSelNotify(xcb_selection_request_event_t *event, bool success); // on selection owner changes by X clients (Xwl -> Wl) diff --git a/xwl/selection.cpp b/xwl/selection.cpp --- a/xwl/selection.cpp +++ b/xwl/selection.cpp @@ -51,6 +51,21 @@ return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection()); } +QString Selection::atomName(xcb_atom_t atom) +{ + auto *xcbConn = kwinApp()->x11Connection(); + xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom); + xcb_get_atom_name_reply_t *nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, NULL); + if (nameReply == NULL) { + return QString(); + } + + size_t len = xcb_get_atom_name_name_length(nameReply); + QString name = QString::fromLatin1(xcb_get_atom_name_name(nameReply), len); + free(nameReply); + return name; +} + QStringList Selection::atomToMimeTypes(xcb_atom_t atom) { QStringList mimeTypes; @@ -62,17 +77,7 @@ } else if (atom == atoms->uri_list) { mimeTypes << "text/uri-list" << "text/x-uri"; } else { - auto *xcbConn = kwinApp()->x11Connection(); - xcb_get_atom_name_cookie_t nameCookie = xcb_get_atom_name(xcbConn, atom); - xcb_get_atom_name_reply_t *nameReply = xcb_get_atom_name_reply(xcbConn, nameCookie, NULL); - if (nameReply == NULL) { - return QStringList(); - } - - size_t len = xcb_get_atom_name_name_length(nameReply); - char *name = xcb_get_atom_name_name(nameReply); - mimeTypes << QString::fromLatin1(name, len); - free(nameReply); + mimeTypes << atomName(atom); } return mimeTypes; } diff --git a/xwl/transfer.h b/xwl/transfer.h --- a/xwl/transfer.h +++ b/xwl/transfer.h @@ -41,7 +41,7 @@ namespace Xwl { -/* +/** * Represents for an arbitrary selection a data transfer between * sender and receiver. * @@ -103,7 +103,7 @@ bool m_timeout = false; }; -/* +/** * Represents a transfer from a Wayland native source to an X window. */ class TransferWltoX : public Transfer @@ -139,7 +139,7 @@ bool flushPropOnDelete = false; }; -/* +/** * Helper class for X to Wl transfers */ class DataReceiver @@ -166,7 +166,27 @@ QByteArray m_data; }; -/* +/** + * Compatibility receiver for clients only + * supporting the NETSCAPE_URL scheme (Firefox) + */ +class NetscapeUrlReceiver : public DataReceiver +{ +public: + void setData(char *value, int length) override; +}; + +/** + * Compatibility receiver for clients only + * supporting the text/x-moz-url scheme (Chromium on own drags) + */ +class MozUrlReceiver : public DataReceiver +{ +public: + void setData(char *value, int length) override; +}; + +/** * Represents a transfer from an X window to a Wayland native client. */ class TransferXtoWl : public Transfer diff --git a/xwl/transfer.cpp b/xwl/transfer.cpp --- a/xwl/transfer.cpp +++ b/xwl/transfer.cpp @@ -351,7 +351,13 @@ return True; } - m_receiver = new DataReceiver; + if (event->target == atoms->netscape_url) { + m_receiver = new NetscapeUrlReceiver; + } else if (event->target == atoms->moz_url) { + m_receiver = new MozUrlReceiver; + } else { + m_receiver = new DataReceiver; + } startTransfer(); return true; } @@ -463,6 +469,87 @@ } } +void NetscapeUrlReceiver::setData(char *value, int length) +{ + auto origData = QByteArray::fromRawData(value, length); + + if (origData.indexOf('\n') == -1) { + // there are no line breaks, not in Netscape url format or empty, + // but try anyway + setDataInternal(origData); + return; + } + // remove every second line + QByteArray data; + int start = 0; + bool remLine = false; + while (start < length) { + auto part = QByteArray::fromRawData(value + start, length - start); + const int linebreak = part.indexOf('\n'); + if (linebreak == -1) { + // no more linebreaks, end of work + if (!remLine) { + // append the rest + data.append(part); + } + break; + } + if (remLine) { + // no data to add, but add a linebreak for the next line + data.append('\n'); + } else { + // add data till before linebreak + data.append(part.data(), linebreak); + } + remLine = !remLine; + start = linebreak + 1; + } + setDataInternal(data); +} + +void MozUrlReceiver::setData(char *value, int length) +{ + // represent as QByteArray (guaranteed '\0'-terminated) + const auto origData = QByteArray::fromRawData(value, length); + + // text/x-moz-url data is sent in utf-16 - copies the content + // and converts it into 8 byte representation + const auto byteData = QString::fromUtf16(reinterpret_cast(origData.data())).toLatin1(); + + if (byteData.indexOf('\n') == -1) { + // there are no line breaks, not in text/x-moz-url format or empty, + // but try anyway + setDataInternal(byteData); + return; + } + // remove every second line + QByteArray data; + int start = 0; + bool remLine = false; + while (start < length) { + auto part = QByteArray::fromRawData(byteData.data() + start, byteData.size() - start); + const int linebreak = part.indexOf('\n'); + if (linebreak == -1) { + // no more linebreaks, end of work + if (!remLine) { + // append the rest + data.append(part); + } + break; + } + if (remLine) { + // no data to add, but add a linebreak for the next line + data.append('\n'); + } else { + // add data till before linebreak + data.append(part.data(), linebreak); + } + remLine = !remLine; + start = linebreak + 1; + } + setDataInternal(data); +} + void TransferXtoWl::dataSourceWrite() { QByteArray property = m_receiver->data();