+
+
+
+
+
+
diff --git a/extension/action_popup.js b/extension/action_popup.js
new file mode 100644
index 00000000..a2510d59
--- /dev/null
+++ b/extension/action_popup.js
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2019 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 .
+ */
+
+document.addEventListener("DOMContentLoaded", () => {
+
+ sendMessage("browserAction", "getStatus").then((status) => {
+
+ switch (status.portStatus) {
+ case "UNSUPPORTED_OS":
+ document.getElementById("unsupported_os_error").classList.remove("hidden");
+ break;
+
+ case "STARTUP_FAILED":
+ document.getElementById("startup_error").classList.remove("hidden");
+ break;
+
+ default:
+ document.getElementById("main").classList.remove("hidden");
+
+ let errorText = status.portLastErrorMessage;
+ if (errorText === "UNKNOWN") {
+ errorText = chrome.i18n.getMessage("general_error_unknown");
+ }
+
+ if (errorText) {
+ document.getElementById("runtime_error_text").innerText = errorText;
+ document.getElementById("runtime_error").classList.remove("hidden");
+ }
+
+ break;
+ }
+
+ // HACK so the extension can tell we closed, see "browserAction" "ready" callback in extension.js
+ chrome.runtime.onConnect.addListener((port) => {
+ if (port.name !== "browserActionPort") {
+ return;
+ }
+
+ // do we need to do something with the port here?
+ });
+ sendMessage("browserAction", "ready");
+ });
+
+});
diff --git a/extension/constants.js b/extension/constants.js
index bed0f986..fee2332d 100644
--- a/extension/constants.js
+++ b/extension/constants.js
@@ -1,43 +1,44 @@
/*
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 .
*/
DEFAULT_EXTENSION_SETTINGS = {
mpris: {
enabled: true
},
mprisMediaSessions: {
enabled: true
},
kdeconnect: {
enabled: true
},
downloads: {
enabled: true
},
tabsrunner: {
enabled: true
},
breezeScrollBars: {
// this breaks pages in interesting ways, disable by default
enabled: false
}
};
IS_FIREFOX = (typeof InstallTrigger !== "undefined"); // heh.
+// NOTE if you change this, make sure to adjust the error message shown in action_popup.html
SUPPORTED_PLATFORMS = ["linux", "openbsd"];
diff --git a/extension/extension.js b/extension/extension.js
index b027a524..6e35f88a 100644
--- a/extension/extension.js
+++ b/extension/extension.js
@@ -1,187 +1,254 @@
/*
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
- for (let device of kdeConnectDevices) {
- chrome.contextMenus.remove(kdeConnectMenuIdPrefix + device);
+ try {
+ for (let device of kdeConnectDevices) {
+ chrome.contextMenus.remove(kdeConnectMenuIdPrefix + device);
+ }
+ } catch (e) {
+ console.warn("Failed to cleanup after port disconnect", e);
}
kdeConnectDevices = [];
- var reason = chrome.i18n.getMessage("general_error_unknown");
- if (error && error.message) {
- reason = error.message;
- }
-
- var message = receivedMessageOnce ? chrome.i18n.getMessage("general_error_port_disconnect", reason)
- : chrome.i18n.getMessage("general_error_port_startupfail");
-
- chrome.notifications.create(null, {
- type: "basic",
- title: chrome.i18n.getMessage("general_error_title"),
- message: message,
- iconUrl: "icons/sad-face-128.png"
- });
-
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("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/manifest.json b/extension/manifest.json
index 96777e22..eefab78b 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -1,67 +1,71 @@
{
"manifest_version": 2,
"name": "Plasma Integration",
"short_name": "Plasma",
"description": "Provides better integration with the KDE Plasma 5 desktop.",
"version": "1.5",
"default_locale": "en",
"author": "Kai Uwe Broulik ",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Wz6JigIdPBxPJvYrnjuKM3X4YEaUHgyOW2TM1G1Maxook1HO8vLFGhBR7g3jEQ7Yw9PTa6ZAa8J/I9a/1EQz0Ssx2+m3R9SvJfdKSCrCth8nwMpB8V8I5EhiUoW57kM0v9r/18Buem5cBIo0EnSAVCbmNG95R5jtg62P4+LNGEwHk5L7RSblXUN2hhUwXty4A98EXD6T2Pnpu1m8nRF2B1t5AcspSMV5ECnL9x8kT5bup1gJo0FGTz6C+1huDNRaI3OY1YDWyvCVRFXlAhuFERTW6siMdghK++kYM43H7cvJMFYnVVuffD9WNRsUCI9V0SrqYbPre/2nEl+8VUXVwIDAQAB",
"icons": {
"16": "icons/plasma-16.png",
"32": "icons/plasma-32.png",
"48": "icons/plasma-48.png",
"128": "icons/plasma-128.png"
},
"background": {
"scripts": [
"constants.js",
"extension-utils.js",
"extension-kdeconnect.js",
"extension-mpris.js",
"extension-downloads.js",
"extension-tabsrunner.js",
"extension.js"
],
"persistent": false
},
+ "browser_action": {
+ "default_popup": "action_popup.html"
+ },
+
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["constants.js", "content-utils.js", "content-script.js"],
"run_at":"document_start",
"all_frames": true,
"match_about_blank": true
}
],
"options_ui": {
"page": "options.html",
"chrome_style": true
},
"permissions": [
"nativeMessaging",
"notifications",
"storage",
"downloads",
"tabs",
"",
"contextMenus"
],
"applications": {
"gecko": {
"id": "plasma-browser-integration@kde.org",
"strict_min_version": "50.0"
}
},
"optional_permissions": [ "webRequest" ]
}