/******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2013 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "scripting_model.h" #include #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif #include "x11client.h" #include "screens.h" #include "workspace.h" #include "xdgshellclient.h" #include "wayland_server.h" namespace KWin { namespace ScriptingClientModel { static quint32 nextId() { static quint32 counter = 0; return ++counter; } ClientLevel::ClientLevel(ClientModel *model, AbstractLevel *parent) : AbstractLevel(model, parent) { connect(Workspace::self(), &Workspace::clientAdded, this, &ClientLevel::clientAdded); connect(Workspace::self(), &Workspace::clientRemoved, this, &ClientLevel::clientRemoved); connect(model, SIGNAL(exclusionsChanged()), SLOT(reInit())); if (waylandServer()) { connect(waylandServer(), &WaylandServer::shellClientAdded, this, &ClientLevel::clientAdded); } } ClientLevel::~ClientLevel() { } void ClientLevel::clientAdded(AbstractClient *client) { setupClientConnections(client); checkClient(client); } void ClientLevel::clientRemoved(AbstractClient *client) { removeClient(client); } void ClientLevel::setupClientConnections(AbstractClient *client) { auto check = [this, client] { checkClient(client); }; connect(client, &AbstractClient::desktopChanged, this, check); connect(client, &AbstractClient::screenChanged, this, check); connect(client, &AbstractClient::activitiesChanged, this, check); connect(client, &AbstractClient::windowHidden, this, check); connect(client, &AbstractClient::windowShown, this, check); } void ClientLevel::checkClient(AbstractClient *client) { const bool shouldInclude = !exclude(client) && shouldAdd(client); const bool contains = containsClient(client); if (shouldInclude && !contains) { addClient(client); } else if (!shouldInclude && contains) { removeClient(client); } } bool ClientLevel::exclude(AbstractClient *client) const { ClientModel::Exclusions exclusions = model()->exclusions(); if (exclusions == ClientModel::NoExclusion) { return false; } if (exclusions & ClientModel::DesktopWindowsExclusion) { if (client->isDesktop()) { return true; } } if (exclusions & ClientModel::DockWindowsExclusion) { if (client->isDock()) { return true; } } if (exclusions & ClientModel::UtilityWindowsExclusion) { if (client->isUtility()) { return true; } } if (exclusions & ClientModel::SpecialWindowsExclusion) { if (client->isSpecialWindow()) { return true; } } if (exclusions & ClientModel::SkipTaskbarExclusion) { if (client->skipTaskbar()) { return true; } } if (exclusions & ClientModel::SkipPagerExclusion) { if (client->skipPager()) { return true; } } if (exclusions & ClientModel::SwitchSwitcherExclusion) { if (client->skipSwitcher()) { return true; } } if (exclusions & ClientModel::OtherDesktopsExclusion) { if (!client->isOnCurrentDesktop()) { return true; } } if (exclusions & ClientModel::OtherActivitiesExclusion) { if (!client->isOnCurrentActivity()) { return true; } } if (exclusions & ClientModel::MinimizedExclusion) { if (client->isMinimized()) { return true; } } if (exclusions & ClientModel::NotAcceptingFocusExclusion) { if (!client->wantsInput()) { return true; } } return false; } bool ClientLevel::shouldAdd(AbstractClient *client) const { if (restrictions() == ClientModel::NoRestriction) { return true; } if (restrictions() & ClientModel::ActivityRestriction) { if (!client->isOnActivity(activity())) { return false; } } if (restrictions() & ClientModel::VirtualDesktopRestriction) { if (!client->isOnDesktop(virtualDesktop())) { return false; } } if (restrictions() & ClientModel::ScreenRestriction) { if (client->screen() != int(screen())) { return false; } } return true; } void ClientLevel::addClient(AbstractClient *client) { if (containsClient(client)) { return; } emit beginInsert(m_clients.count(), m_clients.count(), id()); m_clients.insert(nextId(), client); emit endInsert(); } void ClientLevel::removeClient(AbstractClient *client) { int index = 0; auto it = m_clients.begin(); for (; it != m_clients.end(); ++it, ++index) { if (it.value() == client) { break; } } if (it == m_clients.end()) { return; } emit beginRemove(index, index, id()); m_clients.erase(it); emit endRemove(); } void ClientLevel::init() { const QList &clients = Workspace::self()->clientList(); for (auto it = clients.begin(); it != clients.end(); ++it) { X11Client *client = *it; setupClientConnections(client); if (!exclude(client) && shouldAdd(client)) { m_clients.insert(nextId(), client); } } } void ClientLevel::reInit() { const QList &clients = Workspace::self()->clientList(); for (auto it = clients.begin(); it != clients.end(); ++it) { checkClient((*it)); } if (waylandServer()) { const auto &clients = waylandServer()->clients(); for (auto *c : clients) { checkClient(c); } } } quint32 ClientLevel::idForRow(int row) const { if (row >= m_clients.size()) { return 0; } auto it = m_clients.constBegin(); for (int i=0; i(this); } return nullptr; } AbstractLevel *AbstractLevel::create(const QList< ClientModel::LevelRestriction > &restrictions, ClientModel::LevelRestrictions parentRestrictions, ClientModel *model, AbstractLevel *parent) { if (restrictions.isEmpty() || restrictions.first() == ClientModel::NoRestriction) { ClientLevel *leaf = new ClientLevel(model, parent); leaf->setRestrictions(parentRestrictions); if (!parent) { leaf->setParent(model); } return leaf; } // create a level QList childRestrictions(restrictions); ClientModel::LevelRestriction restriction = childRestrictions.takeFirst(); ClientModel::LevelRestrictions childrenRestrictions = restriction | parentRestrictions; ForkLevel *currentLevel = new ForkLevel(childRestrictions, model, parent); currentLevel->setRestrictions(childrenRestrictions); currentLevel->setRestriction(restriction); if (!parent) { currentLevel->setParent(model); } switch (restriction) { case ClientModel::ActivityRestriction: { #ifdef KWIN_BUILD_ACTIVITIES if (Activities::self()) { const QStringList &activities = Activities::self()->all(); for (QStringList::const_iterator it = activities.begin(); it != activities.end(); ++it) { AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); if (!childLevel) { continue; } childLevel->setActivity(*it); currentLevel->addChild(childLevel); } } break; #else return nullptr; #endif } case ClientModel::ScreenRestriction: for (int i=0; icount(); ++i) { AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); if (!childLevel) { continue; } childLevel->setScreen(i); currentLevel->addChild(childLevel); } break; case ClientModel::VirtualDesktopRestriction: for (uint i=1; i<=VirtualDesktopManager::self()->count(); ++i) { AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); if (!childLevel) { continue; } childLevel->setVirtualDesktop(i); currentLevel->addChild(childLevel); } break; default: // invalid return nullptr; } return currentLevel; } AbstractLevel::AbstractLevel(ClientModel *model, AbstractLevel *parent) : QObject(parent) , m_model(model) , m_parent(parent) , m_screen(0) , m_virtualDesktop(0) , m_activity() , m_restriction(ClientModel::ClientModel::NoRestriction) , m_restrictions(ClientModel::NoRestriction) , m_id(nextId()) { } AbstractLevel::~AbstractLevel() { } void AbstractLevel::setRestriction(ClientModel::LevelRestriction restriction) { m_restriction = restriction; } void AbstractLevel::setActivity(const QString &activity) { m_activity = activity; } void AbstractLevel::setScreen(uint screen) { m_screen = screen; } void AbstractLevel::setVirtualDesktop(uint virtualDesktop) { m_virtualDesktop = virtualDesktop; } void AbstractLevel::setRestrictions(ClientModel::LevelRestrictions restrictions) { m_restrictions = restrictions; } ForkLevel::ForkLevel(const QList &childRestrictions, ClientModel *model, AbstractLevel *parent) : AbstractLevel(model, parent) , m_childRestrictions(childRestrictions) { connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), SLOT(desktopCountChanged(uint,uint))); connect(screens(), SIGNAL(countChanged(int,int)), SLOT(screenCountChanged(int,int))); #ifdef KWIN_BUILD_ACTIVITIES if (Activities *activities = Activities::self()) { connect(activities, SIGNAL(added(QString)), SLOT(activityAdded(QString))); connect(activities, SIGNAL(removed(QString)), SLOT(activityRemoved(QString))); } #endif } ForkLevel::~ForkLevel() { } void ForkLevel::desktopCountChanged(uint previousCount, uint newCount) { if (restriction() != ClientModel::ClientModel::VirtualDesktopRestriction) { return; } if (previousCount != uint(count())) { return; } if (previousCount > newCount) { // desktops got removed emit beginRemove(newCount, previousCount-1, id()); while (uint(m_children.count()) > newCount) { delete m_children.takeLast(); } emit endRemove(); } else { // desktops got added emit beginInsert(previousCount, newCount-1, id()); for (uint i=previousCount+1; i<=newCount; ++i) { AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this); if (!childLevel) { continue; } childLevel->setVirtualDesktop(i); childLevel->init(); addChild(childLevel); } emit endInsert(); } } void ForkLevel::screenCountChanged(int previousCount, int newCount) { if (restriction() != ClientModel::ClientModel::ClientModel::ScreenRestriction) { return; } if (newCount == previousCount || previousCount != count()) { return; } if (previousCount > newCount) { // screens got removed emit beginRemove(newCount, previousCount-1, id()); while (m_children.count() > newCount) { delete m_children.takeLast(); } emit endRemove(); } else { // screens got added emit beginInsert(previousCount, newCount-1, id()); for (int i=previousCount; isetScreen(i); childLevel->init(); addChild(childLevel); } emit endInsert(); } } void ForkLevel::activityAdded(const QString &activityId) { #ifdef KWIN_BUILD_ACTIVITIES if (restriction() != ClientModel::ClientModel::ActivityRestriction) { return; } // verify that our children do not contain this activity foreach (AbstractLevel *child, m_children) { if (child->activity() == activityId) { return; } } emit beginInsert(m_children.count(), m_children.count(), id()); AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this); if (!childLevel) { emit endInsert(); return; } childLevel->setActivity(activityId); childLevel->init(); addChild(childLevel); emit endInsert(); #else Q_UNUSED(activityId) #endif } void ForkLevel::activityRemoved(const QString &activityId) { #ifdef KWIN_BUILD_ACTIVITIES if (restriction() != ClientModel::ClientModel::ActivityRestriction) { return; } for (int i=0; iactivity() == activityId) { emit beginRemove(i, i, id()); delete m_children.takeAt(i); emit endRemove(); break; } } #else Q_UNUSED(activityId) #endif } int ForkLevel::count() const { return m_children.count(); } void ForkLevel::addChild(AbstractLevel *child) { m_children.append(child); connect(child, SIGNAL(beginInsert(int,int,quint32)), SIGNAL(beginInsert(int,int,quint32))); connect(child, SIGNAL(beginRemove(int,int,quint32)), SIGNAL(beginRemove(int,int,quint32))); connect(child, SIGNAL(endInsert()), SIGNAL(endInsert())); connect(child, SIGNAL(endRemove()), SIGNAL(endRemove())); } void ForkLevel::setActivity(const QString &activity) { AbstractLevel::setActivity(activity); for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->setActivity(activity); } } void ForkLevel::setScreen(uint screen) { AbstractLevel::setScreen(screen); for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->setScreen(screen); } } void ForkLevel::setVirtualDesktop(uint virtualDesktop) { AbstractLevel::setVirtualDesktop(virtualDesktop); for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->setVirtualDesktop(virtualDesktop); } } void ForkLevel::init() { for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { (*it)->init(); } } quint32 ForkLevel::idForRow(int row) const { if (row >= m_children.length()) { return 0; } return m_children.at(row)->id(); } const AbstractLevel *ForkLevel::levelForId(quint32 id) const { if (id == AbstractLevel::id()) { return this; } for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { if (const AbstractLevel *child = (*it)->levelForId(id)) { return child; } } // not found return nullptr; } AbstractLevel *ForkLevel::parentForId(quint32 child) const { if (child == id()) { return parentLevel(); } for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { if (AbstractLevel *parent = (*it)->parentForId(child)) { return parent; } } // not found return nullptr; } int ForkLevel::rowForId(quint32 child) const { if (id() == child) { return 0; } for (int i=0; iid() == child) { return i; } } // do recursion for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { int row = (*it)->rowForId(child); if (row != -1) { return row; } } // not found return -1; } AbstractClient *ForkLevel::clientForId(quint32 child) const { for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { if (AbstractClient *client = (*it)->clientForId(child)) { return client; } } // not found return nullptr; } ClientModel::ClientModel(QObject *parent) : QAbstractItemModel(parent) , m_root(nullptr) , m_exclusions(NoExclusion) { } ClientModel::~ClientModel() { } void ClientModel::setLevels(QList< ClientModel::LevelRestriction > restrictions) { beginResetModel(); if (m_root) { delete m_root; } m_root = AbstractLevel::create(restrictions, NoRestriction, this); connect(m_root, SIGNAL(beginInsert(int,int,quint32)), SLOT(levelBeginInsert(int,int,quint32))); connect(m_root, SIGNAL(beginRemove(int,int,quint32)), SLOT(levelBeginRemove(int,int,quint32))); connect(m_root, SIGNAL(endInsert()), SLOT(levelEndInsert())); connect(m_root, SIGNAL(endRemove()), SLOT(levelEndRemove())); m_root->init(); endResetModel(); } void ClientModel::setExclusions(ClientModel::Exclusions exclusions) { if (exclusions == m_exclusions) { return; } m_exclusions = exclusions; emit exclusionsChanged(); } QVariant ClientModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.column() != 0) { return QVariant(); } if (const AbstractLevel *level = getLevel(index)) { LevelRestriction restriction = level->restriction(); if (restriction == ActivityRestriction && (role == Qt::DisplayRole || role == ActivityRole)) { return level->activity(); } else if (restriction == VirtualDesktopRestriction && (role == Qt::DisplayRole || role == DesktopRole)) { return level->virtualDesktop(); } else if (restriction ==ScreenRestriction && (role == Qt::DisplayRole || role == ScreenRole)) { return level->screen(); } else { return QVariant(); } } if (role == Qt::DisplayRole || role == ClientRole) { if (AbstractClient *client = m_root->clientForId(index.internalId())) { return QVariant::fromValue(client); } } return QVariant(); } int ClientModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 1; } int ClientModel::rowCount(const QModelIndex &parent) const { if (!m_root) { return 0; } if (!parent.isValid()) { return m_root->count(); } if (const AbstractLevel *level = getLevel(parent)) { if (level->id() != parent.internalId()) { // not a real level - no children return 0; } return level->count(); } return 0; } QHash ClientModel::roleNames() const { return { { Qt::DisplayRole, QByteArrayLiteral("display") }, { ClientRole, QByteArrayLiteral("client") }, { ScreenRole, QByteArrayLiteral("screen") }, { DesktopRole, QByteArrayLiteral("desktop") }, { ActivityRole, QByteArrayLiteral("activity") }, }; } QModelIndex ClientModel::parent(const QModelIndex &child) const { if (!child.isValid() || child.column() != 0) { return QModelIndex(); } return parentForId(child.internalId()); } QModelIndex ClientModel::parentForId(quint32 childId) const { if (childId == m_root->id()) { // asking for parent of our toplevel return QModelIndex(); } if (AbstractLevel *parentLevel = m_root->parentForId(childId)) { if (parentLevel == m_root) { return QModelIndex(); } const int row = m_root->rowForId(parentLevel->id()); if (row == -1) { // error return QModelIndex(); } return createIndex(row, 0, parentLevel->id()); } return QModelIndex(); } QModelIndex ClientModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0 || row < 0 || !m_root) { return QModelIndex(); } if (!parent.isValid()) { if (row >= rowCount()) { return QModelIndex(); } return createIndex(row, 0, m_root->idForRow(row)); } const AbstractLevel *parentLevel = getLevel(parent); if (!parentLevel) { return QModelIndex(); } if (row >= parentLevel->count()) { return QModelIndex(); } const quint32 id = parentLevel->idForRow(row); if (id == 0) { return QModelIndex(); } return createIndex(row, column, id); } const AbstractLevel *ClientModel::getLevel(const QModelIndex &index) const { if (!index.isValid()) { return m_root; } return m_root->levelForId(index.internalId()); } void ClientModel::levelBeginInsert(int rowStart, int rowEnd, quint32 id) { const int row = m_root->rowForId(id); QModelIndex parent; if (row != -1) { parent = createIndex(row, 0, id); } beginInsertRows(parent, rowStart, rowEnd); } void ClientModel::levelBeginRemove(int rowStart, int rowEnd, quint32 id) { const int row = m_root->rowForId(id); QModelIndex parent; if (row != -1) { parent = createIndex(row, 0, id); } beginRemoveRows(parent, rowStart, rowEnd); } void ClientModel::levelEndInsert() { endInsertRows(); } void ClientModel::levelEndRemove() { endRemoveRows(); } #define CLIENT_MODEL_WRAPPER(name, levels) \ name::name(QObject *parent) \ : ClientModel(parent) \ { \ setLevels(levels); \ } \ name::~name() {} CLIENT_MODEL_WRAPPER(SimpleClientModel, QList()) CLIENT_MODEL_WRAPPER(ClientModelByScreen, QList() << ScreenRestriction) CLIENT_MODEL_WRAPPER(ClientModelByScreenAndDesktop, QList() << ScreenRestriction << VirtualDesktopRestriction) #undef CLIENT_MODEL_WRAPPER ClientFilterModel::ClientFilterModel(QObject *parent) : QSortFilterProxyModel(parent) , m_clientModel(nullptr) { } ClientFilterModel::~ClientFilterModel() { } void ClientFilterModel::setClientModel(ClientModel *clientModel) { if (clientModel == m_clientModel) { return; } m_clientModel = clientModel; setSourceModel(m_clientModel); emit clientModelChanged(); } void ClientFilterModel::setFilter(const QString &filter) { if (filter == m_filter) { return; } m_filter = filter; emit filterChanged(); invalidateFilter(); } bool ClientFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { if (!m_clientModel) { return false; } if (m_filter.isEmpty()) { return true; } QModelIndex index = m_clientModel->index(sourceRow, 0, sourceParent); if (!index.isValid()) { return false; } QVariant data = index.data(); if (!data.isValid()) { // an invalid QVariant is valid data return true; } // TODO: introduce a type as a data role and properly check, this seems dangerous if (data.type() == QVariant::Int || data.type() == QVariant::UInt || data.type() == QVariant::String) { // we do not filter out screen, desktop and activity return true; } X11Client *client = qvariant_cast(data); if (!client) { return false; } 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; } } // namespace Scripting } // namespace KWin