scripting: Introduce ClientModel V3
ClientModel V3 is much easier to extend and maintain than the one in V2.
This commit is contained in:
parent
cecf2ee7a1
commit
5435865329
4 changed files with 440 additions and 0 deletions
|
@ -100,6 +100,7 @@ set(kwin_SRCS
|
||||||
screenlockerwatcher.cpp
|
screenlockerwatcher.cpp
|
||||||
screens.cpp
|
screens.cpp
|
||||||
scripting/v2/clientmodel.cpp
|
scripting/v2/clientmodel.cpp
|
||||||
|
scripting/v3/clientmodel.cpp
|
||||||
scripting/dbuscall.cpp
|
scripting/dbuscall.cpp
|
||||||
scripting/screenedgeitem.cpp
|
scripting/screenedgeitem.cpp
|
||||||
scripting/scriptedeffect.cpp
|
scripting/scriptedeffect.cpp
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "thumbnailitem.h"
|
#include "thumbnailitem.h"
|
||||||
|
|
||||||
#include "v2/clientmodel.h"
|
#include "v2/clientmodel.h"
|
||||||
|
#include "v3/clientmodel.h"
|
||||||
|
|
||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include "options.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::ClientModelByScreenAndActivity>("org.kde.kwin", 2, 1, "ClientModelByScreenAndActivity");
|
||||||
qmlRegisterType<ScriptingModels::V2::ClientFilterModel>("org.kde.kwin", 2, 0, "ClientFilterModel");
|
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::AbstractClient>();
|
||||||
qmlRegisterType<KWin::X11Client>();
|
qmlRegisterType<KWin::X11Client>();
|
||||||
qmlRegisterType<QAbstractItemModel>();
|
qmlRegisterType<QAbstractItemModel>();
|
||||||
|
|
314
src/scripting/v3/clientmodel.cpp
Normal file
314
src/scripting/v3/clientmodel.cpp
Normal file
|
@ -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
|
118
src/scripting/v3/clientmodel.h
Normal file
118
src/scripting/v3/clientmodel.h
Normal file
|
@ -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…
Reference in a new issue