Asking advice on WM2AppMenuObjectPath in KX11Extras

HI Devs, I am trying to correct a bug in @zren 's material-decoration-lim : GitHub - Zren/material-decoration: Material-ish window decoration theme for KWin, with LIM, based on zzag's original design. The problem only occurs with Libreoffice and is as follows: when you open the startcenter and then a file from there, the window changes to that of the application (Writer, Calc, etc.) but the menu remains that of the startcenter.

From a bit of investigation with xprop, I have noticed that when switching, the window ID remains the same, as does the property _KDE_NET_WM_APPMENU_SERVICE_NAME, but change the property _KDE_NET_WM_APPMENU_OBJECT_PATH.

I therefore thought of creating a filter in this file: material-decoration/src/AppMenuModel.cc at master · Zren/material-decoration · GitHub

On lines 139-141, I have modified them as follows:

void (KX11Extras::*myWindowChangeSignal)(WId window, NET::Properties properties, NET::Properties2 properties2) = &KX11Extras::windowChanged;
connect(KX11Extras::self(), myWindowChangeSignal,
        this, [this](WId window, NET::Properties properties, NET::Properties2 properties2) {
            if (properties2 & NET::WM2AppMenuObjectPath) {
                
                qCDebug(category) << "WM2AppMenuObjectPath changed";
                QTimer::singleShot(50, this, &AppMenuModel::onWinIdChanged);

However, debugging tells me that the code is executed at least four times every time I open a window, which seems inefficient to me. I have introduced the QTimer but it doesn’t seem like a great solution. I wonder why this signal is emitted so repeatedly. Do you have any suggestions?

Thanks in advance.

Can’t help you on the “why” but your code will still emit the other signal as many times as the original, just delayed.

If you want to to “compress” events you will need some state.

For example

if (properties2 & NET::WM2AppMenuObjectPath && !m_handlingAppMenuObjectPathChange) {
    qCDebug(category) << "WM2AppMenuObjectPath changed";

    // bool member of "this"
    m_handlingAppMenuObjectPathChange = true;
    QTimer::singleShot(50, this, [this]() {
        m_handlingAppMenuObjectPathChange = false;
        onWinIdChanged();
   }
}

thank you! Is the timer still necessary?

The timer is the “compressor”, e.g. the thing that combines several calls into one.

You could try a different timeout though, possibly even 0.

It really depends on why the signal is emitted several times and what the timing of those emits are.

You might also be able to write the connect in a simpler form

connect(KX11Extras::self(), &KX11Extras::windowChanged,
        this, [this](WId, NET::Properties, NET::Properties2 properties2) {
            if (properties2 & NET::WM2AppMenuObjectPath) {

Something is wrong, this is the code I have now:

void (KX11Extras::*myWindowChangeSignal)(WId window, NET::Properties properties, NET::Properties2 properties2) = &KX11Extras::windowChanged;
    connect(KX11Extras::self(), myWindowChangeSignal,
        this, [this](WId window, NET::Properties properties, NET::Properties2 properties2) {
            if ((properties2 & NET::WM2AppMenuObjectPath) && !m_handlingAppMenuObjectPathChange) {
               qCDebug(category) << "WM2AppMenuObjectPath changed";
               // bool member of "this"
               m_handlingAppMenuObjectPathChange = true;
               QTimer::singleShot(50, this, [this]() {
                   m_handlingAppMenuObjectPathChange = false;
                   onWinIdChanged();
               });
            }

I still get the debug message four times.

If you put log output into the code triggered by the timer, does it also happen 4 times?

If the new output happens once after each old output then the timeout is too short.

Also make sure you have initialized the m_handlingAppMenuObjectPathChange to false to ensure the first time this gets executed is having the correct value

Sorry I posted my code before I added the debug message.

This is the code:

void (KX11Extras::*myWindowChangeSignal)(WId, NET::Properties, NET::Properties2) = &KX11Extras::windowChanged;
        connect(KX11Extras::self(), myWindowChangeSignal,
            this, [this](WId window, NET::Properties properties, NET::Properties2 properties2) {
                // Controlliamo che la maschera contenga WM2AppMenuObjectPath
                if ((properties2 & NET::WM2AppMenuObjectPath) && !m_handlingAppMenuObjectPathChange) {
               qCDebug(category) << "WM2AppMenuObjectPath changed";
               // bool member of "this"
               m_handlingAppMenuObjectPathChange = true;
               QTimer::singleShot(50, this, [this]() {
                   m_handlingAppMenuObjectPathChange = false;
                   qCDebug(category) << "onWinIdChanged called";
                   onWinIdChanged();
               });

and these are the log messages:

07.02.2025 16:44:04:131 kwin_x11 kdecoration.material: AppMenuModel::setWinId QVariant(int, -1) => QVariant(qulonglong, 62914569)
07.02.2025 16:44:04:168 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed
07.02.2025 16:44:04:168 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed
07.02.2025 16:44:04:168 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed
07.02.2025 16:44:04:168 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed
07.02.2025 16:44:04:168 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed
07.02.2025 16:44:04:218 kwin_x11 kdecoration.material: onWinIdChanged called
07.02.2025 16:44:04:218 kwin_x11 kdecoration.material: onWinIdChanged called
07.02.2025 16:44:04:218 kwin_x11 kdecoration.material: onWinIdChanged called
07.02.2025 16:44:04:218 kwin_x11 kdecoration.material: onWinIdChanged called
07.02.2025 16:44:04:218 kwin_x11 kdecoration.material: onWinIdChanged called

As you can see, it is called several times, the same number of times the message appears on WM2AppMenu…etc. And all in the same millisecond.

I don’t know, it’s as if there are several threads doing the same thing 4 times, but it seems a bit absurd to me. Also the fact that the posts do not appear alternately but first 4 and then 4 seems strange to me.

mmmh…
5 kwins, 5 messages

The only way I can see that would lead to that output is if there are four instances of AppMenuModel.

In which case you are not actually getting four signal emits but each one gets its single one.

In which case there is no need for any workaround.

AppMenuModel is part of the decoration itself.
In any case, maybe it’s not a coincidence that in the properties of the kwin process I see 5 repetitions and have 5 repetitions of the message.

However, I have another point where I have a debug message:

void AppMenuModel::setWinId(const QVariant &id)
{
    if (m_winId == id) {
        return;
    }
    qCDebug(category) << "AppMenuModel::setWinId" << m_winId << " => " << id;
    m_winId = id;
    emit winIdChanged();
}

and is never repeated five times.

It seems that the signal is not emitted multiple times as initially suspected but only once and there are simply several identical receivers.

You could expand your log to output PID and address of this

qDebug(category) << QCoreApplication::applicationPid() << this << .....

Or even the current thread

qDebug(category) << QCoreApplication::applicationPid() << QThread::currentThread() << this << .....

07.02.2025 17:48:12:189 kwin_x11 kdecoration.material: Pid: 16278 Thread: QThread(0x6205afda0160, name = Qt mainThread) This: Material::AppMenuModel(0x6205b050c780)
07.02.2025 17:48:12:189 kwin_x11 kdecoration.material: onWinIdChanged called for 62914569
07.02.2025 17:48:12:189 kwin_x11 kdecoration.material: Pid: 16278 Thread: QThread(0x6205afda0160, name = Qt mainThread) This: Material::AppMenuModel(0x6205b03b9640)
07.02.2025 17:48:12:189 kwin_x11 kdecoration.material: onWinIdChanged called for 62914569
07.02.2025 17:48:12:189 kwin_x11 kdecoration.material: Pid: 16278 Thread: QThread(0x6205afda0160, name = Qt mainThread) This: Material::AppMenuModel(0x6205b03c47e0)
07.02.2025 17:48:12:189 kwin_x11 kdecoration.material: onWinIdChanged called for 62914569
07.02.2025 17:48:12:189 kwin_x11 kdecoration.material: Pid: 16278 Thread: QThread(0x6205afda0160, name = Qt mainThread) This: Material::AppMenuModel(0x6205b05ae190)
07.02.2025 17:48:12:190 kwin_x11 kdecoration.material: onWinIdChanged called for 62914569
07.02.2025 17:48:12:190 kwin_x11 kdecoration.material: Pid: 16278 Thread: QThread(0x6205afda0160, name = Qt mainThread) This: Material::AppMenuModel(0x6205b03ca0f0)

I have now also put a debug in onwinidchanged, that is also repeated

07.02.2025 17:56:13:907 kwin_x11 kdecoration.material: AppMenuModel::setWinId QVariant(int, -1) => QVariant(qulonglong, 81788937)
07.02.2025 17:56:13:907 kwin_x11 kdecoration.material: LOG INSIDE onWinIdChanged: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762da101a0)
07.02.2025 17:56:13:944 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed for 81788937
07.02.2025 17:56:13:944 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed for 81788937
07.02.2025 17:56:13:944 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed for 81788937
07.02.2025 17:56:13:944 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed for 81788937
07.02.2025 17:56:13:944 kwin_x11 kdecoration.material: WM2AppMenuObjectPath changed for 81788937
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: onWinIdChanged called for 81788937
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762c7435b0)
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: LOG INSIDE onWinIdChanged: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762c7435b0)
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: onWinIdChanged called for 81788937
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762cb27be0)
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: LOG INSIDE onWinIdChanged: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762cb27be0)
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: onWinIdChanged called for 81788937
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762c8ea180)
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: LOG INSIDE onWinIdChanged: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762c8ea180)
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: onWinIdChanged called for 81788937
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762cb2a3f0)
07.02.2025 17:56:14:452 kwin_x11 kdecoration.material: LOG INSIDE onWinIdChanged: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762cb2a3f0)
07.02.2025 17:56:14:453 kwin_x11 kdecoration.material: onWinIdChanged called for 81788937
07.02.2025 17:56:14:453 kwin_x11 kdecoration.material: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762da101a0)
07.02.2025 17:56:14:453 kwin_x11 kdecoration.material: LOG INSIDE onWinIdChanged: Pid: 17117 Thread: QThread(0x5a762c227160, name = Qt mainThread) This: Material::AppMenuModel(0x5a762da101a0)

right, so same process, same thread, different instances of AppMenuModel.

So all fine, no need for the QTimer hack :slight_smile:

1 Like

OK, now re-reading your message where you were talking about “multiple receivers” I had an epiphany.

Obviously there are multiple receivers: there are multiple windows open, hence multiple decorations.

In fact, by closing all the windows, I no longer have any repetition of the messages (or rather I have two, but one is the registry window and the other the one that has just been opened.

However, this does not detract from the fact that to me it seems that the code is executed several times simply because there are more open windows, or am I wrong?

Yes, correct.

Every decoration seems to have its own instance of the model and they are all connected the same way.

Depending on how the model works it might be possible to share it between decorations or each model could only react when “its” window updates.

1 Like

Thank you very much!