You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

546 lines
17 KiB

/********************************************************************
Copyright 2016 Eike Hein <hein@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "waylandtasksmodel.h"
#include "tasktools.h"
#include <KActivities/ResourceInstance>
#include <KRun>
#include <KService>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/plasmawindowmanagement.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
#include <KWindowSystem>
#include <QGuiApplication>
#include <QQuickItem>
#include <QQuickWindow>
#include <QSet>
#include <QUrl>
#include <QWindow>
namespace TaskManager
{
class WaylandTasksModel::Private
{
public:
Private(WaylandTasksModel *q);
QList<KWayland::Client::PlasmaWindow*> windows;
QHash<KWayland::Client::PlasmaWindow*, AppData> appDataCache;
KWayland::Client::PlasmaWindowManagement *windowManagement = nullptr;
void initWayland();
void addWindow(KWayland::Client::PlasmaWindow *window);
AppData appData(KWayland::Client::PlasmaWindow *window);
void dataChanged(KWayland::Client::PlasmaWindow *window, int role);
void dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles);
private:
WaylandTasksModel *q;
};
WaylandTasksModel::Private::Private(WaylandTasksModel *q)
: q(q)
{
}
void WaylandTasksModel::Private::initWayland()
{
if (!KWindowSystem::isPlatformWayland()) {
return;
}
KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(q);
if (!connection) {
return;
}
KWayland::Client::Registry *registry = new KWayland::Client::Registry(q);
registry->create(connection);
QObject::connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, [this, registry] (quint32 name, quint32 version) {
windowManagement = registry->createPlasmaWindowManagement(name, version, q);
QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::interfaceAboutToBeReleased, q,
[this] {
q->beginResetModel();
windows.clear();
q->endResetModel();
}
);
QObject::connect(windowManagement, &KWayland::Client::PlasmaWindowManagement::windowCreated, q,
[this](KWayland::Client::PlasmaWindow *window) {
addWindow(window);
}
);
const auto windows = windowManagement->windows();
for (auto it = windows.constBegin(); it != windows.constEnd(); ++it) {
addWindow(*it);
}
}
);
registry->setup();
}
void WaylandTasksModel::Private::addWindow(KWayland::Client::PlasmaWindow *window)
{
if (windows.indexOf(window) != -1) {
return;
}
const int count = windows.count();
q->beginInsertRows(QModelIndex(), count, count);
windows.append(window);
q->endInsertRows();
auto removeWindow = [window, this] {
const int row = windows.indexOf(window);
if (row != -1) {
q->beginRemoveRows(QModelIndex(), row, row);
windows.removeAt(row);
appDataCache.remove(window);
q->endRemoveRows();
}
};
QObject::connect(window, &KWayland::Client::PlasmaWindow::unmapped, q, removeWindow);
QObject::connect(window, &QObject::destroyed, q, removeWindow);
QObject::connect(window, &KWayland::Client::PlasmaWindow::titleChanged, q,
[window, this] { dataChanged(window, Qt::DisplayRole); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::iconChanged, q,
[window, this] { dataChanged(window, Qt::DecorationRole); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::appIdChanged, q,
[window, this] {
appDataCache.remove(window);
dataChanged(window, QVector<int>{AppId, AppName, GenericName,
LauncherUrl, LauncherUrlWithoutIcon});
}
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::activeChanged, q,
[window, this] { this->dataChanged(window, IsActive); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::closeableChanged, q,
[window, this] { this->dataChanged(window, IsClosable); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::movableChanged, q,
[window, this] { this->dataChanged(window, IsMovable); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::resizableChanged, q,
[window, this] { this->dataChanged(window, IsResizable); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::fullscreenableChanged, q,
[window, this] { this->dataChanged(window, IsFullScreenable); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::fullscreenChanged, q,
[window, this] { this->dataChanged(window, IsFullScreen); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::maximizeableChanged, q,
[window, this] { this->dataChanged(window, IsMaximizable); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::maximizedChanged, q,
[window, this] { this->dataChanged(window, IsMaximized); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::minimizeableChanged, q,
[window, this] { this->dataChanged(window, IsMinimizable); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::minimizedChanged, q,
[window, this] { this->dataChanged(window, IsMinimized); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::keepAboveChanged, q,
[window, this] { this->dataChanged(window, IsKeepAbove); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::keepBelowChanged, q,
[window, this] { this->dataChanged(window, IsKeepBelow); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::shadeableChanged, q,
[window, this] { this->dataChanged(window, IsShadeable); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChangeableChanged, q,
[window, this] { this->dataChanged(window, IsVirtualDesktopChangeable); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::virtualDesktopChanged, q,
[window, this] { this->dataChanged(window, VirtualDesktop); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::onAllDesktopsChanged, q,
[window, this] { this->dataChanged(window, IsOnAllVirtualDesktops); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::geometryChanged, q,
[window, this] { this->dataChanged(window, QVector<int>{Geometry, ScreenGeometry}); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::demandsAttentionChanged, q,
[window, this] { this->dataChanged(window, IsDemandingAttention); }
);
QObject::connect(window, &KWayland::Client::PlasmaWindow::skipTaskbarChanged, q,
[window, this] { this->dataChanged(window, SkipTaskbar); }
);
}
AppData WaylandTasksModel::Private::appData(KWayland::Client::PlasmaWindow *window)
{
const auto &it = appDataCache.constFind(window);
if (it != appDataCache.constEnd()) {
return *it;
}
const AppData &data = appDataFromAppId(window->appId());
appDataCache.insert(window, data);
return data;
}
void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, int role)
{
QModelIndex idx = q->index(windows.indexOf(window));
emit q->dataChanged(idx, idx, QVector<int>{role});
}
void WaylandTasksModel::Private::dataChanged(KWayland::Client::PlasmaWindow *window, const QVector<int> &roles)
{
QModelIndex idx = q->index(windows.indexOf(window));
emit q->dataChanged(idx, idx, roles);
}
WaylandTasksModel::WaylandTasksModel(QObject *parent)
: AbstractTasksModel(parent)
, d(new Private(this))
{
d->initWayland();
}
WaylandTasksModel::~WaylandTasksModel() = default;
QVariant WaylandTasksModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= d->windows.count()) {
return QVariant();
}
KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
if (role == Qt::DisplayRole) {
return window->title();
} else if (role == Qt::DecorationRole) {
return window->icon();
} else if (role == AppId) {
return window->appId();
} else if (role == AppName) {
return d->appData(window).name;
} else if (role == GenericName) {
return d->appData(window).genericName;
} else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) {
return d->appData(window).url;
} else if (role == IsWindow) {
return true;
} else if (role == IsActive) {
return window->isActive();
} else if (role == IsClosable) {
return window->isCloseable();
} else if (role == IsMovable) {
return window->isMovable();
} else if (role == IsResizable) {
return window->isResizable();
} else if (role == IsMaximizable) {
return window->isMaximizeable();
} else if (role == IsMaximized) {
return window->isMaximized();
} else if (role == IsMinimizable) {
return window->isMinimizeable();
} else if (role == IsMinimized) {
return window->isMinimized();
} else if (role == IsKeepAbove) {
return window->isKeepAbove();
} else if (role == IsKeepBelow) {
return window->isKeepBelow();
} else if (role == IsFullScreenable) {
return window->isFullscreenable();
} else if (role == IsFullScreen) {
return window->isFullscreen();
} else if (role == IsShadeable) {
return window->isShadeable();
} else if (role == IsShaded) {
return window->isShaded();
} else if (role == IsVirtualDesktopChangeable) {
return window->isVirtualDesktopChangeable();
} else if (role == VirtualDesktop) {
return window->virtualDesktop();
} else if (role == IsOnAllVirtualDesktops) {
return window->isOnAllDesktops();
} else if (role == Geometry) {
return window->geometry();
} else if (role == ScreenGeometry) {
return screenGeometry(window->geometry().center());
} else if (role == Activities) {
// FIXME Implement.
} else if (role == IsDemandingAttention) {
return window->isDemandingAttention();
} else if (role == SkipTaskbar) {
return window->skipTaskbar();
} else if (role == SkipPager) {
// FIXME Implement.
} else if (role == AppPid) {
// FIXME Implement.
}
return QVariant();
}
int WaylandTasksModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : d->windows.count();
}
QModelIndex WaylandTasksModel::index(int row, int column, const QModelIndex &parent) const
{
return hasIndex(row, column, parent) ? createIndex(row, column, d->windows.at(row)) : QModelIndex();
}
void WaylandTasksModel::requestActivate(const QModelIndex &index)
{
// FIXME Lacks transient handling of the XWindows version.
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
d->windows.at(index.row())->requestActivate();
}
void WaylandTasksModel::requestNewInstance(const QModelIndex &index)
{
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
KWayland::Client::PlasmaWindow* window = d->windows.at(index.row());
if (d->appDataCache.contains(window)) {
const AppData &data = d->appData(window);
new KRun(data.url, 0, false);
if (!data.id.isEmpty()) {
KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("applications:") + data.id),
QStringLiteral("org.kde.libtaskmanager"));
}
}
}
void WaylandTasksModel::requestOpenUrls(const QModelIndex &index, const QList<QUrl> &urls)
{
if (!index.isValid() || index.model() != this || index.row() < 0
|| index.row() >= d->windows.count()
|| urls.isEmpty()) {
return;
}
const QUrl &url = d->appData(d->windows.at(index.row())).url;
const KService::Ptr service = KService::serviceByDesktopPath(url.toLocalFile());
if (service) {
KRun::runApplication(*service, urls, nullptr, 0);
KActivities::ResourceInstance::notifyAccessed(QUrl(QStringLiteral("applications:") + service->storageId()),
QStringLiteral("org.kde.libtaskmanager"));
}
}
void WaylandTasksModel::requestClose(const QModelIndex &index)
{
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
d->windows.at(index.row())->requestClose();
}
void WaylandTasksModel::requestMove(const QModelIndex &index)
{
// FIXME Move-to-desktop logic from XWindows version. (See also others.)
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
d->windows.at(index.row())->requestMove();
}
void WaylandTasksModel::requestResize(const QModelIndex &index)
{
// FIXME Move-to-desktop logic from XWindows version. (See also others.)
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
d->windows.at(index.row())->requestResize();
}
void WaylandTasksModel::requestToggleMinimized(const QModelIndex &index)
{
// FIXME Move-to-desktop logic from XWindows version. (See also others.)
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
d->windows.at(index.row())->requestToggleMinimized();
}
void WaylandTasksModel::requestToggleMaximized(const QModelIndex &index)
{
// FIXME Move-to-desktop logic from XWindows version. (See also others.)
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
d->windows.at(index.row())->requestToggleMaximized();
}
void WaylandTasksModel::requestToggleKeepAbove(const QModelIndex &index)
{
Q_UNUSED(index)
// FIXME Implement.
}
void WaylandTasksModel::requestToggleKeepBelow(const QModelIndex &index)
{
Q_UNUSED(index)
// FIXME Implement.
}
void WaylandTasksModel::requestToggleFullScreen(const QModelIndex &index)
{
Q_UNUSED(index)
// FIXME Implement.
}
void WaylandTasksModel::requestToggleShaded(const QModelIndex &index)
{
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
d->windows.at(index.row())->requestToggleShaded();
}
void WaylandTasksModel::requestVirtualDesktop(const QModelIndex &index, qint32 desktop)
{
// FIXME Lacks add-new-desktop code from XWindows version.
// FIXME Does this do the set-on-all-desktops stuff from the XWindows version?
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
d->windows.at(index.row())->requestVirtualDesktop(desktop);
}
void WaylandTasksModel::requestActivities(const QModelIndex &index, const QStringList &activities)
{
Q_UNUSED(index)
Q_UNUSED(activities)
}
void WaylandTasksModel::requestPublishDelegateGeometry(const QModelIndex &index, const QRect &geometry, QObject *delegate)
{
/*
FIXME: This introduces the dependency on Qt5::Quick. I might prefer
reversing this and publishing the window pointer through the model,
then calling PlasmaWindow::setMinimizeGeometry in the applet backend,
rather than hand delegate items into the lib, keeping the lib more UI-
agnostic.
*/
Q_UNUSED(geometry)
if (!index.isValid() || index.model() != this || index.row() < 0 || index.row() >= d->windows.count()) {
return;
}
const QQuickItem *item = qobject_cast<const QQuickItem *>(delegate);
if (!item || !item->parentItem() || !item->window()) {
return;
}
QWindow *itemWindow = item->window();
if (!itemWindow) {
return;
}
using namespace KWayland::Client;
Surface *surface = Surface::fromWindow(itemWindow);
if (!surface) {
return;
}
QRect rect(item->x(), item->y(), item->width(), item->height());
rect.moveTopLeft(item->parentItem()->mapToScene(rect.topLeft()).toPoint());
KWayland::Client::PlasmaWindow *window = d->windows.at(index.row());
window->setMinimizedGeometry(surface, rect);
}
}