[xwl] text/x-uri converter for selected X url list format targets

Summary:
On X several target atoms are established to provide data similar to the
text/uri-list target format (the respective MIME on Wayland is called
text/x-uri).

Firefox can send link data in the NETSCAPE_URL format ones. Chromium on the
other side sends link data in the text/x-moz-url format, which transports
UTF-16 text.

For both these peculiarities this patch provides converter functions, that
translate these formats into the Wayland native text/x-uri format. In the
other direction no translation is necessary. Both browsers supports the
text/uri-list format when receiving link lists.

Test Plan: Manually with Firefox and Chromium.

Reviewers: #kwin

Subscribers: zzag, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D15629
icc-effect-5.17.5
Roman Gilg 2018-08-24 23:05:35 +02:00
parent 548978bfe1
commit 522d2935e6
7 changed files with 153 additions and 18 deletions

View File

@ -70,6 +70,8 @@ Atoms::Atoms()
, 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"))

View File

@ -79,6 +79,8 @@ public:
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;

View File

@ -42,6 +42,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
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 @@ bool WlVisit::handleEnter(xcb_client_message_event_t *ev)
// 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 @@ void WlVisit::getMimesFromWinProperty(Mimes &offers)
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 = Selection::atomToMimeTypes(mimeAtoms[i]);
const auto mimeStrings = atomToMimeTypes(mimeAtoms[i]);
for (const auto mime : mimeStrings ) {
if (!hasMimeName(offers, mime)) {
offers << Mime(mime, mimeAtoms[i]);

View File

@ -51,6 +51,21 @@ xcb_atom_t Selection::mimeTypeToAtomLiteral(const QString &mimeType)
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 @@ QStringList Selection::atomToMimeTypes(xcb_atom_t atom)
} 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;
}

View File

@ -60,6 +60,7 @@ public:
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)

View File

@ -351,7 +351,13 @@ bool TransferXtoWl::handleSelNotify(xcb_selection_notify_event_t *event)
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 DataReceiver::partRead(int length)
}
}
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<const char16_t*>(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();

View File

@ -41,7 +41,7 @@ namespace KWin
namespace Xwl
{
/*
/**
* Represents for an arbitrary selection a data transfer between
* sender and receiver.
*
@ -103,7 +103,7 @@ private:
bool m_timeout = false;
};
/*
/**
* Represents a transfer from a Wayland native source to an X window.
*/
class TransferWltoX : public Transfer
@ -139,7 +139,7 @@ private:
bool flushPropOnDelete = false;
};
/*
/**
* Helper class for X to Wl transfers
*/
class DataReceiver
@ -166,7 +166,27 @@ private:
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