Add a surface tree to DebugConsole
Summary: While developing support for sub-surfaces it became obvious that there is a need for visualizing the tree of sub-surfaces. The surface-tree is a new mode added in the debug console. There are now two buttons to switch between the default window tree and the surface tree. The surface tree is a little bit more basic than the windows tree. The type of window (whether x11, wayland or internal) is ignored. All windows build up the top level with the sub surfaces as children. Each surface is represented by some basic information about it: * Client (binary path and pid) * internal surface id If the surface has a shared memory buffer mapped a scaled down version is used as the window decoration role. The model gets reset whenever the tree changes in some way as it's rather complex to track correctly and well, it's just a debug console. Currently the tree is not really functional yet as KWin doesn't announce support for sub-compositor protocol, which is also the reason for lack of autotests for the model. Will be added once sub-compositor works properly. Reviewers: #plasma Subscribers: plasma-devel Projects: #plasma Differential Revision: https://phabricator.kde.org/D1205
This commit is contained in:
parent
6e18cae42a
commit
e62dad48a6
3 changed files with 265 additions and 2 deletions
|
@ -28,6 +28,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
#include "ui_debug_console.h"
|
||||
|
||||
// KWayland
|
||||
#include <KWayland/Server/buffer_interface.h>
|
||||
#include <KWayland/Server/clientconnection.h>
|
||||
#include <KWayland/Server/subcompositor_interface.h>
|
||||
#include <KWayland/Server/surface_interface.h>
|
||||
// frameworks
|
||||
#include <KLocalizedString>
|
||||
|
@ -47,8 +50,34 @@ DebugConsole::DebugConsole()
|
|||
m_ui->treeView->setItemDelegate(new DebugConsoleDelegate(this));
|
||||
m_ui->treeView->setModel(new DebugConsoleModel(this));
|
||||
m_ui->quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit")));
|
||||
m_ui->windowsButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
|
||||
m_ui->surfacesButton->setIcon(QIcon::fromTheme(QStringLiteral("view-list-tree")));
|
||||
|
||||
if (kwinApp()->operationMode() == Application::OperationMode::OperationModeX11) {
|
||||
m_ui->surfacesButton->setDisabled(true);
|
||||
}
|
||||
|
||||
connect(m_ui->quitButton, &QAbstractButton::clicked, this, &DebugConsole::deleteLater);
|
||||
connect(m_ui->windowsButton, &QAbstractButton::toggled, this,
|
||||
[this] (bool toggled) {
|
||||
if (!toggled) {
|
||||
return;
|
||||
}
|
||||
m_ui->surfacesButton->setChecked(false);
|
||||
m_ui->treeView->model()->deleteLater();
|
||||
m_ui->treeView->setModel(new DebugConsoleModel(this));
|
||||
}
|
||||
);
|
||||
connect(m_ui->surfacesButton, &QAbstractButton::toggled, this,
|
||||
[this] (bool toggled) {
|
||||
if (!toggled) {
|
||||
return;
|
||||
}
|
||||
m_ui->windowsButton->setChecked(false);
|
||||
m_ui->treeView->model()->deleteLater();
|
||||
m_ui->treeView->setModel(new SurfaceTreeModel(this));
|
||||
}
|
||||
);
|
||||
|
||||
// for X11
|
||||
setWindowFlags(Qt::X11BypassWindowManagerHint);
|
||||
|
@ -514,4 +543,201 @@ Unmanaged *DebugConsoleModel::unmanaged(const QModelIndex &index) const
|
|||
return clientForIndex(index, m_unmanageds, s_x11UnmanagedId);
|
||||
}
|
||||
|
||||
/////////////////////////////////////// SurfaceTreeModel
|
||||
SurfaceTreeModel::SurfaceTreeModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
// TODO: it would be nice to not have to reset the model on each change
|
||||
auto reset = [this] {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
};
|
||||
using namespace KWayland::Server;
|
||||
|
||||
const auto unmangeds = workspace()->unmanagedList();
|
||||
for (auto u : unmangeds) {
|
||||
connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset);
|
||||
}
|
||||
for (auto c : workspace()->allClientList()) {
|
||||
connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset);
|
||||
}
|
||||
for (auto c : workspace()->desktopList()) {
|
||||
connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset);
|
||||
}
|
||||
for (auto c : waylandServer()->internalClients()) {
|
||||
connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset);
|
||||
}
|
||||
connect(waylandServer(), &WaylandServer::shellClientAdded, this,
|
||||
[this, reset] (ShellClient *c) {
|
||||
connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset);
|
||||
reset();
|
||||
}
|
||||
);
|
||||
connect(workspace(), &Workspace::clientAdded, this,
|
||||
[this, reset] (AbstractClient *c) {
|
||||
connect(c->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset);
|
||||
reset();
|
||||
}
|
||||
);
|
||||
connect(workspace(), &Workspace::clientRemoved, this, reset);
|
||||
connect(workspace(), &Workspace::unmanagedAdded, this,
|
||||
[this, reset] (Unmanaged *u) {
|
||||
connect(u->surface(), &SurfaceInterface::subSurfaceTreeChanged, this, reset);
|
||||
reset();
|
||||
}
|
||||
);
|
||||
connect(workspace(), &Workspace::unmanagedRemoved, this, reset);
|
||||
}
|
||||
|
||||
SurfaceTreeModel::~SurfaceTreeModel() = default;
|
||||
|
||||
int SurfaceTreeModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int SurfaceTreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
using namespace KWayland::Server;
|
||||
if (SurfaceInterface *surface = static_cast<SurfaceInterface*>(parent.internalPointer())) {
|
||||
const auto &children = surface->childSubSurfaces();
|
||||
return children.count();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// toplevel are all windows
|
||||
return workspace()->allClientList().count() +
|
||||
workspace()->desktopList().count() +
|
||||
workspace()->unmanagedList().count() +
|
||||
waylandServer()->internalClients().count();
|
||||
}
|
||||
|
||||
QModelIndex SurfaceTreeModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (column != 0) {
|
||||
// invalid column
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
if (parent.isValid()) {
|
||||
using namespace KWayland::Server;
|
||||
if (SurfaceInterface *surface = static_cast<SurfaceInterface*>(parent.internalPointer())) {
|
||||
const auto &children = surface->childSubSurfaces();
|
||||
if (row < children.count()) {
|
||||
return createIndex(row, column, children.at(row)->surface().data());
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
// a window
|
||||
const auto &allClients = workspace()->allClientList();
|
||||
if (row < allClients.count()) {
|
||||
// references a client
|
||||
return createIndex(row, column, allClients.at(row)->surface());
|
||||
}
|
||||
int reference = allClients.count();
|
||||
const auto &desktopClients = workspace()->desktopList();
|
||||
if (row < reference + desktopClients.count()) {
|
||||
return createIndex(row, column, desktopClients.at(row-reference)->surface());
|
||||
}
|
||||
reference += desktopClients.count();
|
||||
const auto &unmanaged = workspace()->unmanagedList();
|
||||
if (row < reference + unmanaged.count()) {
|
||||
return createIndex(row, column, unmanaged.at(row-reference)->surface());
|
||||
}
|
||||
reference += unmanaged.count();
|
||||
const auto &internal = waylandServer()->internalClients();
|
||||
if (row < reference + internal.count()) {
|
||||
return createIndex(row, column, internal.at(row-reference)->surface());
|
||||
}
|
||||
// not found
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex SurfaceTreeModel::parent(const QModelIndex &child) const
|
||||
{
|
||||
using namespace KWayland::Server;
|
||||
if (SurfaceInterface *surface = static_cast<SurfaceInterface*>(child.internalPointer())) {
|
||||
const auto &subsurface = surface->subSurface();
|
||||
if (subsurface.isNull()) {
|
||||
// doesn't reference a subsurface, this is a top-level window
|
||||
return QModelIndex();
|
||||
}
|
||||
SurfaceInterface *parent = subsurface->parentSurface().data();
|
||||
if (!parent) {
|
||||
// something is wrong
|
||||
return QModelIndex();
|
||||
}
|
||||
// is the parent a subsurface itself?
|
||||
if (parent->subSurface()) {
|
||||
auto grandParent = parent->subSurface()->parentSurface();
|
||||
if (grandParent.isNull()) {
|
||||
// something is wrong
|
||||
return QModelIndex();
|
||||
}
|
||||
const auto &children = grandParent->childSubSurfaces();
|
||||
for (int row = 0; row < children.count(); row++) {
|
||||
if (children.at(row).data() == parent->subSurface().data()) {
|
||||
return createIndex(row, 0, parent);
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
// not a subsurface, thus it's a true window
|
||||
int row = 0;
|
||||
const auto &allClients = workspace()->allClientList();
|
||||
for (; row < allClients.count(); row++) {
|
||||
if (allClients.at(row)->surface() == parent) {
|
||||
return createIndex(row, 0, parent);
|
||||
}
|
||||
}
|
||||
row = allClients.count();
|
||||
const auto &desktopClients = workspace()->desktopList();
|
||||
for (int i = 0; i < desktopClients.count(); i++) {
|
||||
if (desktopClients.at(i)->surface() == parent) {
|
||||
return createIndex(row + i, 0, parent);
|
||||
}
|
||||
}
|
||||
row += desktopClients.count();
|
||||
const auto &unmanaged = workspace()->unmanagedList();
|
||||
for (int i = 0; i < unmanaged.count(); i++) {
|
||||
if (unmanaged.at(i)->surface() == parent) {
|
||||
return createIndex(row + i, 0, parent);
|
||||
}
|
||||
}
|
||||
row += unmanaged.count();
|
||||
const auto &internal = waylandServer()->internalClients();
|
||||
for (int i = 0; i < internal.count(); i++) {
|
||||
if (internal.at(i)->surface() == parent) {
|
||||
return createIndex(row + i, 0, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QVariant SurfaceTreeModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
using namespace KWayland::Server;
|
||||
if (SurfaceInterface *surface = static_cast<SurfaceInterface*>(index.internalPointer())) {
|
||||
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
|
||||
return QStringLiteral("%1 (%2) - %3").arg(surface->client()->executablePath())
|
||||
.arg(surface->client()->processId())
|
||||
.arg(surface->id());
|
||||
} else if (role == Qt::DecorationRole) {
|
||||
if (auto buffer = surface->buffer()) {
|
||||
if (buffer->shmBuffer()) {
|
||||
return buffer->data().scaled(QSize(64, 64), Qt::KeepAspectRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -100,6 +100,20 @@ private:
|
|||
QScopedPointer<Ui::DebugConsole> m_ui;
|
||||
};
|
||||
|
||||
class SurfaceTreeModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SurfaceTreeModel(QObject *parent = nullptr);
|
||||
virtual ~SurfaceTreeModel();
|
||||
|
||||
int columnCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex & parent) const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
QModelIndex parent(const QModelIndex &child) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -17,9 +17,25 @@
|
|||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="quitButton">
|
||||
<widget class="QPushButton" name="windowsButton">
|
||||
<property name="text">
|
||||
<string>Quit Debug Console</string>
|
||||
<string>Windows</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="surfacesButton">
|
||||
<property name="text">
|
||||
<string>Surfaces</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -36,6 +52,13 @@
|
|||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="quitButton">
|
||||
<property name="text">
|
||||
<string>Quit Debug Console</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
|
Loading…
Reference in a new issue