From 5435865329e75d5fdfe89aa2e80ddf6d45d33f15 Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Mon, 2 Aug 2021 16:21:26 +0300 Subject: [PATCH] scripting: Introduce ClientModel V3 ClientModel V3 is much easier to extend and maintain than the one in V2. --- src/CMakeLists.txt | 1 + src/scripting/scripting.cpp | 7 + src/scripting/v3/clientmodel.cpp | 314 +++++++++++++++++++++++++++++++ src/scripting/v3/clientmodel.h | 118 ++++++++++++ 4 files changed, 440 insertions(+) create mode 100644 src/scripting/v3/clientmodel.cpp create mode 100644 src/scripting/v3/clientmodel.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 72d6805bfa..4fa57bede0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -100,6 +100,7 @@ set(kwin_SRCS screenlockerwatcher.cpp screens.cpp scripting/v2/clientmodel.cpp + scripting/v3/clientmodel.cpp scripting/dbuscall.cpp scripting/screenedgeitem.cpp scripting/scriptedeffect.cpp diff --git a/src/scripting/scripting.cpp b/src/scripting/scripting.cpp index 4162f4a699..7fe3077c4f 100644 --- a/src/scripting/scripting.cpp +++ b/src/scripting/scripting.cpp @@ -19,6 +19,7 @@ #include "thumbnailitem.h" #include "v2/clientmodel.h" +#include "v3/clientmodel.h" #include "input.h" #include "options.h" @@ -642,6 +643,12 @@ void KWin::Scripting::init() qmlRegisterType("org.kde.kwin", 2, 1, "ClientModelByScreenAndActivity"); qmlRegisterType("org.kde.kwin", 2, 0, "ClientFilterModel"); + qmlRegisterType("org.kde.kwin", 3, 0, "WindowThumbnailItem"); + qmlRegisterType("org.kde.kwin", 3, 0, "DBusCall"); + qmlRegisterType("org.kde.kwin", 3, 0, "ScreenEdgeItem"); + qmlRegisterType("org.kde.kwin", 3, 0, "ClientModel"); + qmlRegisterType("org.kde.kwin", 3, 0, "ClientFilterModel"); + qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); diff --git a/src/scripting/v3/clientmodel.cpp b/src/scripting/v3/clientmodel.cpp new file mode 100644 index 0000000000..90a0d2e22d --- /dev/null +++ b/src/scripting/v3/clientmodel.cpp @@ -0,0 +1,314 @@ +/* + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "clientmodel.h" +#include "abstract_client.h" +#include "screens.h" +#include "workspace.h" + +namespace KWin::ScriptingModels::V3 +{ + +ClientModel::ClientModel(QObject *parent) + : QAbstractListModel(parent) +{ + connect(workspace(), &Workspace::clientAdded, this, &ClientModel::handleClientAdded); + connect(workspace(), &Workspace::clientRemoved, this, &ClientModel::handleClientRemoved); + + m_clients = workspace()->allClientList(); + for (AbstractClient *client : qAsConst(m_clients)) { + setupClientConnections(client); + } +} + +void ClientModel::markRoleChanged(AbstractClient *client, int role) +{ + const QModelIndex row = index(m_clients.indexOf(client), 0); + Q_EMIT dataChanged(row, row, {role}); +} + +void ClientModel::setupClientConnections(AbstractClient *client) +{ + connect(client, &AbstractClient::desktopChanged, this, [this, client]() { + markRoleChanged(client, DesktopRole); + }); + connect(client, &AbstractClient::screenChanged, this, [this, client]() { + markRoleChanged(client, ScreenRole); + }); + connect(client, &AbstractClient::activitiesChanged, this, [this, client]() { + markRoleChanged(client, ActivityRole); + }); +} + +void ClientModel::handleClientAdded(AbstractClient *client) +{ + beginInsertRows(QModelIndex(), m_clients.count(), m_clients.count()); + m_clients.append(client); + endInsertRows(); + + setupClientConnections(client); +} + +void ClientModel::handleClientRemoved(AbstractClient *client) +{ + const int index = m_clients.indexOf(client); + Q_ASSERT(index != -1); + + beginRemoveRows(QModelIndex(), index, index); + m_clients.removeAt(index); + endRemoveRows(); +} + +QHash ClientModel::roleNames() const +{ + return { + { Qt::DisplayRole, QByteArrayLiteral("display") }, + { ClientRole, QByteArrayLiteral("client") }, + { ScreenRole, QByteArrayLiteral("screen") }, + { DesktopRole, QByteArrayLiteral("desktop") }, + { ActivityRole, QByteArrayLiteral("activity") }, + }; +} + +QVariant ClientModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= m_clients.count()) { + return QVariant(); + } + + AbstractClient *client = m_clients[index.row()]; + switch (role) { + case Qt::DisplayRole: + case ClientRole: + return QVariant::fromValue(client); + case ScreenRole: + return client->screen(); + case DesktopRole: + return client->desktop(); + case ActivityRole: + return client->activities(); + default: + return QVariant(); + } +} + +int ClientModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : m_clients.count(); +} + +ClientFilterModel::ClientFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +ClientModel *ClientFilterModel::clientModel() const +{ + return m_clientModel; +} + +void ClientFilterModel::setClientModel(ClientModel *clientModel) +{ + if (clientModel == m_clientModel) { + return; + } + m_clientModel = clientModel; + setSourceModel(m_clientModel); + Q_EMIT clientModelChanged(); +} + +QString ClientFilterModel::activity() const +{ + return m_activity.value_or(QString()); +} + +void ClientFilterModel::setActivity(const QString &activity) +{ + if (m_activity != activity) { + m_activity = activity; + Q_EMIT activityChanged(); + invalidateFilter(); + } +} + +void ClientFilterModel::resetActivity() +{ + if (m_activity.has_value()) { + m_activity.reset(); + Q_EMIT activityChanged(); + invalidateFilter(); + } +} + +int ClientFilterModel::desktop() const +{ + return m_desktop.value_or(0); +} + +void ClientFilterModel::setDesktop(int desktop) +{ + if (m_desktop != desktop) { + m_desktop = desktop; + Q_EMIT desktopChanged(); + invalidateFilter(); + } +} + +void ClientFilterModel::resetDesktop() +{ + if (m_desktop.has_value()) { + m_desktop.reset(); + Q_EMIT desktopChanged(); + invalidateFilter(); + } +} + +QString ClientFilterModel::filter() const +{ + return m_filter; +} + +void ClientFilterModel::setFilter(const QString &filter) +{ + if (filter == m_filter) { + return; + } + m_filter = filter; + Q_EMIT filterChanged(); + invalidateFilter(); +} + +QString ClientFilterModel::screenName() const +{ + return m_screenName.value_or(QString()); +} + +void ClientFilterModel::setScreenName(const QString &screen) +{ + if (m_screenName != screen) { + m_screenName = screen; + Q_EMIT screenNameChanged(); + invalidateFilter(); + } +} + +void ClientFilterModel::resetScreenName() +{ + if (m_screenName.has_value()) { + m_screenName.reset(); + Q_EMIT screenNameChanged(); + invalidateFilter(); + } +} + +ClientFilterModel::WindowTypes ClientFilterModel::windowType() const +{ + return m_windowType.value_or(WindowTypes()); +} + +void ClientFilterModel::setWindowType(WindowTypes windowType) +{ + if (m_windowType != windowType) { + m_windowType = windowType; + Q_EMIT windowTypeChanged(); + invalidateFilter(); + } +} + +void ClientFilterModel::resetWindowType() +{ + if (m_windowType.has_value()) { + m_windowType.reset(); + Q_EMIT windowTypeChanged(); + invalidateFilter(); + } +} + +bool ClientFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (!m_clientModel) { + return false; + } + const QModelIndex index = m_clientModel->index(sourceRow, 0, sourceParent); + if (!index.isValid()) { + return false; + } + const QVariant data = index.data(); + if (!data.isValid()) { + // an invalid QVariant is valid data + return true; + } + + AbstractClient *client = qvariant_cast(data); + if (!client) { + return false; + } + + if (m_activity.has_value()) { + if (!client->isOnActivity(*m_activity)) { + return false; + } + } + + if (m_desktop.has_value()) { + if (!client->isOnDesktop(*m_desktop)) { + return false; + } + } + + if (m_screenName.has_value()) { + if (screens()->name(client->screen()) != m_screenName) { + return false; + } + } + + if (m_windowType.has_value()) { + if (!(windowTypeMask(client) & *m_windowType)) { + return false; + } + } + + if (!m_filter.isEmpty()) { + if (client->caption().contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + const QString windowRole(QString::fromUtf8(client->windowRole())); + if (windowRole.contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + const QString resourceName(QString::fromUtf8(client->resourceName())); + if (resourceName.contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + const QString resourceClass(QString::fromUtf8(client->resourceClass())); + if (resourceClass.contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + return false; + } + return true; +} + +ClientFilterModel::WindowTypes ClientFilterModel::windowTypeMask(AbstractClient *client) const +{ + WindowTypes mask; + if (client->isNormalWindow()) { + mask |= WindowType::Normal; + } else if (client->isDialog()) { + mask |= WindowType::Dialog; + } else if (client->isDock()) { + mask |= WindowType::Dock; + } else if (client->isDesktop()) { + mask |= WindowType::Desktop; + } else if (client->isNotification()) { + mask |= WindowType::Notification; + } else if (client->isCriticalNotification()) { + mask |= WindowType::CriticalNotification; + } + return mask; +} + +} // namespace KWin::ScriptingModels::V3 diff --git a/src/scripting/v3/clientmodel.h b/src/scripting/v3/clientmodel.h new file mode 100644 index 0000000000..c63ce107e4 --- /dev/null +++ b/src/scripting/v3/clientmodel.h @@ -0,0 +1,118 @@ +/* + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include +#include + +#include + +namespace KWin +{ +class AbstractClient; + +namespace ScriptingModels::V3 +{ + +class ClientModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + ClientRole = Qt::UserRole + 1, + ScreenRole, + DesktopRole, + ActivityRole + }; + + explicit ClientModel(QObject *parent = nullptr); + + QHash roleNames() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + +private: + void markRoleChanged(AbstractClient *client, int role); + + void handleClientAdded(AbstractClient *client); + void handleClientRemoved(AbstractClient *client); + void setupClientConnections(AbstractClient *client); + + QList m_clients; +}; + +class ClientFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(ClientModel *clientModel READ clientModel WRITE setClientModel NOTIFY clientModelChanged) + Q_PROPERTY(QString activity READ activity WRITE setActivity RESET resetActivity NOTIFY activityChanged) + Q_PROPERTY(int desktop READ desktop WRITE setDesktop RESET resetDesktop NOTIFY desktopChanged) + Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) + Q_PROPERTY(QString screenName READ screenName WRITE setScreenName RESET resetScreenName NOTIFY screenNameChanged) + Q_PROPERTY(WindowTypes windowType READ windowType WRITE setWindowType RESET resetWindowType NOTIFY windowTypeChanged) + +public: + enum WindowType { + Normal = 0x1, + Dialog = 0x2, + Dock = 0x4, + Desktop = 0x8, + Notification = 0x10, + CriticalNotification = 0x20, + }; + Q_DECLARE_FLAGS(WindowTypes, WindowType) + Q_FLAG(WindowTypes) + + explicit ClientFilterModel(QObject *parent = nullptr); + + ClientModel *clientModel() const; + void setClientModel(ClientModel *clientModel); + + QString activity() const; + void setActivity(const QString &activity); + void resetActivity(); + + int desktop() const; + void setDesktop(int desktop); + void resetDesktop(); + + QString filter() const; + void setFilter(const QString &filter); + + QString screenName() const; + void setScreenName(const QString &screenName); + void resetScreenName(); + + WindowTypes windowType() const; + void setWindowType(WindowTypes windowType); + void resetWindowType(); + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; + +Q_SIGNALS: + void activityChanged(); + void desktopChanged(); + void screenNameChanged(); + void clientModelChanged(); + void filterChanged(); + void windowTypeChanged(); + +private: + WindowTypes windowTypeMask(AbstractClient *client) const; + + ClientModel *m_clientModel = nullptr; + std::optional m_activity; + std::optional m_desktop; + QString m_filter; + std::optional m_screenName; + std::optional m_windowType; +}; + +} // namespace ScriptingModels::V3 +} // namespace KWin