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();
}