diff --git a/extension/_locales/en/messages.json b/extension/_locales/en/messages.json index 0601a6f0..0f68e873 100644 --- a/extension/_locales/en/messages.json +++ b/extension/_locales/en/messages.json @@ -1,183 +1,192 @@ { "store_description": { "description": "The extension description on the extension store", "message": "Multitask efficiently by controlling browser functions from the desktop, even while Chrome is in the background. Manage audio and video playback, check downloads in the notification area, send files to your phone using KDE Connect and more inside the KDE Plasma Desktop!\\n\\nThe plasma-browser-integration package must be installed for this extension to work. It should be available from your distribution's package manager when running Plasma 5.13 or later.\\n\\nNOTE: This extension is not supported on Debian." }, "browseraction_title": { "description": "Title for toolbar popup", "message": "Plasma Browser Integration" }, "options_title": { "description": "Title for settings page", "message": "Plasma Integration Settings" }, "options_save_failed": { "message": "Saving settings failed" }, "options_save_success": { "message": "Settings successfully saved" }, "options_not_supported_os": { "message": "This extension is not supported on this operating system." }, "options_tab_general": { "description": "The 'General settings' tab in settings", "message": "General" }, "options_tab_about": { "description": "The 'About this plugin' tab in settings", "message": "About" }, "options_plugin_mpris_title": { "description": "Title for Media Controls plugin", "message": "Media Controls" }, "options_plugin_mpris_description": { "description": "Description for Media Controls plugin", "message": "Lets you control video and audio players in websites using the Media Controller plasmoid." }, "options_plugin_mpris_media_sessions_title": { "description": "Title for MediaSessions API Control plugin", "message": "Enhanced Media Controls" }, "options_plugin_mpris_media_sessions_description": { "description": "Description for MediaSessions API Control plugin", "message": "Extract metadata and thumbnails of currently playing content." }, "options_plugin_kdeconnect_title": { "description": "Title for KDE Connect plugin", "message": "Send via KDE Connect" }, "options_plugin_kdeconnect_description": { "description": "Description for KDE Connect plugin", "message": "Adds a context menu entry to links enabling you to send them to your phone and other paired devices using KDE Connect." }, "options_plugin_downloads_title": { "description": "Title for Downloads plugin", "message": "Show downloads in notification area" }, "options_plugin_downloads_saveOriginUrl": { "description": "Option for saving download source URL in file metadata", "message": "Save URL a file was downloaded from in the file's attributes" }, "options_plugin_downloads_saveOriginUrl_description": { "message": "Note: The URL may contain sensitive information that could be disclosed when the file is accessible by or shared with others" }, "options_plugin_tabsrunner_title": { "description": "Title for Browser Tabs KRunner plugin", "message": "Find browser tabs in “Run Command” window" }, "options_plugin_tabsrunner_description": { "description": "Description for Browser Tabs KRunner plugin", "message": "Make sure the “Browser Tabs” module is enabled in Plasma Search settings." }, "options_plugin_purpose_title": { "description": "Title for Purpose / Web Share plugin", "message": "Content Sharing" }, "options_plugin_purpose_description": { "description": "Description for Purpose / Web Share plugin", "message": "Adds a \"Share...\" context menu entry and allows websites to open a dialog for sharing contents using the Web Share API." }, "options_plugin_breezeScrollBars_title": { "description": "Title for Breeze style scroll bars plugin", "message": "Use Breeze-style scroll bars" }, "options_plugin_breezeScrollBars_description": { "description": "Description for Breeze style scroll bars plugin", "message": "This may interfere with the appearance of websites that already apply a custom styling to their scroll bars." }, + "options_about_host_version": { + "description": "Version of extension native host", + "message": "Host version: $1" + }, + "options_about_extension_version": { + "description": "Version of browser extension", + "message": "Extension version: $1" + }, + "options_about_copyright": { "message": "© 2017-2019 Kai Uwe Broulik and David Edmundson" }, "options_about_license": { "message": "License: GNU General Public License Version 3" }, "options_about_translated_by": { "message": "Translated by: $1" }, "options_about_translators": { "description": "Name of translators", "message": "Your names" }, "options_about_created_by_kde": { "message": "This browser extension was created by the KDE Community. You can find more information about this project on the KDE Community Wiki." }, "options_about_bugs": { "message": "If you find an issue, please check the list of open bugs and then file a bug report." }, "options_about_kde": { "description": "KDE description taken from kaboutkdedialog_p.h in kmxlgui", "message": "KDE is a world-wide community of software engineers, artists, writers, translators and creators who are committed to Free Software development. KDE produces the Plasma desktop environment, hundreds of applications, and the many software libraries that support them. KDE is a cooperative enterprise: no single entity controls its direction or products. Instead, we work together to achieve the common goal of building the world's finest Free Software. Everyone is welcome to join and contribute to KDE, including you. Visit $3 for more information about the KDE community and the software we produce." }, "options_about_donate": { "message": "If you like what you saw, please consider donating to KDE, so we can continue to make the best free software possible." }, "kdeconnect_open_via": { "description": "Context menu, open link on device whose name we don't (yet) know", "message": "Open via KDE Connect" }, "kdeconnect_open_device": { "description": "Context menu, open link on device $1, similar to 'Open in New Tab'", "message": "Open on '$1'" }, "purpose_share": { "description": "Context menu, share link or page via Purpose framework", "message": "Share..." }, "purpose_share_finished_title": { "description": "Title of share finished notification", "message": "Content Shared" }, "purpose_share_finished_text": { "description": "Text of the share finished notification", "message": "The shared content link ($1) has been copied to the clipboard." }, "purpose_share_failed_title": { "description": "Title of share failed notification", "message": "Sharing Failed" }, "purpose_share_failed_text": { "description": "Text of share failed notification", "message": "Could not share this content: $1" }, "general_error_unknown": { "description": "An unknown error occurred, usually used when an error message by the system is not provided", "message": "Unknown Error" }, "general_error_not_supported_os_title": { "message": "Unsupported operating system" }, "general_error_not_supported_os": { "message": "This extension is only supported on Linux and FreeBSD." }, "general_error_startup_failed_title": { "description": "Title for failure to start plasma-browser-integration-host binary", "message": "Failed to connect to the native host." }, "general_error_startup_failed": { "description": "Description for failure to start plasma-browser-integration-host binary", "message": "Make sure the 'plasma-browser-integration' package is installed correctly and that you are running Plasma 5.13 or later." }, "general_error_startup_failed_wiki_link": { "message": "Visit project wiki page for more information" }, "general_error_host_disconnected_title": { "description": "Title for plasma-browser-integration-host binary unexpectedly closing/crashing", "message": "The native host disconnected unexpectedly." } } diff --git a/extension/extension.js b/extension/extension.js index 6e35f88a..70b34d0c 100644 --- a/extension/extension.js +++ b/extension/extension.js @@ -1,254 +1,258 @@ /* Copyright (C) 2017 Kai Uwe Broulik 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 3 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 . */ function sendEnvironment() { var browser = ""; var ua = navigator.userAgent; // Try to match the most derived first if (ua.match(/vivaldi/i)) { browser = "vivaldi"; } else if(ua.match(/OPR/i)) { browser = "opera"; } else if(ua.match(/chrome/i)) { browser = "chromium"; // Apparently there is no better way to distinuish chromium from chrome for (i in window.navigator.plugins) { if (window.navigator.plugins[i].name === "Chrome PDF Viewer") { browser = "chrome"; break; } } } else if(ua.match(/firefox/i)) { browser = "firefox"; } sendPortMessage("settings", "setEnvironment", {browserName: browser}); } function sendSettings() { storage.get(DEFAULT_EXTENSION_SETTINGS, function (items) { if (chrome.runtime.lastError) { console.warn("Failed to load settings"); return; } sendPortMessage("settings", "changed", items); }); } // activates giveb tab and raises its window, used by tabs runner and mpris Raise command function raiseTab(tabId) { // first activate the tab, this means it's current in its window chrome.tabs.update(tabId, {active: true}, function (tab) { if (chrome.runtime.lastError || !tab) { // this "lastError" stuff feels so archaic // failed to update return; } // then raise the tab's window too chrome.windows.update(tab.windowId, {focused: true}); }); } // Debug // ------------------------------------------------------------------------ // addCallback("debug", "debug", function(payload) { console.log("From host:", payload.message); } ) addCallback("debug", "warning", function(payload) { console.warn("From host:", payload.message); } ) // System // ------------------------------------------------------------------------ // // When connecting to native host fails (e.g. not installed), we immediately get a disconnect // event immediately afterwards. Also avoid infinite restart loop then. var receivedMessageOnce = false; var portStatus = ""; var portLastErrorMessage = undefined; function updateBrowserAction() { let enableAction = false; if (portStatus === "UNSUPPORTED_OS" || portStatus === "STARTUP_FAILED") { chrome.browserAction.setIcon({ path: { "16": "icons/plasma-disabled-16.png", "32": "icons/plasma-disabled-32.png", "48": "icons/plasma-disabled-48.png", "128": "icons/plasma-disabled-128.png" } }); enableAction = true; } if (portLastErrorMessage) { chrome.browserAction.setBadgeText({ text: "!" }); chrome.browserAction.setBadgeBackgroundColor({ color: "#da4453" }); // breeze "negative" color enableAction = true; } else { chrome.browserAction.setBadgeText({ text: "" }); } if (enableAction) { chrome.browserAction.enable(); } else { chrome.browserAction.disable(); } } updateBrowserAction(); // Check for supported platform to avoid loading it on e.g. Windows and then failing // when the extension got synced to another device and then failing chrome.runtime.getPlatformInfo(function (info) { if (!SUPPORTED_PLATFORMS.includes(info.os)) { console.log("This extension is not supported on", info.os); portStatus = "UNSUPPORTED_OS"; updateBrowserAction(); return; } connectHost(); }); function connectHost() { port = chrome.runtime.connectNative("org.kde.plasma.browser_integration"); port.onMessage.addListener(function (message) { var subsystem = message.subsystem; var action = message.action; let isReply = message.hasOwnProperty("replyToSerial"); let replyToSerial = message.replyToSerial; if (!isReply && (!subsystem || !action)) { return; } if (portStatus) { portStatus = ""; updateBrowserAction(); } receivedMessageOnce = true; if (isReply) { let replyResolver = pendingMessageReplyResolvers[replyToSerial]; if (replyResolver) { replyResolver(message.payload); delete pendingMessageReplyResolvers[replyToSerial]; } else { console.warn("There is no reply resolver for message with serial", replyToSerial); } return; } if (callbacks[subsystem] && callbacks[subsystem][action]) { callbacks[subsystem][action](message.payload, action); } else { console.warn("Don't know what to do with host message", subsystem, action); } }); port.onDisconnect.addListener(function() { var error = chrome.runtime.lastError; console.warn("Host disconnected", error); // Remove all kde connect menu entries since they won't work without a host try { for (let device of kdeConnectDevices) { chrome.contextMenus.remove(kdeConnectMenuIdPrefix + device); } } catch (e) { console.warn("Failed to cleanup after port disconnect", e); } kdeConnectDevices = []; if (receivedMessageOnce) { portLastErrorMessage = error && error.message || "UNKNOWN"; portStatus = "DISCONNECTED"; console.log("Auto-restarting it"); connectHost(); } else { portLastErrorMessage = ""; portStatus = "STARTUP_FAILED"; console.warn("Not auto-restarting host as we haven't received any message from it before. Check that it's working/installed correctly"); } updateBrowserAction(); }); sendEnvironment(); sendSettings(); sendDownloads(); } addRuntimeCallback("settings", "changed", function () { // we could also just reload our extension :) // but this also causes the settings dialog to quit //chrome.runtime.reload(); sendSettings(); }); addRuntimeCallback("settings", "openKRunnerSettings", function () { sendPortMessage("settings", "openKRunnerSettings"); }); addRuntimeCallback("settings", "getSubsystemStatus", (message, sender, action) => { return sendPortMessageWithReply("settings", "getSubsystemStatus"); }); +addRuntimeCallback("settings", "getVersion", () => { + return sendPortMessageWithReply("settings", "getVersion"); +}); + addRuntimeCallback("browserAction", "getStatus", (message) => { let info = { portStatus, portLastErrorMessage }; return Promise.resolve(info); }); addRuntimeCallback("browserAction", "ready", () => { // HACK there's no way to tell whether the browser action popup got closed // None of onunload, onbeforeunload, onvisibilitychanged are fired. // Instead, we create a port once the browser action is ready and then // listen for the port being disconnected. let browserActionPort = chrome.runtime.connect({ name: "browserActionPort" }); browserActionPort.onDisconnect.addListener((port) => { if (port.name !== "browserActionPort") { return; } // disabling the browser action immediately when opening it // causes opening to fail on Firefox, so clear the error only when it's being closed. portLastErrorMessage = ""; updateBrowserAction(); }); }); diff --git a/extension/options.html b/extension/options.html index 2f979d97..c192154c 100644 --- a/extension/options.html +++ b/extension/options.html @@ -1,111 +1,117 @@

I18N

I18N

I18N

I18N

I18N
  • I18N

  • I18N

  • I18N

  • I18N

  • I18N

  • I18N

  • I18N

+ +

+ I18N
+ I18N +

+

I18N
I18N
I18N

I18N

I18N

I18N

I18N

diff --git a/extension/options.js b/extension/options.js index d5ec018c..ac662d81 100644 --- a/extension/options.js +++ b/extension/options.js @@ -1,265 +1,279 @@ /* Copyright (C) 2017 Kai Uwe Broulik Copyright (C) 2018 David Edmundson 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 3 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 . */ var storage = (IS_FIREFOX ? chrome.storage.local : chrome.storage.sync); function tabClicked(tabbar, tabbutton) { tabbar.buttons.forEach(function (button) { var tablink = button.dataset.tabLink var tabtarget = document.querySelector("[data-tab-id=" + tablink + "]"); if (tabbutton == button) { button.classList.add("active"); tabtarget.classList.add("active"); } else { button.classList.remove("active"); tabtarget.classList.remove("active"); } }); } function loadSettings(cb) { storage.get(DEFAULT_EXTENSION_SETTINGS, function (items) { if (chrome.runtime.lastError) { if (typeof cb === "function") { cb(false); } return; } for (let key in items) { if (!items.hasOwnProperty(key)) { continue; } let controls = document.querySelectorAll("[data-extension=" + key + "]"); for (let control of controls) { let settingsKey = control.dataset.settingsKey; if (!settingsKey) { console.warn("Invalid settings key in", control, "cannot load this"); continue; } let value = items[key][settingsKey] if (control.type === "checkbox") { control.checked = !!value; } else { if (value === true) { control.value = "TRUE"; } else if (value === false) { control.value = "FALSE"; } else { control.value = value; } } updateDependencies(control, key, settingsKey); control.addEventListener("change", () => { let saveMessage = document.getElementById("save-message"); saveMessage.innerText = ""; updateDependencies(control, key, settingsKey); saveSettings((error) => { if (error) { try { saveMessage.innerText = chrome.i18n.getMessage("options_save_failed"); } catch (e) { // When the extension is reloaded, any call to extension APIs throws, make sure we show at least some form of error saveMessage.innerText = "Saving settings failed (" + (error || e) + ")"; } return; } sendMessage("settings", "changed"); }); }); } } if (typeof cb === "function") { cb(true); } }); } function saveSettings(cb) { var settings = {}; let controls = document.querySelectorAll("[data-extension]"); for (let control of controls) { let extension = control.dataset.extension; if (!DEFAULT_EXTENSION_SETTINGS.hasOwnProperty(extension)) { console.warn("Cannot save settings for extension", extension, "which isn't in DEFAULT_EXTENSION_SETTINGS"); continue; } let settingsKey = control.dataset.settingsKey; if (!settingsKey) { console.warn("Invalid settings key in", control, "cannot save this"); continue; } if (!settings[extension]) { settings[extension] = {}; } if (!DEFAULT_EXTENSION_SETTINGS[extension].hasOwnProperty(settingsKey)) { console.warn("Cannot save settings key", settingsKey, "in extension", extension, "which isn't in DEFAULT_EXTENSION_SETTINGS"); continue; } if (control.type === "checkbox") { settings[extension][settingsKey] = control.checked; } else { let value = control.value; if (value === "TRUE") { value = true; } else if (value === "FALSE") { value = false; } settings[extension][settingsKey] = value; } } try { storage.set(settings, function () { return cb(chrome.runtime.lastError); }); // When the extension is reloaded, any call to extension APIs throws } catch (e) { cb(e); } } function updateDependencies(control, extension, settingsKey) { // Update all depending controls let value = control.type === "checkbox" ? control.checked : control.value; if (value === true) { value = "TRUE"; } else if (value === false) { value = "FALSE"; } let dependencies = document.querySelectorAll("[data-depends-extension=" + extension + "][data-depends-settings-key=" + settingsKey + "]"); for (let dependency of dependencies) { dependency.disabled = (value != dependency.dataset.dependsSettingsValue); } } document.addEventListener("DOMContentLoaded", function () { // poor man's tab widget :) document.querySelectorAll(".tabbar").forEach(function (tabbar) { tabbar.buttons = []; tabbar.querySelectorAll("[data-tab-link]").forEach(function (button) { var tablink = button.dataset.tabLink var tabtarget = document.querySelector("[data-tab-id=" + tablink + "]"); button.addEventListener("click", function (event) { tabClicked(tabbar, button); event.preventDefault(); }); tabbar.buttons.push(button); // start with the one tab page that is active if (tabtarget.classList.contains("active")) { tabClicked(tabbar, button); } }); }); if (IS_FIREFOX) { document.querySelectorAll("[data-not-show-in=firefox]").forEach(function (item) { item.style.display = "none"; }); } // check whether the platform is supported before loading and activating settings chrome.runtime.getPlatformInfo(function (info) { if (!SUPPORTED_PLATFORMS.includes(info.os)) { document.body.classList.add("os-not-supported"); return; } loadSettings(); // When getSubsystemStatus fails we assume it's an old host without any of the new features // for which we added the requires-extension attributes. Disable all of them initially // and then have the supported ones enabled below. document.querySelectorAll("[data-requires-extension]").forEach((item) => { item.classList.add("not-supported", "by-host"); }); sendMessage("settings", "getSubsystemStatus").then((status) => { document.querySelectorAll("[data-requires-extension]").forEach((item) => { let requiresExtension = item.dataset.requiresExtension; if (requiresExtension && !status.hasOwnProperty(requiresExtension)) { console.log("Extension", requiresExtension, "is not supported by this version of the host"); return; // continue } let requiresMinimumVersion = Number(item.dataset.requiresExtensionVersionMinimum); if (requiresMinimumVersion) { let runningVersion = status[requiresExtension].version; if (runningVersion < requiresMinimumVersion) { console.log("Extension", requiresExtension, "of version", requiresMinimumVersion, "is required but only", runningVersion, "is present in the host"); return; // continue } } item.classList.remove("not-supported", "by-host"); }); }).catch((e) => { // The host is most likely not working correctly // If we run this against an older host which doesn't support message replies // this handler is never entered, so we really encountered an error just now! console.warn("Failed to determine subsystem status", e); document.body.classList.add("startup-failure"); }); + + Promise.all([ + sendMessage("settings", "getVersion"), + chrome.runtime.getManifest() + ]).then((results) => { + const versionInfo = results[0]; + const manifest = results[1]; + + document.getElementById("version-info-host").innerText = chrome.i18n.getMessage("options_about_host_version", +versionInfo.host); + document.getElementById("version-info-extension").innerText = chrome.i18n.getMessage("options_about_extension_version", manifest.version); + + document.getElementById("version-info").classList.remove("not-supported"); + }); }); document.getElementById("open-krunner-settings").addEventListener("click", function (event) { sendMessage("settings", "openKRunnerSettings"); event.preventDefault(); }); // Make translators credit behave like the one in KAboutData var translatorsAboutData = ""; var translators = chrome.i18n.getMessage("options_about_translators"); if (translators && translators !== "Your names") { translatorsAboutData = chrome.i18n.getMessage("options_about_translated_by", translators) } var translatorsAboutDataItem = document.getElementById("translators-aboutdata"); if (translatorsAboutData) { translatorsAboutDataItem.innerText = translatorsAboutData; } else { translatorsAboutDataItem.style.display = "none"; } }); diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index 06499d48..fbe4345c 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -1,35 +1,37 @@ add_definitions(-DTRANSLATION_DOMAIN=\"plasma-browser-integration-host\") +configure_file(config-host.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-host.h) + set(HOST_SOURCES main.cpp connection.cpp pluginmanager.cpp settings.cpp mprisplugin.cpp abstractbrowserplugin.cpp kdeconnectplugin.cpp downloadplugin.cpp downloadjob.cpp tabsrunnerplugin.cpp purposeplugin.cpp ) qt5_add_dbus_adaptor(HOST_SOURCES ../dbus/org.kde.plasma.browser_integration.TabsRunner.xml tabsrunnerplugin.h TabsRunnerPlugin) qt5_add_dbus_adaptor(HOST_SOURCES ../dbus/org.kde.plasma.browser_integration.Settings.xml settings.h Settings) qt5_add_dbus_adaptor(HOST_SOURCES ../dbus/org.mpris.MediaPlayer2.xml mprisplugin.h MPrisPlugin mprisroot MPrisRoot) qt5_add_dbus_adaptor(HOST_SOURCES ../dbus/org.mpris.MediaPlayer2.Player.xml mprisplugin.h MPrisPlugin mprisplayer MPrisPlayer) add_executable(plasma-browser-integration-host ${HOST_SOURCES}) target_link_libraries( plasma-browser-integration-host Qt5::DBus Qt5::Gui Qt5::Widgets KF5::Crash KF5::I18n KF5::KIOCore KF5::PurposeWidgets KF5::FileMetaData ) install(TARGETS plasma-browser-integration-host ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/host/config-host.h.cmake b/host/config-host.h.cmake new file mode 100644 index 00000000..91769e3d --- /dev/null +++ b/host/config-host.h.cmake @@ -0,0 +1 @@ +#define HOST_VERSION_STRING "${PROJECT_VERSION}" diff --git a/host/settings.cpp b/host/settings.cpp index 73f340bd..f5d0e466 100644 --- a/host/settings.cpp +++ b/host/settings.cpp @@ -1,180 +1,184 @@ /* Copyright (C) 2017 by Kai Uwe Broulik Copyright (C) 2017 by David Edmundson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "settings.h" #include #include #include #include "pluginmanager.h" #include "settingsadaptor.h" +#include + const QMap Settings::environmentNames = { {Settings::Environment::Chrome, QStringLiteral("chrome")}, {Settings::Environment::Chromium, QStringLiteral("chromium")}, {Settings::Environment::Firefox, QStringLiteral("firefox")}, {Settings::Environment::Opera, QStringLiteral("opera")}, {Settings::Environment::Vivaldi, QStringLiteral("vivaldi")}, }; const QMap Settings::environmentDescriptions = { {Settings::Environment::Chrome, { QStringLiteral("google-chrome"), QStringLiteral("Google Chrome"), QStringLiteral("google-chrome"), QStringLiteral("google.com"), QStringLiteral("Google") } }, {Settings::Environment::Chromium, { QStringLiteral("chromium-browser"), QStringLiteral("Chromium"), QStringLiteral("chromium-browser"), QStringLiteral("google.com"), QStringLiteral("Google") } }, {Settings::Environment::Firefox, { QStringLiteral("firefox"), QStringLiteral("Mozilla Firefox"), QStringLiteral("firefox"), QStringLiteral("mozilla.org"), QStringLiteral("Mozilla") } }, {Settings::Environment::Opera, { QStringLiteral("opera"), QStringLiteral("Opera"), QStringLiteral("opera"), QStringLiteral("opera.com"), QStringLiteral("Opera") } }, {Settings::Environment::Vivaldi, { QStringLiteral("vivaldi"), QStringLiteral("Vivaldi"), // This is what the official package on their website uses QStringLiteral("vivaldi-stable"), QStringLiteral("vivaldi.com"), QStringLiteral("Vivaldi") } } }; Settings::Settings() : AbstractBrowserPlugin(QStringLiteral("settings"), 1, nullptr) { new SettingsAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), this); } Settings &Settings::self() { static Settings s_self; return s_self; } void Settings::handleData(const QString &event, const QJsonObject &data) { if (event == QLatin1String("changed")) { m_settings = data; for (auto it = data.begin(), end = data.end(); it != end; ++it) { const QString &subsystem = it.key(); const QJsonObject &settingsObject = it->toObject(); const QJsonValue enabledVariant = settingsObject.value(QStringLiteral("enabled")); // probably protocol overhead, not a plugin setting, skip. if (enabledVariant.type() == QJsonValue::Undefined) { continue; } auto *plugin = PluginManager::self().pluginForSubsystem(subsystem); if (!plugin) { continue; } if (enabledVariant.toBool()) { PluginManager::self().loadPlugin(plugin); } else { PluginManager::self().unloadPlugin(plugin); } PluginManager::self().settingsChanged(plugin, settingsObject); } emit changed(data); } else if (event == QLatin1String("openKRunnerSettings")) { QProcess::startDetached(QStringLiteral("kcmshell5"), {QStringLiteral("kcm_plasmasearch")}); } else if (event == QLatin1String("setEnvironment")) { QString name = data[QStringLiteral("browserName")].toString(); m_environment = Settings::environmentNames.key(name, Settings::Environment::Unknown); m_currentEnvironment = Settings::environmentDescriptions.value(m_environment); qApp->setApplicationName(m_currentEnvironment.applicationName); qApp->setApplicationDisplayName(m_currentEnvironment.applicationDisplayName); qApp->setDesktopFileName(m_currentEnvironment.desktopFileName); qApp->setOrganizationDomain(m_currentEnvironment.organizationDomain); qApp->setOrganizationName(m_currentEnvironment.organizationName); } } QJsonObject Settings::handleData(int serial, const QString &event, const QJsonObject &data) { Q_UNUSED(serial) Q_UNUSED(data) QJsonObject ret; if (event == QLatin1String("getSubsystemStatus")) { // should we add a PluginManager::knownSubsystems() that returns a QList? const QStringList subsystems = PluginManager::self().knownPluginSubsystems(); for (const QString &subsystem : subsystems) { const AbstractBrowserPlugin *plugin = PluginManager::self().pluginForSubsystem(subsystem); QJsonObject details{ {QStringLiteral("version"), plugin->protocolVersion()}, {QStringLiteral("loaded"), plugin->isLoaded()} }; ret.insert(subsystem, details); } + } else if (event == QLatin1String("getVersion")) { + ret.insert(QStringLiteral("host"), QStringLiteral(HOST_VERSION_STRING)); } return ret; } Settings::Environment Settings::environment() const { return m_environment; } QString Settings::environmentString() const { return Settings::environmentNames.value(m_environment); } bool Settings::pluginEnabled(const QString &subsystem) const { return settingsForPlugin(subsystem).value(QStringLiteral("enabled")).toBool(); } QJsonObject Settings::settingsForPlugin(const QString &subsystem) const { return m_settings.value(subsystem).toObject(); }