[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
This commit is contained in:
parent
548978bfe1
commit
522d2935e6
7 changed files with 153 additions and 18 deletions
|
@ -70,6 +70,8 @@ Atoms::Atoms()
|
||||||
, utf8_string(QByteArrayLiteral("UTF8_STRING"))
|
, utf8_string(QByteArrayLiteral("UTF8_STRING"))
|
||||||
, text(QByteArrayLiteral("TEXT"))
|
, text(QByteArrayLiteral("TEXT"))
|
||||||
, uri_list(QByteArrayLiteral("text/uri-list"))
|
, 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"))
|
, wl_surface_id(QByteArrayLiteral("WL_SURFACE_ID"))
|
||||||
, kde_net_wm_appmenu_service_name(QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"))
|
, 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"))
|
, kde_net_wm_appmenu_object_path(QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"))
|
||||||
|
|
2
atoms.h
2
atoms.h
|
@ -79,6 +79,8 @@ public:
|
||||||
Xcb::Atom utf8_string;
|
Xcb::Atom utf8_string;
|
||||||
Xcb::Atom text;
|
Xcb::Atom text;
|
||||||
Xcb::Atom uri_list;
|
Xcb::Atom uri_list;
|
||||||
|
Xcb::Atom netscape_url;
|
||||||
|
Xcb::Atom moz_url;
|
||||||
Xcb::Atom wl_surface_id;
|
Xcb::Atom wl_surface_id;
|
||||||
Xcb::Atom kde_net_wm_appmenu_service_name;
|
Xcb::Atom kde_net_wm_appmenu_service_name;
|
||||||
Xcb::Atom kde_net_wm_appmenu_object_path;
|
Xcb::Atom kde_net_wm_appmenu_object_path;
|
||||||
|
|
|
@ -42,6 +42,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
namespace KWin {
|
namespace KWin {
|
||||||
namespace Xwl {
|
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)
|
XToWlDrag::XToWlDrag(X11Source *source)
|
||||||
: m_src(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)
|
// message has only max 3 types (which are directly in data)
|
||||||
for (size_t i = 0; i < 3; i++) {
|
for (size_t i = 0; i < 3; i++) {
|
||||||
xcb_atom_t mimeAtom = data->data32[2 + 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 ) {
|
for (const auto mime : mimeStrings ) {
|
||||||
if (!hasMimeName(offers, mime)) {
|
if (!hasMimeName(offers, mime)) {
|
||||||
offers << Mime(mime, mimeAtom);
|
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));
|
xcb_atom_t *mimeAtoms = static_cast<xcb_atom_t*>(xcb_get_property_value(reply));
|
||||||
for (size_t i = 0; i < reply->value_len; ++i) {
|
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 ) {
|
for (const auto mime : mimeStrings ) {
|
||||||
if (!hasMimeName(offers, mime)) {
|
if (!hasMimeName(offers, mime)) {
|
||||||
offers << Mime(mime, mimeAtoms[i]);
|
offers << Mime(mime, mimeAtoms[i]);
|
||||||
|
|
|
@ -51,6 +51,21 @@ xcb_atom_t Selection::mimeTypeToAtomLiteral(const QString &mimeType)
|
||||||
return Xcb::Atom(mimeType.toLatin1(), false, kwinApp()->x11Connection());
|
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 Selection::atomToMimeTypes(xcb_atom_t atom)
|
||||||
{
|
{
|
||||||
QStringList mimeTypes;
|
QStringList mimeTypes;
|
||||||
|
@ -62,17 +77,7 @@ QStringList Selection::atomToMimeTypes(xcb_atom_t atom)
|
||||||
} else if (atom == atoms->uri_list) {
|
} else if (atom == atoms->uri_list) {
|
||||||
mimeTypes << "text/uri-list" << "text/x-uri";
|
mimeTypes << "text/uri-list" << "text/x-uri";
|
||||||
} else {
|
} else {
|
||||||
auto *xcbConn = kwinApp()->x11Connection();
|
mimeTypes << atomName(atom);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
return mimeTypes;
|
return mimeTypes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ public:
|
||||||
static xcb_atom_t mimeTypeToAtom(const QString &mimeType);
|
static xcb_atom_t mimeTypeToAtom(const QString &mimeType);
|
||||||
static xcb_atom_t mimeTypeToAtomLiteral(const QString &mimeType);
|
static xcb_atom_t mimeTypeToAtomLiteral(const QString &mimeType);
|
||||||
static QStringList atomToMimeTypes(xcb_atom_t atom);
|
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);
|
static void sendSelNotify(xcb_selection_request_event_t *event, bool success);
|
||||||
|
|
||||||
// on selection owner changes by X clients (Xwl -> Wl)
|
// on selection owner changes by X clients (Xwl -> Wl)
|
||||||
|
|
|
@ -351,7 +351,13 @@ bool TransferXtoWl::handleSelNotify(xcb_selection_notify_event_t *event)
|
||||||
return True;
|
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();
|
startTransfer();
|
||||||
return true;
|
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()
|
void TransferXtoWl::dataSourceWrite()
|
||||||
{
|
{
|
||||||
QByteArray property = m_receiver->data();
|
QByteArray property = m_receiver->data();
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace KWin
|
||||||
namespace Xwl
|
namespace Xwl
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Represents for an arbitrary selection a data transfer between
|
* Represents for an arbitrary selection a data transfer between
|
||||||
* sender and receiver.
|
* sender and receiver.
|
||||||
*
|
*
|
||||||
|
@ -103,7 +103,7 @@ private:
|
||||||
bool m_timeout = false;
|
bool m_timeout = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Represents a transfer from a Wayland native source to an X window.
|
* Represents a transfer from a Wayland native source to an X window.
|
||||||
*/
|
*/
|
||||||
class TransferWltoX : public Transfer
|
class TransferWltoX : public Transfer
|
||||||
|
@ -139,7 +139,7 @@ private:
|
||||||
bool flushPropOnDelete = false;
|
bool flushPropOnDelete = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Helper class for X to Wl transfers
|
* Helper class for X to Wl transfers
|
||||||
*/
|
*/
|
||||||
class DataReceiver
|
class DataReceiver
|
||||||
|
@ -166,7 +166,27 @@ private:
|
||||||
QByteArray m_data;
|
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.
|
* Represents a transfer from an X window to a Wayland native client.
|
||||||
*/
|
*/
|
||||||
class TransferXtoWl : public Transfer
|
class TransferXtoWl : public Transfer
|
||||||
|
|
Loading…
Reference in a new issue