Linux(XCB) で QWindow::setWindowTitle が即座に反映されないバグの修正

ふとしたことから QWidget::setWindowTitle が XCB 上でうまく動いていないことに気づいたので直しました。今回は楽勝でした。

再現するための最小限のコード

#include <QtWidgets>

int main(int argc, char *argv[])
{
        QApplication app(argc, argv);

        QLabel label(QStringLiteral("Hello World!"));
        label.resize(250, 100);
        label.show();

        QTimer timer;
        QObject::connect(&timer, &QTimer::timeout, [&]() {
                label.setWindowTitle(QTime::currentTime().toString());
        });
        timer.start(100);
        return app.exec();
}

QTimer を使用して 0.1 秒毎に現在時刻をウィンドウのタイトルバーに設定しています。

これによって毎秒時刻が表示されるはずなのですが、Gentoo Linux 上で試したところ不定期に更新される(正確には、なにかしらの操作や描画イベントのタイミングで更新される)という感じで正常に動作していませんでした。

どのへんに問題がありそうか

Linux 向けの XCB 対応の問題なのか、Windows や Mac でも再現するのかを切り分けるため、Twitter で聞いてみたところ、

という返事をいただき、多分 XCB の中に問題があるんだろうという感触を得ました。

バグの登録

地味なバグなので登録されてないと思ってましたが、バグトラッカーで「setWindowTitle」で検索してみたところ、Not always window title changed (using QWidget::setWindowTitle) と、既に報告されていたことが分かりました。

バグを修正する

まず、QWidget::setWindowTitle の中身をチェック。と思ったのですが、ちょっとショートカットをして QWindow::setTitle の中を見てみました。

void QWindow::setTitle(const QString &title)
{
    Q_D(QWindow);
    bool changed = false;
    if (d->windowTitle != title) {
        d->windowTitle = title;
        changed = true;
    }
    if (d->platformWindow && type() != Qt::Desktop)
        d->platformWindow->setWindowTitle(title);
    if (changed)
        emit windowTitleChanged(title);
}

QPlatformWindow::setWindowTitle() を呼んでいることが分かったので、次に void QXcbWindow::setWindowTitle(const QString &title) を見てみました。

void QXcbWindow::setWindowTitle(const QString &title)
{
    const QString fullTitle = formatWindowTitle(title, QString::fromUtf8(" \xe2\x80\x94 ")); // unicode character U+2014, EM DASH
    const QByteArray ba = fullTitle.toUtf8();
    Q_XCB_CALL(xcb_change_property(xcb_connection(),
                                   XCB_PROP_MODE_REPLACE,
                                   m_window,
                                   atom(QXcbAtom::_NET_WM_NAME),
                                   atom(QXcbAtom::UTF8_STRING),
                                   8,
                                   ba.length(),
                                   ba.constData()));
}

xcb_change_property を呼んでタイトルを変えているようです。そのページの下の方の EXAMPLE を見てみると、

void my_example(xcb_connection *conn, xcb_window_t window) { 
xcb_change_property(conn, 
XCB_PROP_MODE_REPLACE, 
window, 
XCB_ATOM_WM_NAME, 
XCB_ATOM_STRING, 
8, 
strlen("XCB Example"), 
"XCB Example"); 
xcb_flush(conn); 
}

こんなコードが書いてあったため、この最後の xcb_flush が Qt のコードでは抜けているんだろうなということが分かったので、qxcbwindow.cpp の中を xcb_flush で検索してヒットしたコードをコピペしてみました。

確認

qtbase/src/plugins/platforms/xcb/ 以下をビルドしなおして確認したところバグが修正されていることが確認できました。

アップストリームにパッチをプッシュ

最小限の修正なので、副作用も無いだろうということで、xcb: make sure to update window title when it is changed というパッチを作成しプッシュしました。

おわりに

楽な Qt でコード書きたいから Qt をやってるのに、気がついたら Carbon と仲良くなったりCocoa と格闘したり、 XCB と戦ってることってよくありますよね。

おすすめ