diff --git a/extension/_locales/en/messages.json b/extension/_locales/en/messages.json --- a/extension/_locales/en/messages.json +++ b/extension/_locales/en/messages.json @@ -3,6 +3,33 @@ "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" + }, + "browseraction_not_supported_os_title": { + "message": "Unsupported operating system" + }, + "browseraction_not_supported_os": { + "message": "This extension is only supported on Linux and FreeBSD." + }, + "browseraction_startup_failed_title": { + "description": "Title for failure to start plasma-browser-integration-host binary", + "message": "Failed to connect to the native host." + }, + "browseraction_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." + }, + "browseraction_startup_failed_wiki_link": { + "message": "Visit project wiki page for more information" + }, + "browseraction_host_disconnected_title": { + "description": "Title for plasma-browser-integration-host binary unexpectedly closing/crashing", + "message": "The native host disconnected unexpectedly." + }, + "options_title": { "description": "Title for settings page", "message": "Plasma Integration Settings" diff --git a/extension/action_popup.html b/extension/action_popup.html new file mode 100644 --- /dev/null +++ b/extension/action_popup.html @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + +
+ I18N +
+ +
+ + + + + + + + + +
+ + + + diff --git a/extension/action_popup.css b/extension/action_popup.css new file mode 100644 --- /dev/null +++ b/extension/action_popup.css @@ -0,0 +1,39 @@ +body { + width: 24em; + /* prevent scroll bars*/ + overflow: hidden; + margin: 0; + /* apply the chrome default styling explicitly so it also works in Firefox */ + font-family: "Noto Sans", sans-serif; + font-size: 75%; +} + +.hidden { + display: none; +} + +header { + background: #1d99f3; + padding: 2px; + color: #fff; + text-align: center; +} + +section > header { + background: #F0F0F0; + color: #757777; + margin: 0 -1em .5em -1em; +} + +.message { + padding: 10px; +} +.message.with-icon::before { + content: ''; + height: 48px; + display: block; + background: center no-repeat; +} +.message.with-icon.error::before { + background-image: url('icons/sad-face-48.png'); +} diff --git a/extension/action_popup.js b/extension/action_popup.js new file mode 100644 --- /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 --- a/extension/constants.js +++ b/extension/constants.js @@ -40,4 +40,5 @@ 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 --- a/extension/extension.js +++ b/extension/extension.js @@ -87,11 +87,46 @@ // 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; } @@ -112,6 +147,11 @@ return; } + if (portStatus) { + portStatus = ""; + updateBrowserAction(); + } + receivedMessageOnce = true; if (isReply) { @@ -138,32 +178,28 @@ 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(); @@ -185,3 +221,34 @@ 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 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -34,6 +34,10 @@ "persistent": false }, + "browser_action": { + "default_popup": "action_popup.html" + }, + "content_scripts": [ { "matches": ["*://*/*"],