File indexing completed on 2024-04-28 05:30:15

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "debug_console.h"
0010 #include "compositor.h"
0011 #include "core/graphicsbufferview.h"
0012 #include "core/inputdevice.h"
0013 #include "effect/effecthandler.h"
0014 #include "input_event.h"
0015 #include "internalwindow.h"
0016 #include "keyboard_input.h"
0017 #include "main.h"
0018 #include "opengl/glplatform.h"
0019 #include "opengl/glutils.h"
0020 #include "platformsupport/scenes/opengl/openglbackend.h"
0021 #include "utils/filedescriptor.h"
0022 #include "utils/subsurfacemonitor.h"
0023 #include "wayland/abstract_data_source.h"
0024 #include "wayland/clientconnection.h"
0025 #include "wayland/datacontrolsource_v1.h"
0026 #include "wayland/datasource.h"
0027 #include "wayland/display.h"
0028 #include "wayland/primaryselectionsource_v1.h"
0029 #include "wayland/seat.h"
0030 #include "wayland/subcompositor.h"
0031 #include "wayland/surface.h"
0032 #include "wayland_server.h"
0033 #include "waylandwindow.h"
0034 #include "workspace.h"
0035 #include "x11window.h"
0036 #include "xkb.h"
0037 #include <cerrno>
0038 
0039 #include "ui_debug_console.h"
0040 
0041 // frameworks
0042 #include <KLocalizedString>
0043 #include <NETWM>
0044 // Qt
0045 #include <QFutureWatcher>
0046 #include <QMetaProperty>
0047 #include <QMetaType>
0048 #include <QMouseEvent>
0049 #include <QScopeGuard>
0050 #include <QSortFilterProxyModel>
0051 #include <QtConcurrentRun>
0052 
0053 #include <wayland-server-core.h>
0054 
0055 // xkb
0056 #include <xkbcommon/xkbcommon.h>
0057 
0058 #include <fcntl.h>
0059 #include <functional>
0060 #include <sys/poll.h>
0061 #include <unistd.h>
0062 
0063 namespace KWin
0064 {
0065 
0066 static QString tableHeaderRow(const QString &title)
0067 {
0068     return QStringLiteral("<tr><th colspan=\"2\">%1</th></tr>").arg(title);
0069 }
0070 
0071 template<typename T>
0072 static QString tableRow(const QString &title, const T &argument)
0073 {
0074     return QStringLiteral("<tr><td>%1</td><td>%2</td></tr>").arg(title).arg(argument);
0075 }
0076 
0077 static QString timestampRow(std::chrono::microseconds timestamp)
0078 {
0079     return tableRow(i18n("Timestamp"), std::chrono::duration_cast<std::chrono::milliseconds>(timestamp).count());
0080 }
0081 
0082 static QString timestampRowUsec(std::chrono::microseconds timestamp)
0083 {
0084     return tableRow(i18n("Timestamp (µsec)"), timestamp.count());
0085 }
0086 
0087 static QString buttonToString(Qt::MouseButton button)
0088 {
0089     switch (button) {
0090     case Qt::LeftButton:
0091         return i18nc("A mouse button", "Left");
0092     case Qt::RightButton:
0093         return i18nc("A mouse button", "Right");
0094     case Qt::MiddleButton:
0095         return i18nc("A mouse button", "Middle");
0096     case Qt::BackButton:
0097         return i18nc("A mouse button", "Back");
0098     case Qt::ForwardButton:
0099         return i18nc("A mouse button", "Forward");
0100     case Qt::TaskButton:
0101         return i18nc("A mouse button", "Task");
0102     case Qt::ExtraButton4:
0103         return i18nc("A mouse button", "Extra Button 4");
0104     case Qt::ExtraButton5:
0105         return i18nc("A mouse button", "Extra Button 5");
0106     case Qt::ExtraButton6:
0107         return i18nc("A mouse button", "Extra Button 6");
0108     case Qt::ExtraButton7:
0109         return i18nc("A mouse button", "Extra Button 7");
0110     case Qt::ExtraButton8:
0111         return i18nc("A mouse button", "Extra Button 8");
0112     case Qt::ExtraButton9:
0113         return i18nc("A mouse button", "Extra Button 9");
0114     case Qt::ExtraButton10:
0115         return i18nc("A mouse button", "Extra Button 10");
0116     case Qt::ExtraButton11:
0117         return i18nc("A mouse button", "Extra Button 11");
0118     case Qt::ExtraButton12:
0119         return i18nc("A mouse button", "Extra Button 12");
0120     case Qt::ExtraButton13:
0121         return i18nc("A mouse button", "Extra Button 13");
0122     case Qt::ExtraButton14:
0123         return i18nc("A mouse button", "Extra Button 14");
0124     case Qt::ExtraButton15:
0125         return i18nc("A mouse button", "Extra Button 15");
0126     case Qt::ExtraButton16:
0127         return i18nc("A mouse button", "Extra Button 16");
0128     case Qt::ExtraButton17:
0129         return i18nc("A mouse button", "Extra Button 17");
0130     case Qt::ExtraButton18:
0131         return i18nc("A mouse button", "Extra Button 18");
0132     case Qt::ExtraButton19:
0133         return i18nc("A mouse button", "Extra Button 19");
0134     case Qt::ExtraButton20:
0135         return i18nc("A mouse button", "Extra Button 20");
0136     case Qt::ExtraButton21:
0137         return i18nc("A mouse button", "Extra Button 21");
0138     case Qt::ExtraButton22:
0139         return i18nc("A mouse button", "Extra Button 22");
0140     case Qt::ExtraButton23:
0141         return i18nc("A mouse button", "Extra Button 23");
0142     case Qt::ExtraButton24:
0143         return i18nc("A mouse button", "Extra Button 24");
0144     default:
0145         return QString();
0146     }
0147 }
0148 
0149 static QString deviceRow(InputDevice *device)
0150 {
0151     if (!device) {
0152         return tableRow(i18n("Input Device"), i18nc("The input device of the event is not known", "Unknown"));
0153     }
0154     return tableRow(i18n("Input Device"), QStringLiteral("%1 (%2)").arg(device->name(), device->sysName()));
0155 }
0156 
0157 static QString buttonsToString(Qt::MouseButtons buttons)
0158 {
0159     QString ret;
0160     for (uint i = 1; i < Qt::ExtraButton24; i = i << 1) {
0161         if (buttons & i) {
0162             ret.append(buttonToString(Qt::MouseButton(uint(buttons) & i)));
0163             ret.append(QStringLiteral(" "));
0164         }
0165     };
0166     return ret.trimmed();
0167 }
0168 
0169 static const QString s_hr = QStringLiteral("<hr/>");
0170 static const QString s_tableStart = QStringLiteral("<table>");
0171 static const QString s_tableEnd = QStringLiteral("</table>");
0172 
0173 DebugConsoleFilter::DebugConsoleFilter(QTextEdit *textEdit)
0174     : InputEventSpy()
0175     , m_textEdit(textEdit)
0176 {
0177 }
0178 
0179 DebugConsoleFilter::~DebugConsoleFilter() = default;
0180 
0181 void DebugConsoleFilter::pointerEvent(MouseEvent *event)
0182 {
0183     QString text = s_hr;
0184     const QString timestamp = timestampRow(event->timestamp());
0185 
0186     text.append(s_tableStart);
0187     switch (event->type()) {
0188     case QEvent::MouseMove: {
0189         text.append(tableHeaderRow(i18nc("A mouse pointer motion event", "Pointer Motion")));
0190         text.append(deviceRow(event->device()));
0191         text.append(timestamp);
0192         text.append(timestampRowUsec(event->timestamp()));
0193         if (!event->delta().isNull()) {
0194             text.append(tableRow(i18nc("The relative mouse movement", "Delta"),
0195                                  QStringLiteral("%1/%2").arg(event->delta().x()).arg(event->delta().y())));
0196         }
0197         if (!event->deltaUnaccelerated().isNull()) {
0198             text.append(tableRow(i18nc("The relative mouse movement", "Delta (not accelerated)"),
0199                                  QStringLiteral("%1/%2").arg(event->deltaUnaccelerated().x()).arg(event->deltaUnaccelerated().y())));
0200         }
0201         text.append(tableRow(i18nc("The global mouse pointer position", "Global Position"), QStringLiteral("%1/%2").arg(event->pos().x()).arg(event->pos().y())));
0202         break;
0203     }
0204     case QEvent::MouseButtonPress:
0205         text.append(tableHeaderRow(i18nc("A mouse pointer button press event", "Pointer Button Press")));
0206         text.append(deviceRow(event->device()));
0207         text.append(timestamp);
0208         text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button())));
0209         text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), event->nativeButton()));
0210         text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons())));
0211         break;
0212     case QEvent::MouseButtonRelease:
0213         text.append(tableHeaderRow(i18nc("A mouse pointer button release event", "Pointer Button Release")));
0214         text.append(deviceRow(event->device()));
0215         text.append(timestamp);
0216         text.append(tableRow(i18nc("A button in a mouse press/release event", "Button"), buttonToString(event->button())));
0217         text.append(tableRow(i18nc("A button in a mouse press/release event", "Native Button code"), event->nativeButton()));
0218         text.append(tableRow(i18nc("All currently pressed buttons in a mouse press/release event", "Pressed Buttons"), buttonsToString(event->buttons())));
0219         break;
0220     default:
0221         break;
0222     }
0223     text.append(s_tableEnd);
0224 
0225     m_textEdit->insertHtml(text);
0226     m_textEdit->ensureCursorVisible();
0227 }
0228 
0229 void DebugConsoleFilter::wheelEvent(WheelEvent *event)
0230 {
0231     QString text = s_hr;
0232     text.append(s_tableStart);
0233     text.append(tableHeaderRow(i18nc("A mouse pointer axis (wheel) event", "Pointer Axis")));
0234     text.append(deviceRow(event->device()));
0235     text.append(timestampRow(event->timestamp()));
0236     const Qt::Orientation orientation = event->angleDelta().x() == 0 ? Qt::Vertical : Qt::Horizontal;
0237     text.append(tableRow(i18nc("The orientation of a pointer axis event", "Orientation"),
0238                          orientation == Qt::Horizontal ? i18nc("An orientation of a pointer axis event", "Horizontal")
0239                                                        : i18nc("An orientation of a pointer axis event", "Vertical")));
0240     text.append(tableRow(i18nc("The angle delta of a pointer axis event", "Delta"),
0241                          orientation == Qt::Horizontal ? event->angleDelta().x() : event->angleDelta().y()));
0242     text.append(s_tableEnd);
0243 
0244     m_textEdit->insertHtml(text);
0245     m_textEdit->ensureCursorVisible();
0246 }
0247 
0248 void DebugConsoleFilter::keyEvent(KeyEvent *event)
0249 {
0250     QString text = s_hr;
0251     text.append(s_tableStart);
0252 
0253     switch (event->type()) {
0254     case QEvent::KeyPress:
0255         text.append(tableHeaderRow(i18nc("A key press event", "Key Press")));
0256         break;
0257     case QEvent::KeyRelease:
0258         text.append(tableHeaderRow(i18nc("A key release event", "Key Release")));
0259         break;
0260     default:
0261         break;
0262     }
0263     text.append(deviceRow(event->device()));
0264     auto modifiersToString = [event] {
0265         QString ret;
0266         if (event->modifiers().testFlag(Qt::ShiftModifier)) {
0267             ret.append(i18nc("A keyboard modifier", "Shift"));
0268             ret.append(QStringLiteral(" "));
0269         }
0270         if (event->modifiers().testFlag(Qt::ControlModifier)) {
0271             ret.append(i18nc("A keyboard modifier", "Control"));
0272             ret.append(QStringLiteral(" "));
0273         }
0274         if (event->modifiers().testFlag(Qt::AltModifier)) {
0275             ret.append(i18nc("A keyboard modifier", "Alt"));
0276             ret.append(QStringLiteral(" "));
0277         }
0278         if (event->modifiers().testFlag(Qt::MetaModifier)) {
0279             ret.append(i18nc("A keyboard modifier", "Meta"));
0280             ret.append(QStringLiteral(" "));
0281         }
0282         if (event->modifiers().testFlag(Qt::KeypadModifier)) {
0283             ret.append(i18nc("A keyboard modifier", "Keypad"));
0284             ret.append(QStringLiteral(" "));
0285         }
0286         if (event->modifiers().testFlag(Qt::GroupSwitchModifier)) {
0287             ret.append(i18nc("A keyboard modifier", "Group-switch"));
0288             ret.append(QStringLiteral(" "));
0289         }
0290         return ret;
0291     };
0292     text.append(timestampRow(event->timestamp()));
0293     text.append(tableRow(i18nc("Whether the event is an automatic key repeat", "Repeat"), event->isAutoRepeat()));
0294 
0295     const auto keyMetaObject = Qt::qt_getEnumMetaObject(Qt::Key());
0296     const auto enumerator = keyMetaObject->enumerator(keyMetaObject->indexOfEnumerator("Key"));
0297     text.append(tableRow(i18nc("The code as read from the input device", "Scan code"), event->nativeScanCode()));
0298     text.append(tableRow(i18nc("Key according to Qt", "Qt::Key code"),
0299                          enumerator.valueToKey(event->key())));
0300     text.append(tableRow(i18nc("The translated code to an Xkb symbol", "Xkb symbol"), event->nativeVirtualKey()));
0301     text.append(tableRow(i18nc("The translated code interpreted as text", "Utf8"), event->text()));
0302     text.append(tableRow(i18nc("The currently active modifiers", "Modifiers"), modifiersToString()));
0303 
0304     text.append(s_tableEnd);
0305 
0306     m_textEdit->insertHtml(text);
0307     m_textEdit->ensureCursorVisible();
0308 }
0309 
0310 void DebugConsoleFilter::touchDown(qint32 id, const QPointF &pos, std::chrono::microseconds time)
0311 {
0312     QString text = s_hr;
0313     text.append(s_tableStart);
0314     text.append(tableHeaderRow(i18nc("A touch down event", "Touch down")));
0315     text.append(timestampRow(time));
0316     text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id));
0317     text.append(tableRow(i18nc("The global position of the touch point", "Global position"),
0318                          QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y())));
0319     text.append(s_tableEnd);
0320 
0321     m_textEdit->insertHtml(text);
0322     m_textEdit->ensureCursorVisible();
0323 }
0324 
0325 void DebugConsoleFilter::touchMotion(qint32 id, const QPointF &pos, std::chrono::microseconds time)
0326 {
0327     QString text = s_hr;
0328     text.append(s_tableStart);
0329     text.append(tableHeaderRow(i18nc("A touch motion event", "Touch Motion")));
0330     text.append(timestampRow(time));
0331     text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id));
0332     text.append(tableRow(i18nc("The global position of the touch point", "Global position"),
0333                          QStringLiteral("%1/%2").arg(pos.x()).arg(pos.y())));
0334     text.append(s_tableEnd);
0335 
0336     m_textEdit->insertHtml(text);
0337     m_textEdit->ensureCursorVisible();
0338 }
0339 
0340 void DebugConsoleFilter::touchUp(qint32 id, std::chrono::microseconds time)
0341 {
0342     QString text = s_hr;
0343     text.append(s_tableStart);
0344     text.append(tableHeaderRow(i18nc("A touch up event", "Touch Up")));
0345     text.append(timestampRow(time));
0346     text.append(tableRow(i18nc("The id of the touch point in the touch event", "Point identifier"), id));
0347     text.append(s_tableEnd);
0348 
0349     m_textEdit->insertHtml(text);
0350     m_textEdit->ensureCursorVisible();
0351 }
0352 
0353 void DebugConsoleFilter::pinchGestureBegin(int fingerCount, std::chrono::microseconds time)
0354 {
0355     QString text = s_hr;
0356     text.append(s_tableStart);
0357     text.append(tableHeaderRow(i18nc("A pinch gesture is started", "Pinch start")));
0358     text.append(timestampRow(time));
0359     text.append(tableRow(i18nc("Number of fingers in this pinch gesture", "Finger count"), fingerCount));
0360     text.append(s_tableEnd);
0361 
0362     m_textEdit->insertHtml(text);
0363     m_textEdit->ensureCursorVisible();
0364 }
0365 
0366 void DebugConsoleFilter::pinchGestureUpdate(qreal scale, qreal angleDelta, const QPointF &delta, std::chrono::microseconds time)
0367 {
0368     QString text = s_hr;
0369     text.append(s_tableStart);
0370     text.append(tableHeaderRow(i18nc("A pinch gesture is updated", "Pinch update")));
0371     text.append(timestampRow(time));
0372     text.append(tableRow(i18nc("Current scale in pinch gesture", "Scale"), scale));
0373     text.append(tableRow(i18nc("Current angle in pinch gesture", "Angle delta"), angleDelta));
0374     text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta x"), delta.x()));
0375     text.append(tableRow(i18nc("Current delta in pinch gesture", "Delta y"), delta.y()));
0376     text.append(s_tableEnd);
0377 
0378     m_textEdit->insertHtml(text);
0379     m_textEdit->ensureCursorVisible();
0380 }
0381 
0382 void DebugConsoleFilter::pinchGestureEnd(std::chrono::microseconds time)
0383 {
0384     QString text = s_hr;
0385     text.append(s_tableStart);
0386     text.append(tableHeaderRow(i18nc("A pinch gesture ended", "Pinch end")));
0387     text.append(timestampRow(time));
0388     text.append(s_tableEnd);
0389 
0390     m_textEdit->insertHtml(text);
0391     m_textEdit->ensureCursorVisible();
0392 }
0393 
0394 void DebugConsoleFilter::pinchGestureCancelled(std::chrono::microseconds time)
0395 {
0396     QString text = s_hr;
0397     text.append(s_tableStart);
0398     text.append(tableHeaderRow(i18nc("A pinch gesture got cancelled", "Pinch cancelled")));
0399     text.append(timestampRow(time));
0400     text.append(s_tableEnd);
0401 
0402     m_textEdit->insertHtml(text);
0403     m_textEdit->ensureCursorVisible();
0404 }
0405 
0406 void DebugConsoleFilter::swipeGestureBegin(int fingerCount, std::chrono::microseconds time)
0407 {
0408     QString text = s_hr;
0409     text.append(s_tableStart);
0410     text.append(tableHeaderRow(i18nc("A swipe gesture is started", "Swipe start")));
0411     text.append(timestampRow(time));
0412     text.append(tableRow(i18nc("Number of fingers in this swipe gesture", "Finger count"), fingerCount));
0413     text.append(s_tableEnd);
0414 
0415     m_textEdit->insertHtml(text);
0416     m_textEdit->ensureCursorVisible();
0417 }
0418 
0419 void DebugConsoleFilter::swipeGestureUpdate(const QPointF &delta, std::chrono::microseconds time)
0420 {
0421     QString text = s_hr;
0422     text.append(s_tableStart);
0423     text.append(tableHeaderRow(i18nc("A swipe gesture is updated", "Swipe update")));
0424     text.append(timestampRow(time));
0425     text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta x"), delta.x()));
0426     text.append(tableRow(i18nc("Current delta in swipe gesture", "Delta y"), delta.y()));
0427     text.append(s_tableEnd);
0428 
0429     m_textEdit->insertHtml(text);
0430     m_textEdit->ensureCursorVisible();
0431 }
0432 
0433 void DebugConsoleFilter::swipeGestureEnd(std::chrono::microseconds time)
0434 {
0435     QString text = s_hr;
0436     text.append(s_tableStart);
0437     text.append(tableHeaderRow(i18nc("A swipe gesture ended", "Swipe end")));
0438     text.append(timestampRow(time));
0439     text.append(s_tableEnd);
0440 
0441     m_textEdit->insertHtml(text);
0442     m_textEdit->ensureCursorVisible();
0443 }
0444 
0445 void DebugConsoleFilter::swipeGestureCancelled(std::chrono::microseconds time)
0446 {
0447     QString text = s_hr;
0448     text.append(s_tableStart);
0449     text.append(tableHeaderRow(i18nc("A swipe gesture got cancelled", "Swipe cancelled")));
0450     text.append(timestampRow(time));
0451     text.append(s_tableEnd);
0452 
0453     m_textEdit->insertHtml(text);
0454     m_textEdit->ensureCursorVisible();
0455 }
0456 
0457 void DebugConsoleFilter::switchEvent(SwitchEvent *event)
0458 {
0459     QString text = s_hr;
0460     text.append(s_tableStart);
0461     text.append(tableHeaderRow(i18nc("A hardware switch (e.g. notebook lid) got toggled", "Switch toggled")));
0462     text.append(timestampRow(event->timestamp()));
0463     text.append(timestampRowUsec(event->timestamp()));
0464     text.append(deviceRow(event->device()));
0465     QString switchName;
0466     if (event->device()->isLidSwitch()) {
0467         switchName = i18nc("Name of a hardware switch", "Notebook lid");
0468     } else if (event->device()->isTabletModeSwitch()) {
0469         switchName = i18nc("Name of a hardware switch", "Tablet mode");
0470     }
0471     text.append(tableRow(i18nc("A hardware switch", "Switch"), switchName));
0472     QString switchState;
0473     switch (event->state()) {
0474     case SwitchEvent::State::Off:
0475         switchState = i18nc("The hardware switch got turned off", "Off");
0476         break;
0477     case SwitchEvent::State::On:
0478         switchState = i18nc("The hardware switch got turned on", "On");
0479         break;
0480     default:
0481         Q_UNREACHABLE();
0482     }
0483     text.append(tableRow(i18nc("State of a hardware switch (on/off)", "State"), switchState));
0484     text.append(s_tableEnd);
0485 
0486     m_textEdit->insertHtml(text);
0487     m_textEdit->ensureCursorVisible();
0488 }
0489 
0490 void DebugConsoleFilter::tabletToolEvent(TabletEvent *event)
0491 {
0492     QString typeString;
0493     {
0494         QDebug d(&typeString);
0495         d << event->type();
0496     }
0497 
0498     QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Tool"))
0499         + tableRow(i18n("EventType"), typeString)
0500         + tableRow(i18n("Position"),
0501                    QStringLiteral("%1,%2").arg(event->pos().x()).arg(event->pos().y()))
0502         + tableRow(i18n("Tilt"),
0503                    QStringLiteral("%1,%2").arg(event->xTilt()).arg(event->yTilt()))
0504         + tableRow(i18n("Rotation"), QString::number(event->rotation()))
0505         + tableRow(i18n("Pressure"), QString::number(event->pressure()))
0506         + tableRow(i18n("Buttons"), QString::number(event->buttons()))
0507         + tableRow(i18n("Modifiers"), QString::number(event->modifiers()))
0508         + s_tableEnd;
0509 
0510     m_textEdit->insertHtml(text);
0511     m_textEdit->ensureCursorVisible();
0512 }
0513 
0514 void DebugConsoleFilter::tabletToolButtonEvent(uint button, bool pressed, const TabletToolId &tabletToolId, std::chrono::microseconds time)
0515 {
0516     QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Tool Button"))
0517         + tableRow(i18n("Button"), button)
0518         + tableRow(i18n("Pressed"), pressed)
0519         + tableRow(i18n("Tablet"), qHash(tabletToolId.m_deviceGroupData))
0520         + timestampRow(time)
0521         + s_tableEnd;
0522 
0523     m_textEdit->insertHtml(text);
0524     m_textEdit->ensureCursorVisible();
0525 }
0526 
0527 void DebugConsoleFilter::tabletPadButtonEvent(uint button, bool pressed, const TabletPadId &tabletPadId, std::chrono::microseconds time)
0528 {
0529     QString text = s_hr + s_tableStart
0530         + tableHeaderRow(i18n("Tablet Pad Button"))
0531         + tableRow(i18n("Button"), button)
0532         + tableRow(i18n("Pressed"), pressed)
0533         + tableRow(i18n("Tablet"), qHash(tabletPadId.data))
0534         + timestampRow(time)
0535         + s_tableEnd;
0536 
0537     m_textEdit->insertHtml(text);
0538     m_textEdit->ensureCursorVisible();
0539 }
0540 
0541 void DebugConsoleFilter::tabletPadStripEvent(int number, int position, bool isFinger, const TabletPadId &tabletPadId, std::chrono::microseconds time)
0542 {
0543     QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Pad Strip"))
0544         + tableRow(i18n("Number"), number)
0545         + tableRow(i18n("Position"), position)
0546         + tableRow(i18n("isFinger"), isFinger)
0547         + tableRow(i18n("Tablet"), qHash(tabletPadId.data))
0548         + timestampRow(time)
0549         + s_tableEnd;
0550 
0551     m_textEdit->insertHtml(text);
0552     m_textEdit->ensureCursorVisible();
0553 }
0554 
0555 void DebugConsoleFilter::tabletPadRingEvent(int number, int position, bool isFinger, const TabletPadId &tabletPadId, std::chrono::microseconds time)
0556 {
0557     QString text = s_hr + s_tableStart + tableHeaderRow(i18n("Tablet Pad Ring"))
0558         + tableRow(i18n("Number"), number)
0559         + tableRow(i18n("Position"), position)
0560         + tableRow(i18n("isFinger"), isFinger)
0561         + tableRow(i18n("Tablet"), qHash(tabletPadId.data))
0562         + timestampRow(time)
0563         + s_tableEnd;
0564 
0565     m_textEdit->insertHtml(text);
0566     m_textEdit->ensureCursorVisible();
0567 }
0568 
0569 static QString sourceString(const AbstractDataSource *const source)
0570 {
0571     if (!source) {
0572         return QString();
0573     }
0574 
0575     if (!source->client()) {
0576         return QStringLiteral("XWayland source");
0577     }
0578 
0579     const QString executable = waylandServer()->display()->getConnection(source->client())->executablePath();
0580 
0581     if (auto dataSource = qobject_cast<const DataSourceInterface *const>(source)) {
0582         return QStringLiteral("wl_data_source@%1 of %2").arg(wl_resource_get_id(dataSource->resource())).arg(executable);
0583     } else if (qobject_cast<const PrimarySelectionSourceV1Interface *const>(source)) {
0584         return QStringLiteral("zwp_primary_selection_source_v1 of %2").arg(executable);
0585     } else if (qobject_cast<const DataControlSourceV1Interface *const>(source)) {
0586         return QStringLiteral("data control by %1").arg(executable);
0587     }
0588     return QStringLiteral("unknown source of").arg(executable);
0589 }
0590 
0591 DebugConsole::DebugConsole()
0592     : QWidget()
0593     , m_ui(new Ui::DebugConsole)
0594 {
0595     setAttribute(Qt::WA_ShowWithoutActivating);
0596     m_ui->setupUi(this);
0597 
0598     auto windowsModel = new DebugConsoleModel(this);
0599     QSortFilterProxyModel *proxyWindowsModel = new QSortFilterProxyModel(this);
0600     proxyWindowsModel->setSourceModel(windowsModel);
0601     m_ui->windowsView->setModel(proxyWindowsModel);
0602     m_ui->windowsView->sortByColumn(0, Qt::AscendingOrder);
0603     m_ui->windowsView->header()->setSortIndicatorShown(true);
0604     m_ui->windowsView->setItemDelegate(new DebugConsoleDelegate(this));
0605 
0606     m_ui->surfacesView->setModel(new SurfaceTreeModel(this));
0607     m_ui->clipboardContent->setModel(new DataSourceModel(this));
0608     m_ui->primaryContent->setModel(new DataSourceModel(this));
0609     m_ui->inputDevicesView->setModel(new InputDeviceModel(this));
0610     m_ui->inputDevicesView->setItemDelegate(new DebugConsoleDelegate(this));
0611     m_ui->quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit")));
0612     m_ui->tabWidget->setTabIcon(0, QIcon::fromTheme(QStringLiteral("view-list-tree")));
0613     m_ui->tabWidget->setTabIcon(1, QIcon::fromTheme(QStringLiteral("view-list-tree")));
0614 
0615     if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) {
0616         m_ui->tabWidget->setTabEnabled(1, false);
0617         m_ui->tabWidget->setTabEnabled(2, false);
0618         m_ui->tabWidget->setTabEnabled(6, false);
0619         setWindowFlags(Qt::X11BypassWindowManagerHint);
0620     }
0621 
0622     connect(m_ui->quitButton, &QAbstractButton::clicked, this, &DebugConsole::deleteLater);
0623     connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, [this](int index) {
0624         // delay creation of input event filter until the tab is selected
0625         if (index == 2 && !m_inputFilter) {
0626             m_inputFilter = std::make_unique<DebugConsoleFilter>(m_ui->inputTextEdit);
0627             input()->installInputEventSpy(m_inputFilter.get());
0628         }
0629         if (index == 5) {
0630             updateKeyboardTab();
0631             connect(input(), &InputRedirection::keyStateChanged, this, &DebugConsole::updateKeyboardTab);
0632         }
0633         if (index == 6) {
0634             static_cast<DataSourceModel *>(m_ui->clipboardContent->model())->setSource(waylandServer()->seat()->selection());
0635             m_ui->clipboardSource->setText(sourceString(waylandServer()->seat()->selection()));
0636             connect(waylandServer()->seat(), &SeatInterface::selectionChanged, this, [this](AbstractDataSource *source) {
0637                 static_cast<DataSourceModel *>(m_ui->clipboardContent->model())->setSource(source);
0638                 m_ui->clipboardSource->setText(sourceString(source));
0639             });
0640             static_cast<DataSourceModel *>(m_ui->primaryContent->model())->setSource(waylandServer()->seat()->primarySelection());
0641             m_ui->primarySource->setText(sourceString(waylandServer()->seat()->primarySelection()));
0642             connect(waylandServer()->seat(), &SeatInterface::primarySelectionChanged, this, [this](AbstractDataSource *source) {
0643                 static_cast<DataSourceModel *>(m_ui->primaryContent->model())->setSource(source);
0644                 m_ui->primarySource->setText(sourceString(source));
0645             });
0646         }
0647     });
0648 
0649     initGLTab();
0650 }
0651 
0652 DebugConsole::~DebugConsole() = default;
0653 
0654 void DebugConsole::initGLTab()
0655 {
0656     if (!effects || !effects->isOpenGLCompositing()) {
0657         m_ui->noOpenGLLabel->setVisible(true);
0658         m_ui->glInfoScrollArea->setVisible(false);
0659         return;
0660     }
0661     GLPlatform *gl = GLPlatform::instance();
0662     m_ui->noOpenGLLabel->setVisible(false);
0663     m_ui->glInfoScrollArea->setVisible(true);
0664     m_ui->glVendorStringLabel->setText(QString::fromLocal8Bit(gl->glVendorString()));
0665     m_ui->glRendererStringLabel->setText(QString::fromLocal8Bit(gl->glRendererString()));
0666     m_ui->glVersionStringLabel->setText(QString::fromLocal8Bit(gl->glVersionString()));
0667     m_ui->glslVersionStringLabel->setText(QString::fromLocal8Bit(gl->glShadingLanguageVersionString()));
0668     m_ui->glDriverLabel->setText(GLPlatform::driverToString(gl->driver()));
0669     m_ui->glGPULabel->setText(GLPlatform::chipClassToString(gl->chipClass()));
0670     m_ui->glVersionLabel->setText(gl->glVersion().toString());
0671     m_ui->glslLabel->setText(gl->glslVersion().toString());
0672 
0673     auto extensionsString = [](const auto &extensions) {
0674         QString text = QStringLiteral("<ul>");
0675         for (auto extension : extensions) {
0676             text.append(QStringLiteral("<li>%1</li>").arg(QString::fromLocal8Bit(extension)));
0677         }
0678         text.append(QStringLiteral("</ul>"));
0679         return text;
0680     };
0681 
0682     const OpenGLBackend *backend = static_cast<OpenGLBackend *>(Compositor::self()->backend());
0683     m_ui->platformExtensionsLabel->setText(extensionsString(backend->extensions()));
0684     m_ui->openGLExtensionsLabel->setText(extensionsString(openGLExtensions()));
0685 }
0686 
0687 template<typename T>
0688 QString keymapComponentToString(xkb_keymap *map, const T &count, std::function<const char *(xkb_keymap *, T)> f)
0689 {
0690     QString text = QStringLiteral("<ul>");
0691     for (T i = 0; i < count; i++) {
0692         text.append(QStringLiteral("<li>%1</li>").arg(QString::fromLocal8Bit(f(map, i))));
0693     }
0694     text.append(QStringLiteral("</ul>"));
0695     return text;
0696 }
0697 
0698 template<typename T>
0699 QString stateActiveComponents(xkb_state *state, const T &count, std::function<int(xkb_state *, T)> f, std::function<const char *(xkb_keymap *, T)> name)
0700 {
0701     QString text = QStringLiteral("<ul>");
0702     xkb_keymap *map = xkb_state_get_keymap(state);
0703     for (T i = 0; i < count; i++) {
0704         if (f(state, i) == 1) {
0705             text.append(QStringLiteral("<li>%1</li>").arg(QString::fromLocal8Bit(name(map, i))));
0706         }
0707     }
0708     text.append(QStringLiteral("</ul>"));
0709     return text;
0710 }
0711 
0712 void DebugConsole::updateKeyboardTab()
0713 {
0714     auto xkb = input()->keyboard()->xkb();
0715     xkb_keymap *map = xkb->keymap();
0716     xkb_state *state = xkb->state();
0717     m_ui->layoutsLabel->setText(keymapComponentToString<xkb_layout_index_t>(map, xkb_keymap_num_layouts(map), &xkb_keymap_layout_get_name));
0718     m_ui->currentLayoutLabel->setText(xkb_keymap_layout_get_name(map, xkb->currentLayout()));
0719     m_ui->modifiersLabel->setText(keymapComponentToString<xkb_mod_index_t>(map, xkb_keymap_num_mods(map), &xkb_keymap_mod_get_name));
0720     m_ui->ledsLabel->setText(keymapComponentToString<xkb_led_index_t>(map, xkb_keymap_num_leds(map), &xkb_keymap_led_get_name));
0721     m_ui->activeLedsLabel->setText(stateActiveComponents<xkb_led_index_t>(state, xkb_keymap_num_leds(map), &xkb_state_led_index_is_active, &xkb_keymap_led_get_name));
0722 
0723     using namespace std::placeholders;
0724     auto modActive = std::bind(xkb_state_mod_index_is_active, _1, _2, XKB_STATE_MODS_EFFECTIVE);
0725     m_ui->activeModifiersLabel->setText(stateActiveComponents<xkb_mod_index_t>(state, xkb_keymap_num_mods(map), modActive, &xkb_keymap_mod_get_name));
0726 }
0727 
0728 void DebugConsole::showEvent(QShowEvent *event)
0729 {
0730     QWidget::showEvent(event);
0731 
0732     // delay the connection to the show event as in ctor the windowHandle returns null
0733     connect(windowHandle(), &QWindow::visibleChanged, this, [this](bool visible) {
0734         if (visible) {
0735             // ignore
0736             return;
0737         }
0738         deleteLater();
0739     });
0740 }
0741 
0742 DebugConsoleDelegate::DebugConsoleDelegate(QObject *parent)
0743     : QStyledItemDelegate(parent)
0744 {
0745 }
0746 
0747 DebugConsoleDelegate::~DebugConsoleDelegate() = default;
0748 
0749 QString DebugConsoleDelegate::displayText(const QVariant &value, const QLocale &locale) const
0750 {
0751     switch (value.userType()) {
0752     case QMetaType::QPoint: {
0753         const QPoint p = value.toPoint();
0754         return QStringLiteral("%1,%2").arg(p.x()).arg(p.y());
0755     }
0756     case QMetaType::QPointF: {
0757         const QPointF p = value.toPointF();
0758         return QStringLiteral("%1,%2").arg(p.x()).arg(p.y());
0759     }
0760     case QMetaType::QSize: {
0761         const QSize s = value.toSize();
0762         return QStringLiteral("%1x%2").arg(s.width()).arg(s.height());
0763     }
0764     case QMetaType::QSizeF: {
0765         const QSizeF s = value.toSizeF();
0766         return QStringLiteral("%1x%2").arg(s.width()).arg(s.height());
0767     }
0768     case QMetaType::QRect: {
0769         const QRect r = value.toRect();
0770         return QStringLiteral("%1,%2 %3x%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height());
0771     }
0772     case QMetaType::QRectF: {
0773         const QRectF r = value.toRectF();
0774         return QStringLiteral("%1,%2 %3x%4").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height());
0775     }
0776     default:
0777         if (value.userType() == qMetaTypeId<KWin::SurfaceInterface *>()) {
0778             if (auto s = value.value<KWin::SurfaceInterface *>()) {
0779                 return QStringLiteral("KWin::SurfaceInterface(0x%1)").arg(qulonglong(s), 0, 16);
0780             } else {
0781                 return QStringLiteral("nullptr");
0782             }
0783         }
0784         if (value.userType() == qMetaTypeId<KWin::Window *>()) {
0785             if (auto w = value.value<KWin::Window *>()) {
0786                 return w->caption() + QLatin1Char(' ') + QString::fromUtf8(w->metaObject()->className());
0787             } else {
0788                 return QStringLiteral("nullptr");
0789             }
0790         }
0791         if (value.userType() == qMetaTypeId<Qt::MouseButtons>()) {
0792             const auto buttons = value.value<Qt::MouseButtons>();
0793             if (buttons == Qt::NoButton) {
0794                 return i18n("No Mouse Buttons");
0795             }
0796             QStringList list;
0797             if (buttons.testFlag(Qt::LeftButton)) {
0798                 list << i18nc("Mouse Button", "left");
0799             }
0800             if (buttons.testFlag(Qt::RightButton)) {
0801                 list << i18nc("Mouse Button", "right");
0802             }
0803             if (buttons.testFlag(Qt::MiddleButton)) {
0804                 list << i18nc("Mouse Button", "middle");
0805             }
0806             if (buttons.testFlag(Qt::BackButton)) {
0807                 list << i18nc("Mouse Button", "back");
0808             }
0809             if (buttons.testFlag(Qt::ForwardButton)) {
0810                 list << i18nc("Mouse Button", "forward");
0811             }
0812             if (buttons.testFlag(Qt::ExtraButton1)) {
0813                 list << i18nc("Mouse Button", "extra 1");
0814             }
0815             if (buttons.testFlag(Qt::ExtraButton2)) {
0816                 list << i18nc("Mouse Button", "extra 2");
0817             }
0818             if (buttons.testFlag(Qt::ExtraButton3)) {
0819                 list << i18nc("Mouse Button", "extra 3");
0820             }
0821             if (buttons.testFlag(Qt::ExtraButton4)) {
0822                 list << i18nc("Mouse Button", "extra 4");
0823             }
0824             if (buttons.testFlag(Qt::ExtraButton5)) {
0825                 list << i18nc("Mouse Button", "extra 5");
0826             }
0827             if (buttons.testFlag(Qt::ExtraButton6)) {
0828                 list << i18nc("Mouse Button", "extra 6");
0829             }
0830             if (buttons.testFlag(Qt::ExtraButton7)) {
0831                 list << i18nc("Mouse Button", "extra 7");
0832             }
0833             if (buttons.testFlag(Qt::ExtraButton8)) {
0834                 list << i18nc("Mouse Button", "extra 8");
0835             }
0836             if (buttons.testFlag(Qt::ExtraButton9)) {
0837                 list << i18nc("Mouse Button", "extra 9");
0838             }
0839             if (buttons.testFlag(Qt::ExtraButton10)) {
0840                 list << i18nc("Mouse Button", "extra 10");
0841             }
0842             if (buttons.testFlag(Qt::ExtraButton11)) {
0843                 list << i18nc("Mouse Button", "extra 11");
0844             }
0845             if (buttons.testFlag(Qt::ExtraButton12)) {
0846                 list << i18nc("Mouse Button", "extra 12");
0847             }
0848             if (buttons.testFlag(Qt::ExtraButton13)) {
0849                 list << i18nc("Mouse Button", "extra 13");
0850             }
0851             if (buttons.testFlag(Qt::ExtraButton14)) {
0852                 list << i18nc("Mouse Button", "extra 14");
0853             }
0854             if (buttons.testFlag(Qt::ExtraButton15)) {
0855                 list << i18nc("Mouse Button", "extra 15");
0856             }
0857             if (buttons.testFlag(Qt::ExtraButton16)) {
0858                 list << i18nc("Mouse Button", "extra 16");
0859             }
0860             if (buttons.testFlag(Qt::ExtraButton17)) {
0861                 list << i18nc("Mouse Button", "extra 17");
0862             }
0863             if (buttons.testFlag(Qt::ExtraButton18)) {
0864                 list << i18nc("Mouse Button", "extra 18");
0865             }
0866             if (buttons.testFlag(Qt::ExtraButton19)) {
0867                 list << i18nc("Mouse Button", "extra 19");
0868             }
0869             if (buttons.testFlag(Qt::ExtraButton20)) {
0870                 list << i18nc("Mouse Button", "extra 20");
0871             }
0872             if (buttons.testFlag(Qt::ExtraButton21)) {
0873                 list << i18nc("Mouse Button", "extra 21");
0874             }
0875             if (buttons.testFlag(Qt::ExtraButton22)) {
0876                 list << i18nc("Mouse Button", "extra 22");
0877             }
0878             if (buttons.testFlag(Qt::ExtraButton23)) {
0879                 list << i18nc("Mouse Button", "extra 23");
0880             }
0881             if (buttons.testFlag(Qt::ExtraButton24)) {
0882                 list << i18nc("Mouse Button", "extra 24");
0883             }
0884             if (buttons.testFlag(Qt::TaskButton)) {
0885                 list << i18nc("Mouse Button", "task");
0886             }
0887             return list.join(QStringLiteral(", "));
0888         }
0889         break;
0890     }
0891     return QStyledItemDelegate::displayText(value, locale);
0892 }
0893 
0894 static const int s_x11WindowId = 1;
0895 static const int s_x11UnmanagedId = 2;
0896 static const int s_waylandWindowId = 3;
0897 static const int s_workspaceInternalId = 4;
0898 static const quint32 s_propertyBitMask = 0xFFFF0000;
0899 static const quint32 s_windowBitMask = 0x0000FFFF;
0900 static const quint32 s_idDistance = 10000;
0901 
0902 template<class T>
0903 void DebugConsoleModel::add(int parentRow, QList<T *> &windows, T *window)
0904 {
0905     beginInsertRows(index(parentRow, 0, QModelIndex()), windows.count(), windows.count());
0906     windows.append(window);
0907     endInsertRows();
0908 }
0909 
0910 template<class T>
0911 void DebugConsoleModel::remove(int parentRow, QList<T *> &windows, T *window)
0912 {
0913     const int remove = windows.indexOf(window);
0914     if (remove == -1) {
0915         return;
0916     }
0917     beginRemoveRows(index(parentRow, 0, QModelIndex()), remove, remove);
0918     windows.removeAt(remove);
0919     endRemoveRows();
0920 }
0921 
0922 DebugConsoleModel::DebugConsoleModel(QObject *parent)
0923     : QAbstractItemModel(parent)
0924 {
0925     const auto windows = workspace()->windows();
0926     for (auto window : windows) {
0927         handleWindowAdded(window);
0928     }
0929     connect(workspace(), &Workspace::windowAdded, this, &DebugConsoleModel::handleWindowAdded);
0930     connect(workspace(), &Workspace::windowRemoved, this, &DebugConsoleModel::handleWindowRemoved);
0931 }
0932 
0933 void DebugConsoleModel::handleWindowAdded(Window *window)
0934 {
0935     if (auto x11 = qobject_cast<X11Window *>(window)) {
0936         if (x11->isUnmanaged()) {
0937             add(s_x11UnmanagedId - 1, m_unmanageds, x11);
0938         } else {
0939             add(s_x11WindowId - 1, m_x11Windows, x11);
0940         }
0941         return;
0942     }
0943 
0944     if (auto wayland = qobject_cast<WaylandWindow *>(window)) {
0945         add(s_waylandWindowId - 1, m_waylandWindows, wayland);
0946         return;
0947     }
0948 
0949     if (auto internal = qobject_cast<InternalWindow *>(window)) {
0950         add(s_workspaceInternalId - 1, m_internalWindows, internal);
0951         return;
0952     }
0953 }
0954 
0955 void DebugConsoleModel::handleWindowRemoved(Window *window)
0956 {
0957     if (auto x11 = qobject_cast<X11Window *>(window)) {
0958         if (x11->isUnmanaged()) {
0959             remove(s_x11UnmanagedId - 1, m_unmanageds, x11);
0960         } else {
0961             remove(s_x11WindowId - 1, m_x11Windows, x11);
0962         }
0963         return;
0964     }
0965 
0966     if (auto wayland = qobject_cast<WaylandWindow *>(window)) {
0967         remove(s_waylandWindowId - 1, m_waylandWindows, wayland);
0968         return;
0969     }
0970 
0971     if (auto internal = qobject_cast<InternalWindow *>(window)) {
0972         remove(s_workspaceInternalId - 1, m_internalWindows, internal);
0973         return;
0974     }
0975 }
0976 
0977 DebugConsoleModel::~DebugConsoleModel() = default;
0978 
0979 int DebugConsoleModel::columnCount(const QModelIndex &parent) const
0980 {
0981     return 2;
0982 }
0983 
0984 int DebugConsoleModel::topLevelRowCount() const
0985 {
0986     return kwinApp()->shouldUseWaylandForCompositing() ? 4 : 2;
0987 }
0988 
0989 template<class T>
0990 int DebugConsoleModel::propertyCount(const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex &) const) const
0991 {
0992     if (T *t = (this->*filter)(parent)) {
0993         return t->metaObject()->propertyCount();
0994     }
0995     return 0;
0996 }
0997 
0998 int DebugConsoleModel::rowCount(const QModelIndex &parent) const
0999 {
1000     if (!parent.isValid()) {
1001         return topLevelRowCount();
1002     }
1003 
1004     switch (parent.internalId()) {
1005     case s_x11WindowId:
1006         return m_x11Windows.count();
1007     case s_x11UnmanagedId:
1008         return m_unmanageds.count();
1009     case s_waylandWindowId:
1010         return m_waylandWindows.count();
1011     case s_workspaceInternalId:
1012         return m_internalWindows.count();
1013     default:
1014         break;
1015     }
1016 
1017     if (parent.internalId() & s_propertyBitMask) {
1018         // properties do not have children
1019         return 0;
1020     }
1021 
1022     if (parent.internalId() < s_idDistance * (s_x11WindowId + 1)) {
1023         return propertyCount(parent, &DebugConsoleModel::x11Window);
1024     } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) {
1025         return propertyCount(parent, &DebugConsoleModel::unmanaged);
1026     } else if (parent.internalId() < s_idDistance * (s_waylandWindowId + 1)) {
1027         return propertyCount(parent, &DebugConsoleModel::waylandWindow);
1028     } else if (parent.internalId() < s_idDistance * (s_workspaceInternalId + 1)) {
1029         return propertyCount(parent, &DebugConsoleModel::internalWindow);
1030     }
1031 
1032     return 0;
1033 }
1034 
1035 template<class T>
1036 QModelIndex DebugConsoleModel::indexForWindow(int row, int column, const QList<T *> &windows, int id) const
1037 {
1038     if (column != 0) {
1039         return QModelIndex();
1040     }
1041     if (row >= windows.count()) {
1042         return QModelIndex();
1043     }
1044     return createIndex(row, column, s_idDistance * id + row);
1045 }
1046 
1047 template<class T>
1048 QModelIndex DebugConsoleModel::indexForProperty(int row, int column, const QModelIndex &parent, T *(DebugConsoleModel::*filter)(const QModelIndex &) const) const
1049 {
1050     if (T *t = (this->*filter)(parent)) {
1051         if (row >= t->metaObject()->propertyCount()) {
1052             return QModelIndex();
1053         }
1054         return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId());
1055     }
1056     return QModelIndex();
1057 }
1058 
1059 QModelIndex DebugConsoleModel::index(int row, int column, const QModelIndex &parent) const
1060 {
1061     if (!parent.isValid()) {
1062         // index for a top level item
1063         if (column != 0 || row >= topLevelRowCount()) {
1064             return QModelIndex();
1065         }
1066         return createIndex(row, column, row + 1);
1067     }
1068     if (column >= 2) {
1069         // max of 2 columns
1070         return QModelIndex();
1071     }
1072     // index for a window (second level)
1073     switch (parent.internalId()) {
1074     case s_x11WindowId:
1075         return indexForWindow(row, column, m_x11Windows, s_x11WindowId);
1076     case s_x11UnmanagedId:
1077         return indexForWindow(row, column, m_unmanageds, s_x11UnmanagedId);
1078     case s_waylandWindowId:
1079         return indexForWindow(row, column, m_waylandWindows, s_waylandWindowId);
1080     case s_workspaceInternalId:
1081         return indexForWindow(row, column, m_internalWindows, s_workspaceInternalId);
1082     default:
1083         break;
1084     }
1085 
1086     // index for a property (third level)
1087     if (parent.internalId() < s_idDistance * (s_x11WindowId + 1)) {
1088         return indexForProperty(row, column, parent, &DebugConsoleModel::x11Window);
1089     } else if (parent.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) {
1090         return indexForProperty(row, column, parent, &DebugConsoleModel::unmanaged);
1091     } else if (parent.internalId() < s_idDistance * (s_waylandWindowId + 1)) {
1092         return indexForProperty(row, column, parent, &DebugConsoleModel::waylandWindow);
1093     } else if (parent.internalId() < s_idDistance * (s_workspaceInternalId + 1)) {
1094         return indexForProperty(row, column, parent, &DebugConsoleModel::internalWindow);
1095     }
1096 
1097     return QModelIndex();
1098 }
1099 
1100 QModelIndex DebugConsoleModel::parent(const QModelIndex &child) const
1101 {
1102     if (child.internalId() <= s_workspaceInternalId) {
1103         return QModelIndex();
1104     }
1105     if (child.internalId() & s_propertyBitMask) {
1106         // a property
1107         const quint32 parentId = child.internalId() & s_windowBitMask;
1108         if (parentId < s_idDistance * (s_x11WindowId + 1)) {
1109             return createIndex(parentId - (s_idDistance * s_x11WindowId), 0, parentId);
1110         } else if (parentId < s_idDistance * (s_x11UnmanagedId + 1)) {
1111             return createIndex(parentId - (s_idDistance * s_x11UnmanagedId), 0, parentId);
1112         } else if (parentId < s_idDistance * (s_waylandWindowId + 1)) {
1113             return createIndex(parentId - (s_idDistance * s_waylandWindowId), 0, parentId);
1114         } else if (parentId < s_idDistance * (s_workspaceInternalId + 1)) {
1115             return createIndex(parentId - (s_idDistance * s_workspaceInternalId), 0, parentId);
1116         }
1117         return QModelIndex();
1118     }
1119     if (child.internalId() < s_idDistance * (s_x11WindowId + 1)) {
1120         return createIndex(s_x11WindowId - 1, 0, s_x11WindowId);
1121     } else if (child.internalId() < s_idDistance * (s_x11UnmanagedId + 1)) {
1122         return createIndex(s_x11UnmanagedId - 1, 0, s_x11UnmanagedId);
1123     } else if (child.internalId() < s_idDistance * (s_waylandWindowId + 1)) {
1124         return createIndex(s_waylandWindowId - 1, 0, s_waylandWindowId);
1125     } else if (child.internalId() < s_idDistance * (s_workspaceInternalId + 1)) {
1126         return createIndex(s_workspaceInternalId - 1, 0, s_workspaceInternalId);
1127     }
1128     return QModelIndex();
1129 }
1130 
1131 QVariant DebugConsoleModel::propertyData(QObject *object, const QModelIndex &index, int role) const
1132 {
1133     const auto property = object->metaObject()->property(index.row());
1134     if (index.column() == 0) {
1135         return property.name();
1136     } else {
1137         const QVariant value = property.read(object);
1138         if (qstrcmp(property.name(), "windowType") == 0) {
1139             switch (value.toInt()) {
1140             case NET::Normal:
1141                 return QStringLiteral("NET::Normal");
1142             case NET::Desktop:
1143                 return QStringLiteral("NET::Desktop");
1144             case NET::Dock:
1145                 return QStringLiteral("NET::Dock");
1146             case NET::Toolbar:
1147                 return QStringLiteral("NET::Toolbar");
1148             case NET::Menu:
1149                 return QStringLiteral("NET::Menu");
1150             case NET::Dialog:
1151                 return QStringLiteral("NET::Dialog");
1152             case NET::Override:
1153                 return QStringLiteral("NET::Override");
1154             case NET::TopMenu:
1155                 return QStringLiteral("NET::TopMenu");
1156             case NET::Utility:
1157                 return QStringLiteral("NET::Utility");
1158             case NET::Splash:
1159                 return QStringLiteral("NET::Splash");
1160             case NET::DropdownMenu:
1161                 return QStringLiteral("NET::DropdownMenu");
1162             case NET::PopupMenu:
1163                 return QStringLiteral("NET::PopupMenu");
1164             case NET::Tooltip:
1165                 return QStringLiteral("NET::Tooltip");
1166             case NET::Notification:
1167                 return QStringLiteral("NET::Notification");
1168             case NET::ComboBox:
1169                 return QStringLiteral("NET::ComboBox");
1170             case NET::DNDIcon:
1171                 return QStringLiteral("NET::DNDIcon");
1172             case NET::OnScreenDisplay:
1173                 return QStringLiteral("NET::OnScreenDisplay");
1174             case NET::CriticalNotification:
1175                 return QStringLiteral("NET::CriticalNotification");
1176             case NET::AppletPopup:
1177                 return QStringLiteral("NET::AppletPopup");
1178             case NET::Unknown:
1179             default:
1180                 return QStringLiteral("NET::Unknown");
1181             }
1182         } else if (qstrcmp(property.name(), "layer") == 0) {
1183             return QMetaEnum::fromType<Layer>().valueToKey(value.value<Layer>());
1184         }
1185         return value;
1186     }
1187     return QVariant();
1188 }
1189 
1190 template<class T>
1191 QVariant DebugConsoleModel::windowData(const QModelIndex &index, int role, const QList<T *> windows, const std::function<QString(T *)> &toString) const
1192 {
1193     if (index.row() >= windows.count()) {
1194         return QVariant();
1195     }
1196     auto c = windows.at(index.row());
1197     if (role == Qt::DisplayRole) {
1198         return toString(c);
1199     } else if (role == Qt::DecorationRole) {
1200         return c->icon();
1201     }
1202     return QVariant();
1203 }
1204 
1205 QVariant DebugConsoleModel::data(const QModelIndex &index, int role) const
1206 {
1207     if (!index.isValid()) {
1208         return QVariant();
1209     }
1210     if (!index.parent().isValid()) {
1211         // one of the top levels
1212         if (index.column() != 0 || role != Qt::DisplayRole) {
1213             return QVariant();
1214         }
1215         switch (index.internalId()) {
1216         case s_x11WindowId:
1217             return i18n("X11 Windows");
1218         case s_x11UnmanagedId:
1219             return i18n("X11 Unmanaged Windows");
1220         case s_waylandWindowId:
1221             return i18n("Wayland Windows");
1222         case s_workspaceInternalId:
1223             return i18n("Internal Windows");
1224         default:
1225             return QVariant();
1226         }
1227     }
1228     if (index.internalId() & s_propertyBitMask) {
1229         if (index.column() >= 2 || role != Qt::DisplayRole) {
1230             return QVariant();
1231         }
1232         if (Window *w = waylandWindow(index)) {
1233             return propertyData(w, index, role);
1234         } else if (InternalWindow *w = internalWindow(index)) {
1235             return propertyData(w, index, role);
1236         } else if (X11Window *w = x11Window(index)) {
1237             return propertyData(w, index, role);
1238         } else if (X11Window *u = unmanaged(index)) {
1239             return propertyData(u, index, role);
1240         }
1241     } else {
1242         if (index.column() != 0) {
1243             return QVariant();
1244         }
1245 
1246         auto generic = [](Window *c) -> QString {
1247             return c->caption() + QLatin1Char(' ') + QString::fromUtf8(c->metaObject()->className());
1248         };
1249         switch (index.parent().internalId()) {
1250         case s_x11WindowId:
1251             return windowData<X11Window>(index, role, m_x11Windows, [](X11Window *c) -> QString {
1252                 return QStringLiteral("0x%1: %2").arg(c->window(), 0, 16).arg(c->caption());
1253             });
1254         case s_x11UnmanagedId: {
1255             if (index.row() >= m_unmanageds.count()) {
1256                 return QVariant();
1257             }
1258             auto u = m_unmanageds.at(index.row());
1259             if (role == Qt::DisplayRole) {
1260                 return QStringLiteral("0x%1").arg(u->window(), 0, 16);
1261             }
1262             break;
1263         }
1264         case s_waylandWindowId:
1265             return windowData<WaylandWindow>(index, role, m_waylandWindows, generic);
1266         case s_workspaceInternalId:
1267             return windowData<InternalWindow>(index, role, m_internalWindows, generic);
1268         default:
1269             break;
1270         }
1271     }
1272 
1273     return QVariant();
1274 }
1275 
1276 template<class T>
1277 static T *windowForIndex(const QModelIndex &index, const QList<T *> &windows, int id)
1278 {
1279     const qint32 row = (index.internalId() & s_windowBitMask) - (s_idDistance * id);
1280     if (row < 0 || row >= windows.count()) {
1281         return nullptr;
1282     }
1283     return windows.at(row);
1284 }
1285 
1286 WaylandWindow *DebugConsoleModel::waylandWindow(const QModelIndex &index) const
1287 {
1288     return windowForIndex(index, m_waylandWindows, s_waylandWindowId);
1289 }
1290 
1291 InternalWindow *DebugConsoleModel::internalWindow(const QModelIndex &index) const
1292 {
1293     return windowForIndex(index, m_internalWindows, s_workspaceInternalId);
1294 }
1295 
1296 X11Window *DebugConsoleModel::x11Window(const QModelIndex &index) const
1297 {
1298     return windowForIndex(index, m_x11Windows, s_x11WindowId);
1299 }
1300 
1301 X11Window *DebugConsoleModel::unmanaged(const QModelIndex &index) const
1302 {
1303     return windowForIndex(index, m_unmanageds, s_x11UnmanagedId);
1304 }
1305 
1306 /////////////////////////////////////// SurfaceTreeModel
1307 SurfaceTreeModel::SurfaceTreeModel(QObject *parent)
1308     : QAbstractItemModel(parent)
1309 {
1310     // TODO: it would be nice to not have to reset the model on each change
1311     auto reset = [this] {
1312         beginResetModel();
1313         endResetModel();
1314     };
1315 
1316     auto watchSubsurfaces = [this, reset](Window *c) {
1317         if (!c->surface()) {
1318             return;
1319         }
1320         auto monitor = new SubSurfaceMonitor(c->surface(), this);
1321         connect(monitor, &SubSurfaceMonitor::subSurfaceAdded, this, reset);
1322         connect(monitor, &SubSurfaceMonitor::subSurfaceRemoved, this, reset);
1323         connect(c, &QObject::destroyed, monitor, &QObject::deleteLater);
1324     };
1325 
1326     for (auto c : workspace()->windows()) {
1327         watchSubsurfaces(c);
1328     }
1329     connect(workspace(), &Workspace::windowAdded, this, [reset, watchSubsurfaces](Window *c) {
1330         watchSubsurfaces(c);
1331         reset();
1332     });
1333     connect(workspace(), &Workspace::windowRemoved, this, reset);
1334 }
1335 
1336 SurfaceTreeModel::~SurfaceTreeModel() = default;
1337 
1338 int SurfaceTreeModel::columnCount(const QModelIndex &parent) const
1339 {
1340     return 1;
1341 }
1342 
1343 int SurfaceTreeModel::rowCount(const QModelIndex &parent) const
1344 {
1345     if (parent.isValid()) {
1346         if (SurfaceInterface *surface = static_cast<SurfaceInterface *>(parent.internalPointer())) {
1347             return surface->below().count() + surface->above().count();
1348         }
1349         return 0;
1350     }
1351     // toplevel are all windows
1352     return workspace()->windows().count();
1353 }
1354 
1355 QModelIndex SurfaceTreeModel::index(int row, int column, const QModelIndex &parent) const
1356 {
1357     if (column != 0) {
1358         // invalid column
1359         return QModelIndex();
1360     }
1361 
1362     if (parent.isValid()) {
1363         if (SurfaceInterface *surface = static_cast<SurfaceInterface *>(parent.internalPointer())) {
1364             int reference = 0;
1365             const auto &below = surface->below();
1366             if (row < reference + below.count()) {
1367                 return createIndex(row, column, below.at(row - reference)->surface());
1368             }
1369             reference += below.count();
1370 
1371             const auto &above = surface->above();
1372             if (row < reference + above.count()) {
1373                 return createIndex(row, column, above.at(row - reference)->surface());
1374             }
1375         }
1376         return QModelIndex();
1377     }
1378     // a window
1379     const auto &allClients = workspace()->windows();
1380     if (row < allClients.count()) {
1381         // references a client
1382         return createIndex(row, column, allClients.at(row)->surface());
1383     }
1384     // not found
1385     return QModelIndex();
1386 }
1387 
1388 QModelIndex SurfaceTreeModel::parent(const QModelIndex &child) const
1389 {
1390     if (SurfaceInterface *surface = static_cast<SurfaceInterface *>(child.internalPointer())) {
1391         const auto &subsurface = surface->subSurface();
1392         if (!subsurface) {
1393             // doesn't reference a subsurface, this is a top-level window
1394             return QModelIndex();
1395         }
1396         SurfaceInterface *parent = subsurface->parentSurface();
1397         if (!parent) {
1398             // something is wrong
1399             return QModelIndex();
1400         }
1401         // is the parent a subsurface itself?
1402         if (parent->subSurface()) {
1403             auto grandParent = parent->subSurface()->parentSurface();
1404             if (!grandParent) {
1405                 // something is wrong
1406                 return QModelIndex();
1407             }
1408             int row = 0;
1409             const auto &below = grandParent->below();
1410             for (int i = 0; i < below.count(); i++) {
1411                 if (below.at(i) == parent->subSurface()) {
1412                     return createIndex(row + i, 0, parent);
1413                 }
1414             }
1415             row += below.count();
1416             const auto &above = grandParent->above();
1417             for (int i = 0; i < above.count(); i++) {
1418                 if (above.at(i) == parent->subSurface()) {
1419                     return createIndex(row + i, 0, parent);
1420                 }
1421             }
1422             return QModelIndex();
1423         }
1424         // not a subsurface, thus it's a true window
1425         const auto &allClients = workspace()->windows();
1426         for (int row = 0; row < allClients.count(); row++) {
1427             if (allClients.at(row)->surface() == parent) {
1428                 return createIndex(row, 0, parent);
1429             }
1430         }
1431     }
1432     return QModelIndex();
1433 }
1434 
1435 QVariant SurfaceTreeModel::data(const QModelIndex &index, int role) const
1436 {
1437     if (!index.isValid()) {
1438         return QVariant();
1439     }
1440     if (SurfaceInterface *surface = static_cast<SurfaceInterface *>(index.internalPointer())) {
1441         if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
1442             return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath()).arg(surface->client()->processId()).arg(surface->id());
1443         } else if (role == Qt::DecorationRole) {
1444             if (surface->buffer()) {
1445                 const GraphicsBufferView view(surface->buffer());
1446                 if (const QImage *image = view.image()) {
1447                     return image->scaled(QSize(64, 64), Qt::KeepAspectRatio);
1448                 }
1449             }
1450             return QImage();
1451         }
1452     }
1453     return QVariant();
1454 }
1455 
1456 InputDeviceModel::InputDeviceModel(QObject *parent)
1457     : QAbstractItemModel(parent)
1458     , m_devices(input()->devices())
1459 {
1460     for (auto it = m_devices.constBegin(); it != m_devices.constEnd(); ++it) {
1461         setupDeviceConnections(*it);
1462     }
1463 
1464     connect(input(), &InputRedirection::deviceAdded, this, [this](InputDevice *d) {
1465         beginInsertRows(QModelIndex(), m_devices.count(), m_devices.count());
1466         m_devices << d;
1467         setupDeviceConnections(d);
1468         endInsertRows();
1469     });
1470     connect(input(), &InputRedirection::deviceRemoved, this, [this](InputDevice *d) {
1471         const int index = m_devices.indexOf(d);
1472         if (index == -1) {
1473             return;
1474         }
1475         beginRemoveRows(QModelIndex(), index, index);
1476         m_devices.removeAt(index);
1477         endRemoveRows();
1478     });
1479 }
1480 
1481 InputDeviceModel::~InputDeviceModel() = default;
1482 
1483 int InputDeviceModel::columnCount(const QModelIndex &parent) const
1484 {
1485     return 2;
1486 }
1487 
1488 QVariant InputDeviceModel::data(const QModelIndex &index, int role) const
1489 {
1490     if (!index.isValid()) {
1491         return QVariant();
1492     }
1493     if (!index.parent().isValid() && index.column() == 0) {
1494         if (index.row() >= m_devices.count()) {
1495             return QVariant();
1496         }
1497         if (role == Qt::DisplayRole) {
1498             return m_devices.at(index.row())->name();
1499         }
1500     }
1501     if (index.parent().isValid()) {
1502         if (role == Qt::DisplayRole) {
1503             const auto device = m_devices.at(index.parent().row());
1504             const auto property = device->metaObject()->property(index.row());
1505             if (index.column() == 0) {
1506                 return property.name();
1507             } else if (index.column() == 1) {
1508                 return device->property(property.name());
1509             }
1510         }
1511     }
1512     return QVariant();
1513 }
1514 
1515 QModelIndex InputDeviceModel::index(int row, int column, const QModelIndex &parent) const
1516 {
1517     if (column >= 2) {
1518         return QModelIndex();
1519     }
1520     if (parent.isValid()) {
1521         if (parent.internalId() & s_propertyBitMask) {
1522             return QModelIndex();
1523         }
1524         if (row >= m_devices.at(parent.row())->metaObject()->propertyCount()) {
1525             return QModelIndex();
1526         }
1527         return createIndex(row, column, quint32(row + 1) << 16 | parent.internalId());
1528     }
1529     if (row >= m_devices.count()) {
1530         return QModelIndex();
1531     }
1532     return createIndex(row, column, row + 1);
1533 }
1534 
1535 int InputDeviceModel::rowCount(const QModelIndex &parent) const
1536 {
1537     if (!parent.isValid()) {
1538         return m_devices.count();
1539     }
1540     if (parent.internalId() & s_propertyBitMask) {
1541         return 0;
1542     }
1543 
1544     return m_devices.at(parent.row())->metaObject()->propertyCount();
1545 }
1546 
1547 QModelIndex InputDeviceModel::parent(const QModelIndex &child) const
1548 {
1549     if (child.internalId() & s_propertyBitMask) {
1550         const quintptr parentId = child.internalId() & s_windowBitMask;
1551         return createIndex(parentId - 1, 0, parentId);
1552     }
1553     return QModelIndex();
1554 }
1555 
1556 void InputDeviceModel::slotPropertyChanged()
1557 {
1558     const auto device = static_cast<InputDevice *>(sender());
1559 
1560     for (int i = 0; i < device->metaObject()->propertyCount(); ++i) {
1561         const QMetaProperty metaProperty = device->metaObject()->property(i);
1562         if (metaProperty.notifySignalIndex() == senderSignalIndex()) {
1563             const QModelIndex parent = index(m_devices.indexOf(device), 0, QModelIndex());
1564             const QModelIndex child = index(i, 1, parent);
1565             Q_EMIT dataChanged(child, child, QList<int>{Qt::DisplayRole});
1566         }
1567     }
1568 }
1569 
1570 void InputDeviceModel::setupDeviceConnections(InputDevice *device)
1571 {
1572     QMetaMethod handler = metaObject()->method(metaObject()->indexOfMethod("slotPropertyChanged()"));
1573     for (int i = 0; i < device->metaObject()->propertyCount(); ++i) {
1574         const QMetaProperty metaProperty = device->metaObject()->property(i);
1575         if (metaProperty.hasNotifySignal()) {
1576             connect(device, metaProperty.notifySignal(), this, handler);
1577         }
1578     }
1579 }
1580 
1581 QModelIndex DataSourceModel::index(int row, int column, const QModelIndex &parent) const
1582 {
1583     if (!m_source || parent.isValid() || column >= 2 || row >= m_source->mimeTypes().size()) {
1584         return QModelIndex();
1585     }
1586     return createIndex(row, column, nullptr);
1587 }
1588 
1589 QModelIndex DataSourceModel::parent(const QModelIndex &child) const
1590 {
1591     return QModelIndex();
1592 }
1593 
1594 int DataSourceModel::rowCount(const QModelIndex &parent) const
1595 {
1596     if (!parent.isValid()) {
1597         return m_source ? m_source->mimeTypes().count() : 0;
1598     }
1599     return 0;
1600 }
1601 
1602 QVariant DataSourceModel::headerData(int section, Qt::Orientation orientation, int role) const
1603 {
1604     if (role != Qt::DisplayRole || orientation != Qt::Horizontal || section >= 2) {
1605         return QVariant();
1606     }
1607     return section == 0 ? QStringLiteral("Mime type") : QStringLiteral("Content");
1608 }
1609 
1610 QVariant DataSourceModel::data(const QModelIndex &index, int role) const
1611 {
1612     if (!checkIndex(index, CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) {
1613         return QVariant();
1614     }
1615     const QString mimeType = m_source->mimeTypes().at(index.row());
1616     ;
1617     if (index.column() == 0 && role == Qt::DisplayRole) {
1618         return mimeType;
1619     } else if (index.column() == 1 && index.row() < m_data.count()) {
1620         const QByteArray &data = m_data.at(index.row());
1621         if (mimeType.contains(QLatin1String("image"))) {
1622             if (role == Qt::DecorationRole) {
1623                 return QImage::fromData(data);
1624             }
1625         } else if (role == Qt::DisplayRole) {
1626             return data;
1627         }
1628     }
1629     return QVariant();
1630 }
1631 
1632 static QByteArray readData(int fd)
1633 {
1634     pollfd pfd;
1635     pfd.fd = fd;
1636     pfd.events = POLLIN;
1637     FileDescriptor closeFd{fd};
1638     QByteArray data;
1639     while (true) {
1640         const int ready = poll(&pfd, 1, 1000);
1641         if (ready < 0) {
1642             if (errno != EINTR) {
1643                 return QByteArrayLiteral("poll() failed: ") + strerror(errno);
1644             }
1645         } else if (ready == 0) {
1646             return QByteArrayLiteral("timeout reading from pipe");
1647         } else {
1648             char buf[4096];
1649             int n = read(fd, buf, sizeof buf);
1650 
1651             if (n < 0) {
1652                 return QByteArrayLiteral("read failed: ") + strerror(errno);
1653             } else if (n == 0) {
1654                 return data;
1655             } else if (n > 0) {
1656                 data.append(buf, n);
1657             }
1658         }
1659     }
1660 }
1661 
1662 void DataSourceModel::setSource(AbstractDataSource *source)
1663 {
1664     beginResetModel();
1665     m_source = source;
1666     m_data.clear();
1667     if (source) {
1668         const QStringList mimeTypes = m_source->mimeTypes();
1669         m_data.resize(mimeTypes.size());
1670         for (auto type = mimeTypes.begin(); type != mimeTypes.end(); ++type) {
1671             int pipeFds[2];
1672             if (pipe2(pipeFds, O_CLOEXEC) != 0) {
1673                 continue;
1674             }
1675             source->requestData(*type, pipeFds[1]);
1676             QFuture<QByteArray> data = QtConcurrent::run(readData, pipeFds[0]);
1677             auto watcher = new QFutureWatcher<QByteArray>(this);
1678             watcher->setFuture(data);
1679             const int index = type - mimeTypes.begin();
1680             connect(watcher, &QFutureWatcher<QByteArray>::finished, this, [this, watcher, index, source = QPointer(source)] {
1681                 watcher->deleteLater();
1682                 if (source && source == m_source) {
1683                     m_data[index] = watcher->result();
1684                     Q_EMIT dataChanged(this->index(index, 1), this->index(index, 1), {Qt::DecorationRole | Qt::DisplayRole});
1685                 }
1686             });
1687         }
1688     }
1689     endResetModel();
1690 }
1691 }
1692 
1693 #include "moc_debug_console.cpp"