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.
274 lines
8.4 KiB
274 lines
8.4 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 "startuptasksmodel.h" |
|
|
|
#include <KConfig> |
|
#include <KConfigGroup> |
|
#include <KDirWatch> |
|
#include <KService> |
|
#include <KServiceTypeTrader> |
|
#include <KStartupInfo> |
|
|
|
#include <QIcon> |
|
#include <QTimer> |
|
#include <QUrl> |
|
|
|
namespace TaskManager |
|
{ |
|
|
|
class StartupTasksModel::Private |
|
{ |
|
public: |
|
Private(StartupTasksModel *q); |
|
KDirWatch* configWatcher = nullptr; |
|
KStartupInfo *startupInfo = nullptr; |
|
QVector<KStartupInfoId> startups; |
|
QHash<QByteArray, KStartupInfoData> startupData; |
|
QHash<QByteArray, QUrl> launcherUrls; |
|
|
|
void init(); |
|
void loadConfig(); |
|
QUrl launcherUrl(const KStartupInfoData &data); |
|
|
|
private: |
|
StartupTasksModel *q; |
|
}; |
|
|
|
StartupTasksModel::Private::Private(StartupTasksModel *q) |
|
: q(q) |
|
{ |
|
} |
|
|
|
void StartupTasksModel::Private::init() |
|
{ |
|
configWatcher = new KDirWatch(q); |
|
configWatcher->addFile(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) |
|
+ QLatin1String("/klaunchrc")); |
|
|
|
QObject::connect(configWatcher, &KDirWatch::dirty, [this] { loadConfig(); }); |
|
QObject::connect(configWatcher, &KDirWatch::created, [this] { loadConfig(); }); |
|
QObject::connect(configWatcher, &KDirWatch::deleted, [this] { loadConfig(); }); |
|
|
|
loadConfig(); |
|
} |
|
|
|
void StartupTasksModel::Private::loadConfig() |
|
{ |
|
const KConfig _c("klaunchrc"); |
|
KConfigGroup c(&_c, "FeedbackStyle"); |
|
|
|
if (!c.readEntry("TaskbarButton", true)) { |
|
delete startupInfo; |
|
startupInfo = nullptr; |
|
|
|
q->beginResetModel(); |
|
startups.clear(); |
|
startupData.clear(); |
|
q->endResetModel(); |
|
|
|
return; |
|
} |
|
|
|
if (!startupInfo) { |
|
startupInfo = new KStartupInfo(KStartupInfo::CleanOnCantDetect, q); |
|
|
|
QObject::connect(startupInfo, &KStartupInfo::gotNewStartup, q, |
|
[this](const KStartupInfoId &id, const KStartupInfoData &data) { |
|
if (startups.contains(id)) { |
|
return; |
|
} |
|
|
|
const QString appId = data.applicationId(); |
|
const QString bin = data.bin(); |
|
|
|
foreach(const KStartupInfoData &known, startupData) { |
|
// Reject if we already have a startup notification for this app. |
|
if (known.applicationId() == appId && known.bin() == bin) { |
|
return; |
|
} |
|
} |
|
|
|
const int count = startups.count(); |
|
q->beginInsertRows(QModelIndex(), count, count); |
|
startups.append(id); |
|
startupData.insert(id.id(), data); |
|
launcherUrls.insert(id.id(), launcherUrl(data)); |
|
q->endInsertRows(); |
|
} |
|
); |
|
|
|
QObject::connect(startupInfo, &KStartupInfo::gotRemoveStartup, q, |
|
[this](const KStartupInfoId &id) { |
|
// The order in which startups are cancelled and corresponding |
|
// windows appear is not reliable. Add some grace time to make |
|
// an overlap more likely, giving a proxy some time to arbitrate |
|
// between the two. |
|
QTimer::singleShot(500, |
|
[this, id]() { |
|
const int row = startups.indexOf(id); |
|
|
|
if (row != -1) { |
|
q->beginRemoveRows(QModelIndex(), row, row); |
|
startups.removeAt(row); |
|
startupData.remove(id.id()); |
|
launcherUrls.remove(id.id()); |
|
q->endRemoveRows(); |
|
} |
|
} |
|
); |
|
} |
|
); |
|
|
|
QObject::connect(startupInfo, &KStartupInfo::gotStartupChange, q, |
|
[this](const KStartupInfoId &id, const KStartupInfoData &data) { |
|
const int row = startups.indexOf(id); |
|
if (row != -1) { |
|
startupData.insert(id.id(), data); |
|
launcherUrls.insert(id.id(), launcherUrl(data)); |
|
QModelIndex idx = q->index(row); |
|
emit q->dataChanged(idx, idx); |
|
} |
|
} |
|
); |
|
} |
|
|
|
c = KConfigGroup(&_c, "TaskbarButtonSettings"); |
|
startupInfo->setTimeout(c.readEntry("Timeout", 5)); |
|
} |
|
|
|
QUrl StartupTasksModel::Private::launcherUrl(const KStartupInfoData &data) |
|
{ |
|
QUrl launcherUrl; |
|
KService::List services; |
|
|
|
QString appId = data.applicationId(); |
|
|
|
// Try to match via desktop filename ... |
|
if (!appId.isEmpty() && appId.endsWith(QLatin1String(".desktop"))) { |
|
if (appId.startsWith(QLatin1String("/"))) { |
|
launcherUrl = QUrl::fromLocalFile(appId); |
|
return launcherUrl; |
|
} else { |
|
if (appId.endsWith(QLatin1String(".desktop"))) { |
|
appId = appId.mid(appId.length() - 8); |
|
} |
|
|
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), |
|
QStringLiteral("exist Exec and ('%1' =~ DesktopEntryName)").arg(appId)); |
|
} |
|
} |
|
|
|
const QString wmClass = data.WMClass(); |
|
|
|
// Try StartupWMClass. |
|
if (services.empty() && !wmClass.isEmpty()) { |
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), |
|
QStringLiteral("exist Exec and ('%1' =~ StartupWMClass)").arg(wmClass)); |
|
} |
|
|
|
const QString name = data.findName(); |
|
|
|
// Try via name ... |
|
if (services.empty() && !name.isEmpty()) { |
|
services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), |
|
QStringLiteral("exist Exec and ('%1' =~ Name)").arg(name)); |
|
} |
|
|
|
if (!services.empty()) { |
|
QString path = services.at(0)->entryPath(); |
|
|
|
if (path.isEmpty()) { |
|
path = services.at(0)->exec(); |
|
} |
|
|
|
if (!path.isEmpty()) { |
|
launcherUrl = QUrl::fromLocalFile(path); |
|
} |
|
} |
|
|
|
return launcherUrl; |
|
} |
|
|
|
StartupTasksModel::StartupTasksModel(QObject *parent) |
|
: AbstractTasksModel(parent) |
|
, d(new Private(this)) |
|
{ |
|
d->init(); |
|
} |
|
|
|
StartupTasksModel::~StartupTasksModel() |
|
{ |
|
} |
|
|
|
QVariant StartupTasksModel::data(const QModelIndex &index, int role) const |
|
{ |
|
if (!index.isValid() || index.row() >= d->startups.count()) { |
|
return QVariant(); |
|
} |
|
|
|
const QByteArray &id = d->startups.at(index.row()).id(); |
|
|
|
if (!d->startupData.contains(id)) { |
|
return QVariant(); |
|
} |
|
|
|
const KStartupInfoData &data = d->startupData.value(id); |
|
|
|
if (role == Qt::DisplayRole) { |
|
return data.findName(); |
|
} else if (role == Qt::DecorationRole) { |
|
return QIcon::fromTheme(data.findIcon(), QIcon::fromTheme(QLatin1String("unknown"))); |
|
} else if (role == AppId) { |
|
QString idFromPath = QUrl::fromLocalFile(data.applicationId()).fileName(); |
|
|
|
if (idFromPath.endsWith(QLatin1String(".desktop"))) { |
|
idFromPath = idFromPath.left(idFromPath.length() - 8); |
|
} |
|
|
|
return idFromPath; |
|
} else if (role == AppName) { |
|
return data.findName(); |
|
} else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) { |
|
return d->launcherUrls.value(id); |
|
} else if (role == IsStartup) { |
|
return true; |
|
} else if (role == VirtualDesktop) { |
|
return data.desktop(); |
|
} else if (role == IsOnAllVirtualDesktops) { |
|
return (data.desktop() == 0); |
|
} |
|
|
|
return QVariant(); |
|
} |
|
|
|
int StartupTasksModel::rowCount(const QModelIndex &parent) const |
|
{ |
|
return parent.isValid() ? 0 : d->startups.count(); |
|
} |
|
|
|
void StartupTasksModel::requestNewInstance(const QModelIndex &index) |
|
{ |
|
Q_UNUSED(index) |
|
|
|
// FIXME Implement. |
|
} |
|
|
|
}
|
|
|