scripting: Introduce ClientModel V3

ClientModel V3 is much easier to extend and maintain than the one in V2.
master
Vlad Zahorodnii 3 years ago
parent cecf2ee7a1
commit 5435865329

@ -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

@ -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<ScriptingModels::V2::ClientModelByScreenAndActivity>("org.kde.kwin", 2, 1, "ClientModelByScreenAndActivity");
qmlRegisterType<ScriptingModels::V2::ClientFilterModel>("org.kde.kwin", 2, 0, "ClientFilterModel");
qmlRegisterType<WindowThumbnailItem>("org.kde.kwin", 3, 0, "WindowThumbnailItem");
qmlRegisterType<DBusCall>("org.kde.kwin", 3, 0, "DBusCall");
qmlRegisterType<ScreenEdgeItem>("org.kde.kwin", 3, 0, "ScreenEdgeItem");
qmlRegisterType<ScriptingModels::V3::ClientModel>("org.kde.kwin", 3, 0, "ClientModel");
qmlRegisterType<ScriptingModels::V3::ClientFilterModel>("org.kde.kwin", 3, 0, "ClientFilterModel");
qmlRegisterType<KWin::AbstractClient>();
qmlRegisterType<KWin::X11Client>();
qmlRegisterType<QAbstractItemModel>();

@ -0,0 +1,314 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
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<int, QByteArray> 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<AbstractClient *>(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

@ -0,0 +1,118 @@
/*
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include <optional>
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<int, QByteArray> 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<AbstractClient *> 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<QString> m_activity;
std::optional<int> m_desktop;
QString m_filter;
std::optional<QString> m_screenName;
std::optional<WindowTypes> m_windowType;
};
} // namespace ScriptingModels::V3
} // namespace KWin
Loading…
Cancel
Save