File indexing completed on 2024-05-05 05:38:37
0001 /* 0002 SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "waylandstartuptasksmodel.h" 0008 #include "libtaskmanager_debug.h" 0009 #include "tasktools.h" 0010 0011 #include <KConfigGroup> 0012 #include <KConfigWatcher> 0013 0014 #include <qwayland-plasma-window-management.h> 0015 0016 #include <QDebug> 0017 #include <QTimer> 0018 #include <QUrl> 0019 #include <QtWaylandClient/QWaylandClientExtensionTemplate> 0020 0021 using namespace Qt::StringLiterals; 0022 0023 namespace TaskManager 0024 { 0025 0026 class PlasmaActivation : public QObject, public QtWayland::org_kde_plasma_activation 0027 { 0028 Q_OBJECT 0029 public: 0030 PlasmaActivation(::org_kde_plasma_activation *object) 0031 : QtWayland::org_kde_plasma_activation(object) 0032 { 0033 } 0034 ~PlasmaActivation() override 0035 { 0036 destroy(); 0037 } 0038 void org_kde_plasma_activation_app_id(const QString &app_id) override 0039 { 0040 Q_EMIT appId(app_id); 0041 } 0042 void org_kde_plasma_activation_finished() override 0043 { 0044 Q_EMIT finished(); 0045 } 0046 Q_SIGNALS: 0047 void appId(const QString &appId); 0048 void finished(); 0049 }; 0050 class PlasmaActivationFeedback : public QWaylandClientExtensionTemplate<PlasmaActivationFeedback>, public QtWayland::org_kde_plasma_activation_feedback 0051 { 0052 Q_OBJECT 0053 public: 0054 PlasmaActivationFeedback() 0055 : QWaylandClientExtensionTemplate(1) 0056 { 0057 connect(this, &QWaylandClientExtension::activeChanged, this, [this] { 0058 if (!isActive()) { 0059 destroy(); 0060 } 0061 }); 0062 } 0063 ~PlasmaActivationFeedback() 0064 { 0065 if (isActive()) { 0066 destroy(); 0067 } 0068 } 0069 Q_SIGNALS: 0070 void newActivation(PlasmaActivation *activation); 0071 0072 protected: 0073 void org_kde_plasma_activation_feedback_activation(::org_kde_plasma_activation *id) override 0074 { 0075 Q_EMIT newActivation(new PlasmaActivation(id)); 0076 } 0077 }; 0078 0079 class Q_DECL_HIDDEN WaylandStartupTasksModel::Private 0080 { 0081 public: 0082 Private(WaylandStartupTasksModel *q); 0083 0084 void addActivation(PlasmaActivation *activation); 0085 void removeActivation(PlasmaActivation *activation); 0086 0087 void init(); 0088 void loadConfig(); 0089 0090 struct Startup { 0091 QString name; 0092 QIcon icon; 0093 QString applicationId; 0094 QUrl launcherUrl; 0095 std::unique_ptr<PlasmaActivation> activation; 0096 }; 0097 0098 WaylandStartupTasksModel *q; 0099 KConfigWatcher::Ptr configWatcher = nullptr; 0100 std::unique_ptr<PlasmaActivationFeedback> feedback = nullptr; 0101 std::vector<Startup> startups; 0102 std::chrono::seconds startupTimeout = std::chrono::seconds::zero(); 0103 }; 0104 0105 WaylandStartupTasksModel::Private::Private(WaylandStartupTasksModel *q) 0106 : q(q) 0107 { 0108 } 0109 0110 void WaylandStartupTasksModel::Private::init() 0111 { 0112 configWatcher = KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("klaunchrc"), KConfig::NoGlobals)); 0113 QObject::connect(configWatcher.data(), &KConfigWatcher::configChanged, q, [this] { 0114 loadConfig(); 0115 }); 0116 0117 loadConfig(); 0118 } 0119 0120 void WaylandStartupTasksModel::Private::loadConfig() 0121 { 0122 KConfigGroup feedbackConfig(configWatcher->config(), u"FeedbackStyle"_s); 0123 0124 if (!feedbackConfig.readEntry("TaskbarButton", true)) { 0125 q->beginResetModel(); 0126 startups.clear(); 0127 feedback.reset(); 0128 q->endResetModel(); 0129 return; 0130 } 0131 0132 const KConfigGroup taskbarButtonConfig(configWatcher->config(), u"TaskbarButtonSettings"_s); 0133 startupTimeout = std::chrono::seconds(taskbarButtonConfig.readEntry("Timeout", 5)); 0134 0135 feedback = std::make_unique<PlasmaActivationFeedback>(); 0136 0137 QObject::connect(feedback.get(), &PlasmaActivationFeedback::activeChanged, q, [this] { 0138 if (!feedback->isActive()) { 0139 q->beginResetModel(); 0140 startups.clear(); 0141 q->endResetModel(); 0142 } 0143 }); 0144 0145 QObject::connect(feedback.get(), &PlasmaActivationFeedback::newActivation, q, [this](PlasmaActivation *activation) { 0146 addActivation(activation); 0147 }); 0148 } 0149 0150 void WaylandStartupTasksModel::Private::addActivation(PlasmaActivation *activation) 0151 { 0152 QObject::connect(activation, &PlasmaActivation::appId, q, [this, activation](const QString &appId) { 0153 // The application id is guaranteed to be the desktop filename without ".desktop" 0154 const QString desktopFileName = appId + QLatin1String(".desktop"); 0155 const QString desktopFilePath = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktopFileName); 0156 if (desktopFilePath.isEmpty()) { 0157 qCWarning(TASKMANAGER_DEBUG) << "Got invalid activation app_id:" << appId; 0158 return; 0159 } 0160 0161 const QUrl launcherUrl(QStringLiteral("applications:") + desktopFileName); 0162 const AppData appData = appDataFromUrl(QUrl::fromLocalFile(desktopFilePath)); 0163 0164 const int count = startups.size(); 0165 q->beginInsertRows(QModelIndex(), count, count); 0166 startups.push_back(Startup{ 0167 .name = appData.name, 0168 .icon = appData.icon, 0169 .applicationId = appId, 0170 .launcherUrl = launcherUrl, 0171 .activation = std::unique_ptr<PlasmaActivation>(activation), 0172 }); 0173 q->endInsertRows(); 0174 0175 // Remove the activation if it doesn't finish within certain time interval. 0176 QTimer *timeoutTimer = new QTimer(activation); 0177 QObject::connect(timeoutTimer, &QTimer::timeout, q, [this, activation]() { 0178 removeActivation(activation); 0179 }); 0180 timeoutTimer->setSingleShot(true); 0181 timeoutTimer->start(startupTimeout); 0182 }); 0183 0184 QObject::connect(activation, &PlasmaActivation::finished, q, [this, activation]() { 0185 removeActivation(activation); 0186 }); 0187 } 0188 0189 void WaylandStartupTasksModel::Private::removeActivation(PlasmaActivation *activation) 0190 { 0191 auto it = std::find_if(startups.begin(), startups.end(), [activation](const Startup &startup) { 0192 return startup.activation.get() == activation; 0193 }); 0194 if (it == startups.end()) { 0195 return; 0196 } 0197 const int position = std::distance(startups.begin(), it); 0198 q->beginRemoveRows(QModelIndex(), position, position); 0199 startups.erase(it); 0200 q->endRemoveRows(); 0201 } 0202 0203 WaylandStartupTasksModel::WaylandStartupTasksModel(QObject *parent) 0204 : AbstractTasksModel(parent) 0205 , d(new Private(this)) 0206 { 0207 d->init(); 0208 } 0209 0210 WaylandStartupTasksModel::~WaylandStartupTasksModel() 0211 { 0212 } 0213 0214 QVariant WaylandStartupTasksModel::data(const QModelIndex &index, int role) const 0215 { 0216 // Note: when index is valid, its row >= 0, so casting to unsigned is safe 0217 if (!index.isValid() || static_cast<size_t>(index.row()) >= d->startups.size()) { 0218 return QVariant(); 0219 } 0220 0221 const auto &data = d->startups[index.row()]; 0222 if (role == Qt::DisplayRole) { 0223 return data.name; 0224 } else if (role == Qt::DecorationRole) { 0225 return data.icon; 0226 } else if (role == AppId) { 0227 return data.applicationId; 0228 } else if (role == AppName) { 0229 return data.name; 0230 } else if (role == LauncherUrl || role == LauncherUrlWithoutIcon) { 0231 return data.launcherUrl; 0232 } else if (role == IsStartup) { 0233 return true; 0234 } else if (role == CanLaunchNewInstance) { 0235 return false; 0236 } else if (role == IsOnAllVirtualDesktops) { 0237 return true; 0238 } 0239 0240 return AbstractTasksModel::data(index, role); 0241 } 0242 0243 int WaylandStartupTasksModel::rowCount(const QModelIndex &parent) const 0244 { 0245 return parent.isValid() ? 0 : d->startups.size(); 0246 } 0247 0248 } // namespace TaskManager 0249 0250 #include "waylandstartuptasksmodel.moc"