diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -343,7 +343,24 @@ } virtual void updateMouseGrab(); + /** + * @returns The caption consisting of @link{captionNormal} and @link{captionSuffix} + * @see captionNormal + * @see captionSuffix + **/ virtual QString caption(bool full = true) const = 0; + /** + * @returns The caption as set by the AbstractClient without any suffix. + * @see caption + * @see captionSuffix + **/ + virtual QString captionNormal() const = 0; + /** + * @returns The suffix added to the caption (e.g. shortcut, machine name, etc.) + * @see caption + * @see captionNormal + **/ + virtual QString captionSuffix() const = 0; virtual bool isCloseable() const = 0; // TODO: remove boolean trap virtual bool isShown(bool shaded_is_shown) const = 0; @@ -986,6 +1003,12 @@ QString shortcutCaptionSuffix() const; virtual void updateCaption() = 0; + /** + * Looks for another AbstractClient with same @link{captionNormal} and @link{captionSuffix}. + * If no such AbstractClient exists @c nullptr is returned. + **/ + AbstractClient *findClientWithSameCaption() const; + private: void handlePaletteChange(); QSharedPointer m_tabBoxClient; diff --git a/abstract_client.cpp b/abstract_client.cpp --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -1758,4 +1758,12 @@ return QLatin1String(" {") + shortcut().toString() + QLatin1Char('}'); } +AbstractClient *AbstractClient::findClientWithSameCaption() const +{ + auto fetchNameInternalPredicate = [this](const AbstractClient *cl) { + return (!cl->isSpecialWindow() || cl->isToolbar()) && cl != this && cl->captionNormal() == captionNormal() && cl->captionSuffix() == captionSuffix(); + }; + return workspace()->findAbstractClient(fetchNameInternalPredicate); +} + } diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -74,6 +74,7 @@ void testHidden(); void testDesktopFileName(); void testCaptionSimplified(); + void testCaptionMultipleWindows(); void testKillWindow_data(); void testKillWindow(); void testX11WindowId_data(); @@ -693,6 +694,53 @@ QCOMPARE(c->caption(), origTitle.simplified()); } +void TestShellClient::testCaptionMultipleWindows() +{ + QScopedPointer surface(Test::createSurface()); + QScopedPointer shellSurface(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface.data()))); + shellSurface->setTitle(QStringLiteral("foo")); + auto c = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(c->caption(), QStringLiteral("foo")); + QCOMPARE(c->captionNormal(), QStringLiteral("foo")); + QCOMPARE(c->captionSuffix(), QString()); + + QScopedPointer surface2(Test::createSurface()); + QScopedPointer shellSurface2(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface2.data()))); + shellSurface2->setTitle(QStringLiteral("foo")); + auto c2 = Test::renderAndWaitForShown(surface2.data(), QSize(100, 50), Qt::blue); + QVERIFY(c2); + QCOMPARE(c2->caption(), QStringLiteral("foo <2>")); + QCOMPARE(c2->captionNormal(), QStringLiteral("foo")); + QCOMPARE(c2->captionSuffix(), QStringLiteral(" <2>")); + + QScopedPointer surface3(Test::createSurface()); + QScopedPointer shellSurface3(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface3.data()))); + shellSurface3->setTitle(QStringLiteral("foo")); + auto c3 = Test::renderAndWaitForShown(surface3.data(), QSize(100, 50), Qt::blue); + QVERIFY(c3); + QCOMPARE(c3->caption(), QStringLiteral("foo <3>")); + QCOMPARE(c3->captionNormal(), QStringLiteral("foo")); + QCOMPARE(c3->captionSuffix(), QStringLiteral(" <3>")); + + QScopedPointer surface4(Test::createSurface()); + QScopedPointer shellSurface4(qobject_cast(Test::createShellSurface(Test::ShellSurfaceType::XdgShellV5, surface4.data()))); + shellSurface4->setTitle(QStringLiteral("bar")); + auto c4 = Test::renderAndWaitForShown(surface4.data(), QSize(100, 50), Qt::blue); + QVERIFY(c4); + QCOMPARE(c4->caption(), QStringLiteral("bar")); + QCOMPARE(c4->captionNormal(), QStringLiteral("bar")); + QCOMPARE(c4->captionSuffix(), QString()); + QSignalSpy captionChangedSpy(c4, &ShellClient::captionChanged); + QVERIFY(captionChangedSpy.isValid()); + shellSurface4->setTitle(QStringLiteral("foo")); + QVERIFY(captionChangedSpy.wait()); + QCOMPARE(captionChangedSpy.count(), 1); + QCOMPARE(c4->caption(), QStringLiteral("foo <4>")); + QCOMPARE(c4->captionNormal(), QStringLiteral("foo")); + QCOMPARE(c4->captionSuffix(), QStringLiteral(" <4>")); +} + void TestShellClient::testKillWindow_data() { QTest::addColumn("socketMode"); diff --git a/client.h b/client.h --- a/client.h +++ b/client.h @@ -220,6 +220,12 @@ inline bool isBlockingCompositing() { return blocks_compositing; } QString caption(bool full = true) const override; + QString captionNormal() const override { + return cap_normal; + } + QString captionSuffix() const override { + return cap_suffix; + } using AbstractClient::keyPressEvent; void keyPressEvent(uint key_code, xcb_timestamp_t time); // FRAME ?? diff --git a/client.cpp b/client.cpp --- a/client.cpp +++ b/client.cpp @@ -1463,16 +1463,12 @@ } QString shortcut_suffix = shortcutCaptionSuffix(); cap_suffix = machine_suffix + shortcut_suffix; - auto fetchNameInternalPredicate = [this](const Client *cl) { - return (!cl->isSpecialWindow() || cl->isToolbar()) && - cl != this && cl->caption() == caption(); - }; - if ((!isSpecialWindow() || isToolbar()) && workspace()->findClient(fetchNameInternalPredicate)) { + if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { int i = 2; do { cap_suffix = machine_suffix + QLatin1String(" <") + QString::number(i) + QLatin1Char('>') + LRM; i++; - } while (workspace()->findClient(fetchNameInternalPredicate)); + } while (findClientWithSameCaption()); info->setVisibleName(caption().toUtf8().constData()); reset_name = false; } diff --git a/shell_client.h b/shell_client.h --- a/shell_client.h +++ b/shell_client.h @@ -64,6 +64,12 @@ void blockActivityUpdates(bool b = true) override; QString caption(bool full = true) const override; + QString captionNormal() const override { + return m_caption; + } + QString captionSuffix() const override { + return m_captionSuffix; + } void closeWindow() override; AbstractClient *findModal(bool allow_itself = false) override; bool isCloseable() const override; diff --git a/shell_client.cpp b/shell_client.cpp --- a/shell_client.cpp +++ b/shell_client.cpp @@ -104,12 +104,19 @@ void ShellClient::initSurface(T *shellSurface) { m_caption = shellSurface->title().simplified(); - connect(shellSurface, &T::titleChanged, this, &ShellClient::captionChanged); + // delay till end of init + QTimer::singleShot(0, this, &ShellClient::updateCaption); connect(shellSurface, &T::destroyed, this, &ShellClient::destroyClient); connect(shellSurface, &T::titleChanged, this, [this] (const QString &s) { + const auto oldSuffix = m_captionSuffix; m_caption = s.simplified(); - emit captionChanged(); + updateCaption(); + if (m_captionSuffix == oldSuffix) { + // don't emit caption change twice + // it already got emitted by the changing suffix + emit captionChanged(); + } } ); connect(shellSurface, &T::moveRequested, this, @@ -571,7 +578,15 @@ void ShellClient::updateCaption() { const QString oldSuffix = m_captionSuffix; - m_captionSuffix = shortcutCaptionSuffix(); + const auto shortcut = shortcutCaptionSuffix(); + m_captionSuffix = shortcut; + if ((!isSpecialWindow() || isToolbar()) && findClientWithSameCaption()) { + int i = 2; + do { + m_captionSuffix = shortcut + QLatin1String(" <") + QString::number(i) + QLatin1Char('>'); + i++; + } while (findClientWithSameCaption()); + } if (m_captionSuffix != oldSuffix) { emit captionChanged(); }