File indexing completed on 2024-04-28 16:51:33
0001 /* 0002 SPDX-FileCopyrightText: 2017 Kai Uwe Broulik <kde@privat.broulik.de> 0003 SPDX-FileCopyrightText: 2017 David Edmundson <davidedmundson@kde.org> 0004 SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> 0005 0006 SPDX-License-Identifier: MIT 0007 */ 0008 0009 #include "settings.h" 0010 0011 #include <unistd.h> // getppid 0012 0013 #include <QDBusConnection> 0014 #include <QGuiApplication> 0015 #include <QIcon> 0016 #include <QProcess> 0017 0018 #include <KDesktopFile> 0019 #include <KProcessList> 0020 #include <KService> 0021 0022 #include <taskmanager/abstracttasksmodel.h> 0023 #include <taskmanager/windowtasksmodel.h> 0024 0025 #include "pluginmanager.h" 0026 0027 #include <config-host.h> 0028 0029 const QMap<Settings::Environment, QString> Settings::environmentNames = { 0030 {Settings::Environment::Chrome, QStringLiteral("chrome")}, 0031 {Settings::Environment::Chromium, QStringLiteral("chromium")}, 0032 {Settings::Environment::Firefox, QStringLiteral("firefox")}, 0033 {Settings::Environment::Opera, QStringLiteral("opera")}, 0034 {Settings::Environment::Vivaldi, QStringLiteral("vivaldi")}, 0035 {Settings::Environment::Brave, QStringLiteral("brave")}, 0036 {Settings::Environment::Edge, QStringLiteral("edge")}, 0037 }; 0038 0039 const QMap<Settings::Environment, EnvironmentDescription> Settings::environmentDescriptions = { 0040 {Settings::Environment::Chrome, 0041 { 0042 QStringLiteral("google-chrome"), 0043 QStringLiteral("Google Chrome"), 0044 QStringLiteral("google-chrome"), 0045 QStringLiteral("google.com"), 0046 QStringLiteral("Google"), 0047 QStringLiteral("google-chrome"), 0048 }}, 0049 {Settings::Environment::Chromium, 0050 { 0051 QStringLiteral("chromium-browser"), 0052 QStringLiteral("Chromium"), 0053 QStringLiteral("chromium-browser"), 0054 QStringLiteral("google.com"), 0055 QStringLiteral("Google"), 0056 QStringLiteral("chromium-browser"), 0057 }}, 0058 {Settings::Environment::Firefox, 0059 { 0060 QStringLiteral("firefox"), 0061 QStringLiteral("Mozilla Firefox"), 0062 QStringLiteral("firefox"), 0063 QStringLiteral("mozilla.org"), 0064 QStringLiteral("Mozilla"), 0065 QStringLiteral("firefox"), 0066 }}, 0067 {Settings::Environment::Opera, 0068 { 0069 QStringLiteral("opera"), 0070 QStringLiteral("Opera"), 0071 QStringLiteral("opera"), 0072 QStringLiteral("opera.com"), 0073 QStringLiteral("Opera"), 0074 QStringLiteral("opera"), 0075 }}, 0076 {Settings::Environment::Vivaldi, 0077 { 0078 QStringLiteral("vivaldi"), 0079 QStringLiteral("Vivaldi"), 0080 // This is what the official package on their website uses 0081 QStringLiteral("vivaldi-stable"), 0082 QStringLiteral("vivaldi.com"), 0083 QStringLiteral("Vivaldi"), 0084 QStringLiteral("vivaldi"), 0085 }}, 0086 {Settings::Environment::Brave, 0087 { 0088 QStringLiteral("Brave"), 0089 QStringLiteral("Brave"), 0090 QStringLiteral("brave-browser"), 0091 QStringLiteral("brave.com"), 0092 QStringLiteral("Brave"), 0093 QStringLiteral("brave"), 0094 }}, 0095 {Settings::Environment::Edge, 0096 { 0097 QStringLiteral("Edge"), 0098 QStringLiteral("Microsoft Edge"), 0099 QStringLiteral("microsoft-edge"), 0100 QStringLiteral("microsoft.com"), 0101 QStringLiteral("Microsoft"), 0102 QStringLiteral("microsoft-edge"), 0103 }}, 0104 }; 0105 0106 Settings::Settings() 0107 : AbstractBrowserPlugin(QStringLiteral("settings"), 1, nullptr) 0108 // Settings is a singleton and cleaned up only in exit_handlers 0109 // at which point the QPA will already have cleaned up 0110 // leading to a crash on Wayland 0111 , m_tasksModel(new TaskManager::WindowTasksModel(qGuiApp)) 0112 { 0113 for (int i = 0; i < m_tasksModel->rowCount(); ++i) { 0114 if (setEnvironmentFromTasksModelIndex(m_tasksModel->index(i, 0))) { 0115 break; 0116 } 0117 } 0118 0119 // If we didn't find the browser window yet, monitor the model for changes 0120 if (qApp->desktopFileName().isEmpty()) { 0121 connect(m_tasksModel, &TaskManager::WindowTasksModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) { 0122 if (parent.isValid()) { 0123 return; 0124 } 0125 0126 for (int i = first; i <= last; ++i) { 0127 if (setEnvironmentFromTasksModelIndex(m_tasksModel->index(i, 0))) { 0128 break; 0129 } 0130 } 0131 }); 0132 connect(m_tasksModel, 0133 &TaskManager::WindowTasksModel::dataChanged, 0134 this, 0135 [this](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) { 0136 // TODO should we bother checking this, even? 0137 if (topLeft.parent().isValid() || bottomRight.parent().isValid() || topLeft.column() != 0 || bottomRight.column() != 0) { 0138 return; 0139 } 0140 0141 if (!roles.isEmpty() && !roles.contains(TaskManager::AbstractTasksModel::LauncherUrlWithoutIcon) 0142 && !roles.contains(TaskManager::AbstractTasksModel::AppId)) { 0143 return; 0144 } 0145 0146 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { 0147 if (setEnvironmentFromTasksModelIndex(m_tasksModel->index(i, 0))) { 0148 break; 0149 } 0150 } 0151 }); 0152 } 0153 } 0154 0155 Settings &Settings::self() 0156 { 0157 static Settings s_self; 0158 return s_self; 0159 } 0160 0161 void Settings::handleData(const QString &event, const QJsonObject &data) 0162 { 0163 if (event == QLatin1String("changed")) { 0164 m_settings = data; 0165 0166 for (auto it = data.begin(), end = data.end(); it != end; ++it) { 0167 const QString &subsystem = it.key(); 0168 const QJsonObject &settingsObject = it->toObject(); 0169 0170 const QJsonValue enabledVariant = settingsObject.value(QStringLiteral("enabled")); 0171 // probably protocol overhead, not a plugin setting, skip. 0172 if (enabledVariant.type() == QJsonValue::Undefined) { 0173 continue; 0174 } 0175 0176 auto *plugin = PluginManager::self().pluginForSubsystem(subsystem); 0177 if (!plugin) { 0178 continue; 0179 } 0180 0181 if (enabledVariant.toBool()) { 0182 PluginManager::self().loadPlugin(plugin); 0183 } else { 0184 PluginManager::self().unloadPlugin(plugin); 0185 } 0186 0187 PluginManager::self().settingsChanged(plugin, settingsObject); 0188 } 0189 0190 Q_EMIT changed(data); 0191 } else if (event == QLatin1String("openKRunnerSettings")) { 0192 QProcess::startDetached(QStringLiteral("systemsettings5"), {QStringLiteral("kcm_plasmasearch")}); 0193 } else if (event == QLatin1String("setEnvironment")) { 0194 setEnvironmentFromExtensionMessage(data); 0195 } 0196 } 0197 0198 bool Settings::setEnvironmentFromTasksModelIndex(const QModelIndex &idx) 0199 { 0200 bool ok = false; 0201 const auto pid = idx.data(TaskManager::AbstractTasksModel::AppPid).toLongLong(&ok); 0202 if (!ok || pid != getppid()) { 0203 return false; 0204 } 0205 0206 const QUrl launcherUrl = idx.data(TaskManager::AbstractTasksModel::LauncherUrlWithoutIcon).toUrl(); 0207 if (!launcherUrl.isValid()) { 0208 return false; 0209 } 0210 0211 KService::Ptr service; 0212 if (launcherUrl.scheme() == QLatin1String("applications")) { 0213 service = KService::serviceByMenuId(launcherUrl.path()); 0214 } else if (launcherUrl.isLocalFile()) { 0215 const QString launcherPath = launcherUrl.toLocalFile(); 0216 if (KDesktopFile::isDesktopFile(launcherPath)) { 0217 service = KService::serviceByDesktopPath(launcherUrl.toLocalFile()); 0218 } 0219 } else { 0220 qWarning() << "Got unrecognized launcher URL" << launcherUrl; 0221 return false; 0222 } 0223 0224 if (!service) { 0225 qWarning() << "Failed to get service from launcher URL" << launcherUrl; 0226 return false; 0227 } 0228 0229 // Ignore any browser-hosted app windows. 0230 if (!service->categories().contains(QLatin1String("WebBrowser"))) { 0231 qInfo() << "Ignoring launcher URL" << launcherUrl << "which doesn't have \"WebBrowser\" category"; 0232 return false; 0233 } 0234 0235 qApp->setApplicationName(service->menuId()); 0236 qApp->setApplicationDisplayName(service->name()); 0237 qApp->setDesktopFileName(service->desktopEntryName()); 0238 qApp->setWindowIcon(QIcon::fromTheme(service->icon())); 0239 0240 m_tasksModel->disconnect(this); // prevent further signal emission to not deref a nullptr https://bugs.kde.org/show_bug.cgi?id=435811 0241 m_tasksModel->deleteLater(); 0242 m_tasksModel = nullptr; 0243 0244 return true; 0245 } 0246 0247 void Settings::setEnvironmentFromExtensionMessage(const QJsonObject &data) 0248 { 0249 QString name = data.value(QStringLiteral("browserName")).toString(); 0250 0251 // Most chromium-based browsers just impersonate Chromium nowadays to keep websites from locking them out 0252 // so we'll need to make an educated guess from our parent process 0253 if (name == QLatin1String("chromium") || name == QLatin1String("chrome")) { 0254 const auto processInfo = KProcessList::processInfo(getppid()); 0255 if (processInfo.name().contains(QLatin1String("vivaldi"))) { 0256 name = QStringLiteral("vivaldi"); 0257 } else if (processInfo.name().contains(QLatin1String("brave"))) { 0258 name = QStringLiteral("brave"); 0259 } else if (processInfo.name().contains(QLatin1String("edge"))) { 0260 name = QStringLiteral("edge"); 0261 } 0262 } 0263 0264 m_environment = Settings::environmentNames.key(name, Settings::Environment::Unknown); 0265 m_currentEnvironment = Settings::environmentDescriptions.value(m_environment); 0266 0267 if (qApp->desktopFileName().isEmpty()) { 0268 qApp->setApplicationName(m_currentEnvironment.applicationName); 0269 qApp->setApplicationDisplayName(m_currentEnvironment.applicationDisplayName); 0270 qApp->setDesktopFileName(m_currentEnvironment.desktopFileName); 0271 qApp->setWindowIcon(QIcon::fromTheme(m_currentEnvironment.iconName)); 0272 // TODO remove? 0273 qApp->setOrganizationDomain(m_currentEnvironment.organizationDomain); 0274 qApp->setOrganizationName(m_currentEnvironment.organizationName); 0275 } 0276 } 0277 0278 QJsonObject Settings::handleData(int serial, const QString &event, const QJsonObject &data) 0279 { 0280 Q_UNUSED(serial) 0281 Q_UNUSED(data) 0282 0283 QJsonObject ret; 0284 0285 if (event == QLatin1String("getSubsystemStatus")) { 0286 // should we add a PluginManager::knownSubsystems() that returns a QList<AbstractBrowserPlugin*>? 0287 const QStringList subsystems = PluginManager::self().knownPluginSubsystems(); 0288 for (const QString &subsystem : subsystems) { 0289 const AbstractBrowserPlugin *plugin = PluginManager::self().pluginForSubsystem(subsystem); 0290 0291 QJsonObject details = plugin->status(); 0292 details.insert(QStringLiteral("version"), plugin->protocolVersion()); 0293 details.insert(QStringLiteral("loaded"), plugin->isLoaded()); 0294 ret.insert(subsystem, details); 0295 } 0296 } else if (event == QLatin1String("getVersion")) { 0297 ret.insert(QStringLiteral("host"), QStringLiteral(HOST_VERSION_STRING)); 0298 } 0299 0300 return ret; 0301 } 0302 0303 Settings::Environment Settings::environment() const 0304 { 0305 return m_environment; 0306 } 0307 0308 bool Settings::pluginEnabled(const QString &subsystem) const 0309 { 0310 return settingsForPlugin(subsystem).value(QStringLiteral("enabled")).toBool(); 0311 } 0312 0313 QJsonObject Settings::settingsForPlugin(const QString &subsystem) const 0314 { 0315 return m_settings.value(subsystem).toObject(); 0316 }