Fix captions with non-BMP characters

KWin replaces any non-printable character with a space. This check does not
handle surrogate pairs correctly. Additionally, translators sometimes insert
non-printable soft-hyphens into titles, which also cause KWin to display a
space instead.

This code adds the missing surrogate handling, and (to fix both issues), also
removes non-printable characters instead of replacing them with a space.

Also moved the changed test after these changes, so that changes in non-
printable characters do not cause unneeded redraws.

Unit tests adapted by Vlad Zagorodniy.

Test Plan:

kwrite /tmp/Test😣.txt shows correct title. I also tested actual non-printable
characters, such as 0x1A, and these are correctly omitted.

BUG: 376813
FIXED-IN: 5.15.5

Reviewers: #kwin, zzag

Reviewed By: #kwin, zzag

Subscribers: zzag, grasslin, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D19052
This commit is contained in:
Christoph Feck 2019-04-26 10:52:57 +02:00
parent 97c3cb73ff
commit 57440d1d6b
2 changed files with 39 additions and 14 deletions

View file

@ -49,7 +49,8 @@ private Q_SLOTS:
void init();
void cleanup();
void testCaptionSimplified();
void testTrimCaption_data();
void testTrimCaption();
void testFullscreenLayerWithActiveWaylandWindow();
void testFocusInWithWaylandLastActiveWindow();
void testX11WindowId();
@ -94,13 +95,23 @@ struct XcbConnectionDeleter
}
};
void X11ClientTest::testTrimCaption_data()
{
QTest::addColumn<QByteArray>("originalTitle");
QTest::addColumn<QByteArray>("expectedTitle");
void X11ClientTest::testCaptionSimplified()
QTest::newRow("simplified")
<< QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 Marlies Hübner - Mozilla Firefox")
<< QByteArrayLiteral("Was tun, wenn Schüler Autismus haben? Marlies Hübner - Mozilla Firefox");
QTest::newRow("with emojis")
<< QByteArrayLiteral("\bTesting non\302\255printable:\177, emoij:\360\237\230\203, non-characters:\357\277\276")
<< QByteArrayLiteral("Testing nonprintable:, emoij:\360\237\230\203, non-characters:");
}
void X11ClientTest::testTrimCaption()
{
// this test verifies that caption is properly trimmed
// see BUG 323798 comment #12
QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded);
QVERIFY(windowAddedSpy.isValid());
// create an xcb window
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
@ -119,8 +130,8 @@ void X11ClientTest::testCaptionSimplified()
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
NETWinInfo winInfo(c.data(), w, rootWindow(), NET::Properties(), NET::Properties2());
const QByteArray origTitle = QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 Marlies Hübner - Mozilla Firefox");
winInfo.setName(origTitle.constData());
QFETCH(QByteArray, originalTitle);
winInfo.setName(originalTitle);
xcb_map_window(c.data(), w);
xcb_flush(c.data());
@ -131,8 +142,8 @@ void X11ClientTest::testCaptionSimplified()
Client *client = windowCreatedSpy.first().first().value<Client*>();
QVERIFY(client);
QCOMPARE(client->window(), w);
QVERIFY(client->caption() != QString::fromUtf8(origTitle));
QCOMPARE(client->caption(), QString::fromUtf8(origTitle).simplified());
QFETCH(QByteArray, expectedTitle);
QCOMPARE(client->caption(), QString::fromUtf8(expectedTitle));
// and destroy the window again
xcb_unmap_window(c.data(), w);

View file

@ -1463,13 +1463,27 @@ static const QChar LRM(0x200E);
void Client::setCaption(const QString& _s, bool force)
{
if (!force && _s == cap_normal)
return;
QString s(_s);
for (int i = 0; i < s.length(); ++i)
if (!s[i].isPrint())
s[i] = QChar(u' ');
for (int i = 0; i < s.length(); ) {
if (!s[i].isPrint()) {
if (QChar(s[i]).isHighSurrogate() && i + 1 < s.length() && QChar(s[i + 1]).isLowSurrogate()) {
const uint uc = QChar::surrogateToUcs4(s[i], s[i + 1]);
if (!QChar::isPrint(uc)) {
s.remove(i, 2);
} else {
i += 2;
}
continue;
}
s.remove(i, 1);
continue;
}
++i;
}
const bool changed = (s != cap_normal);
if (!force && !changed) {
return;
}
cap_normal = s;
if (!force && !changed) {
emit captionChanged();