diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index 447bcd1..933b9bc 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -39,7 +39,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import dorkbox.systemTray.gnomeShell.DummyFile; -import dorkbox.systemTray.gnomeShell.Extension; +import dorkbox.systemTray.gnomeShell.LegacyExtension; import dorkbox.systemTray.ui.awt._AwtTray; import dorkbox.systemTray.ui.gtk._AppIndicatorNativeTray; import dorkbox.systemTray.ui.gtk._GtkStatusIconNativeTray; @@ -283,8 +283,8 @@ class SystemTray { Extension.install(); // this can be gnome3 on debian/kali - if (OSUtil.Linux.isKali()) { + LegacyExtension.install(); return selectTypeQuietly(TrayType.GtkStatusIcon); } @@ -294,8 +294,9 @@ class SystemTray { if (DEBUG) { logger.debug("Disabling the extension install. It won't work."); } - - Extension.ENABLE_EXTENSION_INSTALL = false; + } + else { + LegacyExtension.install(); } return selectTypeQuietly(TrayType.GtkStatusIcon); @@ -322,12 +323,8 @@ class SystemTray { if (!DummyFile.isPresent()) { DummyFile.install(); - if (!Extension.ENABLE_EXTENSION_INSTALL) { - logger.warn("You must log out-in for the system tray to work."); - } - else { - Extension.restartShell(); - } + // just restarting the shell is enough to get the system tray to work + LegacyExtension.restartShell(); } } @@ -919,7 +916,7 @@ class SystemTray { return; } } else if (isTrayType(trayType, TrayType.GtkStatusIcon)) { - if (!Extension.isInstalled()) { + if (!LegacyExtension.isInstalled()) { // Automatically install the extension for everyone except Arch. They are bonkers. Extension.ENABLE_EXTENSION_INSTALL = false; SystemTray.logger.info("You may need a work-around for showing the SystemTray icon - we suggest installing the " + diff --git a/src/dorkbox/systemTray/gnomeShell/Extension.java b/src/dorkbox/systemTray/gnomeShell/Extension.java deleted file mode 100644 index bda5917..0000000 --- a/src/dorkbox/systemTray/gnomeShell/Extension.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright 2015 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.systemTray.gnomeShell; - -import static dorkbox.systemTray.SystemTray.logger; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -import dorkbox.executor.ShellAsyncExecutor; -import dorkbox.executor.ShellExecutor; -import dorkbox.systemTray.SystemTray; -import dorkbox.util.IO; -import dorkbox.util.OSUtil; -import dorkbox.util.Property; - -@SuppressWarnings({"DanglingJavadoc", "WeakerAccess"}) -public -class Extension { - private static final String UID = "SystemTray@Dorkbox"; - public static final String DEFAULT_NAME = "SystemTray"; - - @Property - /** - * Permit the StatusTray icon to be displayed next to the clock by installing an extension. By default, gnome places the icon in the - * "notification drawer", which is a collapsible menu at (usually) bottom left corner of the screen. - */ - public static boolean ENABLE_EXTENSION_INSTALL = true; - - @Property - /** Permit the gnome-shell to be restarted when the extension is installed. */ - public static boolean ENABLE_SHELL_RESTART = true; - - @Property - /** Command to restart the gnome-shell. It is recommended to start it in the background (hence '&') */ - public static String SHELL_RESTART_COMMAND = "gnome-shell --replace &"; - - public static - List getEnabledExtensions() { - // gsettings get org.gnome.shell enabled-extensions - final ShellExecutor gsettings = new ShellExecutor(); - gsettings.setExecutable("gsettings"); - gsettings.addArgument("get"); - gsettings.addArgument("org.gnome.shell"); - gsettings.addArgument("enabled-extensions"); - gsettings.start(); - - String output = gsettings.getOutput(); - - // now we have to enable us if we aren't already enabled - - // gsettings get org.gnome.shell enabled-extensions - // defaults are: - // - fedora 23: ['background-logo@fedorahosted.org'] on - // - openSuse: - // - Ubuntu Gnome 16.04: @as [] - - final StringBuilder stringBuilder = new StringBuilder(output); - - // have to remove the end first, otherwise we would have to re-index the location of the ] - - // remove the last ] - int extensionIndex = output.indexOf("]"); - if (extensionIndex > 0) { - stringBuilder.delete(extensionIndex, stringBuilder.length()); - } - - // strip off UP-TO plus the leading [ - extensionIndex = output.indexOf("["); - if (extensionIndex >= 0) { - stringBuilder.delete(0, extensionIndex+1); - } - - // should be 'background-logo@fedorahosted.org', 'zyx', 'abs' - // or nothing - - String installedExtensions = stringBuilder.toString(); - - // now just split the extensions into a list so it is easier to manage - - String[] split = installedExtensions - .split(", "); - for (int i = 0; i < split.length; i++) { - final String s = split[i]; - - int i1 = s.indexOf("'"); - int i2 = s.lastIndexOf("'"); - - if (i1 == 0 && i2 == s.length() - 1) { - split[i] = s.substring(1, s.length() - 1); - } - } - - ArrayList strings = new ArrayList(Arrays.asList(split)); - for (Iterator iterator = strings.iterator(); iterator.hasNext(); ) { - final String string = iterator.next(); - if (string.trim() - .isEmpty()) { - iterator.remove(); - } - } - - if (SystemTray.DEBUG) { - logger.debug("Installed extensions are: {}", strings); - } - - return strings; - } - - public static - void setEnabledExtensions(List extensions) { - StringBuilder stringBuilder = new StringBuilder("["); - - for (int i = 0, extensionsSize = extensions.size(), limit = extensionsSize-1; i < extensionsSize; i++) { - final String extension = extensions.get(i); - if (extension.isEmpty()) { - continue; - } - - stringBuilder.append("'") - .append(extension) - .append("'"); - - if (i < limit) { - stringBuilder.append(","); - } - } - stringBuilder.append("]"); - - - if (SystemTray.DEBUG) { - logger.debug("Setting installed extensions to: {}", stringBuilder.toString()); - } - - // gsettings set org.gnome.shell enabled-extensions "['SystemTray@Dorkbox']" - // gsettings set org.gnome.shell enabled-extensions "['background-logo@fedorahosted.org']" - // gsettings set org.gnome.shell enabled-extensions "['background-logo@fedorahosted.org', 'SystemTray@Dorkbox']" - final ShellExecutor setGsettings = new ShellExecutor(); - setGsettings.setExecutable("gsettings"); - setGsettings.addArgument("set"); - setGsettings.addArgument("org.gnome.shell"); - setGsettings.addArgument("enabled-extensions"); - setGsettings.addArgument(stringBuilder.toString()); - setGsettings.start(); - } - - public static - void restartShell() { - // in some situations, you can no longer restart the shell in wayland. You must logout-login for shell modifications to apply - // https://mail.gnome.org/archives/commits-list/2015-March/msg01019.html - - // HOWEVER, with wayland, shell-extensions that DO NO MODIFY THE GUI (we don't, we just add icons to it) - // are enabled without a shell restart. - - // if there are still difficulties, you can use the following - // gnome-shell-extension-tool -e SystemTray@Dorkbox - - if (OSUtil.DesktopEnv.isWayland()) { - int[] version = OSUtil.Linux.getUbuntuVersion(); - if (version[0] == 17) { - // ubuntu 17.04 is NOT WAYLAND (it's MIR) and ubuntu 17.10 is WAYLAND (and it's broken...) - return; - } - else { - if (SystemTray.DEBUG) { - logger.warn("Trying to restart the shell with an unknown version of wayland. Please create an issue with OS and debug information."); - } - else { - logger.warn("Trying to restart the shell with an unknown version of wayland. Please set `SystemTray.DEBUG=true;` then create an issue " + - "with OS and debug information."); - } - } - } - - - if (ENABLE_SHELL_RESTART) { - if (SystemTray.DEBUG) { - logger.debug("DEBUG mode enabled. You need to log-out/in or manually restart the shell via '{}' to apply the changes.", - SHELL_RESTART_COMMAND); - return; - } - - if (SystemTray.DEBUG) { - logger.debug("Restarting gnome-shell so tray notification changes can be applied."); - } - - // now we have to restart the gnome shell via bash in a background process - ShellAsyncExecutor.runShell(SHELL_RESTART_COMMAND); - - // We don't care when the shell restarts, since WHEN IT DOES restart, our extension will show our icon. - // Until then however, there will be errors which can be ignored, because the shell-restart means everything works. - } - } - - - /** - * topIcons will convert ALL icons to be at the top of the screen, so there is no reason to have both installed - * - * @return true if that extension is installed - */ - public static - boolean isInstalled() { - List enabledExtensions = getEnabledExtensions(); - return enabledExtensions.contains("topIcons@adel.gadllah@gmail.com") || enabledExtensions.contains(UID); - } - - - /** - * Only install a version that specifically moves only our icon next to the clock - */ - public static - void install() { - if (!ENABLE_EXTENSION_INSTALL) { - // note: Debian Gnome3 does NOT work! (tested on Debian 8.5 and 8.6 default installs). - return; - } - - if (SystemTray.DEBUG) { - SystemTray.logger.debug("Installing Gnome extension."); - } - - boolean hasTopIcons; - boolean hasSystemTray; - - // should just be 3.14.1 or 3.20 or similar - String gnomeVersion = OSUtil.DesktopEnv.getGnomeVersion(); - if (gnomeVersion == null) { - return; - } - - List enabledExtensions = getEnabledExtensions(); - hasTopIcons = enabledExtensions.contains("topIcons@adel.gadllah@gmail.com"); - hasSystemTray = enabledExtensions.contains(UID); - - if (hasTopIcons) { - // topIcons will convert ALL icons to be at the top of the screen, so there is no reason to have both installed - return; - } - - // have to copy the extension over and enable it. - String userHome = System.getProperty("user.home"); - - // where the extension is saved - final File file = new File(userHome + "/.local/share/gnome-shell/extensions/" + UID); - final File metaDatafile = new File(file, "metadata.json"); - final File extensionFile = new File(file, "extension.js"); - - - // have to create the metadata.json file (and make it so that it's **always** current). - // we do this via getting the shell version - - // We want "3.14" or "3.20" or whatever the latest version is (excluding the patch version info). - final int indexOf = gnomeVersion.indexOf('.'); - final int nextIndexOf = gnomeVersion.indexOf('.', indexOf + 1); - if (indexOf < nextIndexOf) { - gnomeVersion = gnomeVersion.substring(0, nextIndexOf); // will be 3.14 (without the trailing '.1'), for example - } - - String metadata = "{\n" + - " \"description\": \"Moves the java SystemTray icon from inside the notification drawer to alongside the " + - "clock.\",\n" + - " \"name\": \"Dorkbox SystemTray\",\n" + - " \"shell-version\": [\n" + - " \"" + gnomeVersion + "\"\n" + - " ],\n" + - " \"url\": \"https://github.com/dorkbox/SystemTray\",\n" + - " \"uuid\": \"" + UID + "\",\n" + - " \"version\": " + SystemTray.getVersion() + "\n" + - "}\n"; - - - logger.debug("Checking the gnome-shell extension"); - - if (hasSystemTray) { - if (SystemTray.DEBUG) { - logger.debug("Extension already installed, checking for upgrade"); - } - // have to check to see if the version is correct as well (otherwise we have to reinstall it) - // compat for java 1.6 - - StringBuilder builder = new StringBuilder(256); - BufferedReader bin = null; - try { - bin = new BufferedReader(new FileReader(metaDatafile)); - String line; - while ((line = bin.readLine()) != null) { - builder.append(line) - .append("\n"); - } - } catch (Exception ignored) { - } finally { - if (bin != null) { - try { - bin.close(); - } catch (IOException e) { - logger.error("Error closing: {}", bin, e); - } - } - } - - - // the metadata string we CHECK should equal the metadata string we PROVIDE - if (metadata.equals(builder.toString())) { - // this means that our version info, etc. is the same - there is no need to update anything - if (!SystemTray.DEBUG) { - return; - } else { - // if we are DEBUG, then we ALWAYS want to copy over our extension. We will have to manually restart the shell to see it - logger.debug("Always upgrading extension in DEBUG mode"); - hasSystemTray = false; - } - } - else { - // this means that we need to reinstall our extension, since either GNOME or US have changed versions since - // we last installed the extension. - logger.debug("Need to upgrade extension"); - } - } - - - // we get here if we are NOT installed, or if we are installed and our metadata is NOT THE SAME. (so we need to reinstall) - logger.debug("Installing gnome-shell extension"); - - - // need to make the extension location - if (!file.isDirectory()) { - final boolean mkdirs = file.mkdirs(); - if (!mkdirs) { - final String msg = "Unable to create extension location: " + file; - logger.error(msg); - return; - } - } - - // write out the metadata - BufferedWriter outputWriter = null; - try { - outputWriter = new BufferedWriter(new FileWriter(metaDatafile, false)); - // FileWriter always assumes default encoding is OK - outputWriter.write(metadata); - outputWriter.flush(); - outputWriter.close(); - } catch (IOException e) { - logger.error("Error installing extension metadata file", e); - } finally { - if (outputWriter != null) { - try { - outputWriter.close(); - } catch (IOException e) { - logger.error("Error closing: {}", outputWriter, e); - } - } - } - - - - if (!hasSystemTray) { - // copies our provided extension.js file to the correct location on disk - InputStream reader = null; - FileOutputStream fileOutputStream = null; - try { - reader = Extension.class.getResourceAsStream("extension.js"); - fileOutputStream = new FileOutputStream(extensionFile); - - if (reader == null) { - logger.error("The GnomeShell extension.js file cannot be found. Something is severely wrong."); - return; - } - - IO.copyStream(reader, fileOutputStream); - } catch (FileNotFoundException e) { - logger.error("Cannot find gnome-shell extension", e); - } catch (IOException e) { - logger.error("Unable to get gnome-shell extension", e); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - logger.error("Error closing: {}", reader, e); - } - } - if (fileOutputStream != null) { - try { - fileOutputStream.close(); - } catch (IOException e) { - logger.error("Error closing: {}", fileOutputStream, e); - } - } - } - - logger.debug("Enabling extension in gnome-shell"); - - - if (!enabledExtensions.contains(UID)) { - enabledExtensions.add(UID); - } - setEnabledExtensions(enabledExtensions); - - restartShell(); - } - } - - public static - void unInstall() { - if (!ENABLE_EXTENSION_INSTALL || !OSUtil.DesktopEnv.isGnome()) { - return; - } - - List enabledExtensions = getEnabledExtensions(); - if (enabledExtensions.contains(UID)) { - enabledExtensions.remove(UID); - - setEnabledExtensions(enabledExtensions); - - restartShell(); - } - } -} diff --git a/src/dorkbox/systemTray/gnomeShell/ExtensionSupport.java b/src/dorkbox/systemTray/gnomeShell/ExtensionSupport.java new file mode 100644 index 0000000..6278ac2 --- /dev/null +++ b/src/dorkbox/systemTray/gnomeShell/ExtensionSupport.java @@ -0,0 +1,432 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.gnomeShell; + +import static dorkbox.systemTray.SystemTray.logger; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import dorkbox.executor.ShellAsyncExecutor; +import dorkbox.executor.ShellExecutor; +import dorkbox.systemTray.SystemTray; +import dorkbox.util.IO; +import dorkbox.util.OSUtil; + +@SuppressWarnings({"DanglingJavadoc", "WeakerAccess"}) +public +class ExtensionSupport { + public static + List getEnabledExtensions() { + // gsettings get org.gnome.shell enabled-extensions + final ShellExecutor gsettings = new ShellExecutor(); + gsettings.setExecutable("gsettings"); + gsettings.addArgument("get"); + gsettings.addArgument("org.gnome.shell"); + gsettings.addArgument("enabled-extensions"); + gsettings.start(); + + String output = gsettings.getOutput(); + + // now we have to enable us if we aren't already enabled + + // gsettings get org.gnome.shell enabled-extensions + // defaults are: + // - fedora 23: ['background-logo@fedorahosted.org'] on + // - openSuse: + // - Ubuntu Gnome 16.04: @as [] + + final StringBuilder stringBuilder = new StringBuilder(output); + + // have to remove the end first, otherwise we would have to re-index the location of the ] + + // remove the last ] + int extensionIndex = output.indexOf("]"); + if (extensionIndex > 0) { + stringBuilder.delete(extensionIndex, stringBuilder.length()); + } + + // strip off UP-TO plus the leading [ + extensionIndex = output.indexOf("["); + if (extensionIndex >= 0) { + stringBuilder.delete(0, extensionIndex+1); + } + + // should be 'background-logo@fedorahosted.org', 'zyx', 'abs' + // or nothing + + String installedExtensions = stringBuilder.toString(); + + // now just split the extensions into a list so it is easier to manage + + String[] split = installedExtensions + .split(", "); + for (int i = 0; i < split.length; i++) { + final String s = split[i]; + + int i1 = s.indexOf("'"); + int i2 = s.lastIndexOf("'"); + + if (i1 == 0 && i2 == s.length() - 1) { + split[i] = s.substring(1, s.length() - 1); + } + } + + ArrayList strings = new ArrayList(Arrays.asList(split)); + for (Iterator iterator = strings.iterator(); iterator.hasNext(); ) { + final String string = iterator.next(); + if (string.trim() + .isEmpty()) { + iterator.remove(); + } + } + + if (SystemTray.DEBUG) { + logger.debug("Installed extensions are: {}", strings); + } + + return strings; + } + + public static + void setEnabledExtensions(List extensions) { + StringBuilder stringBuilder = new StringBuilder("["); + + for (int i = 0, extensionsSize = extensions.size(), limit = extensionsSize-1; i < extensionsSize; i++) { + final String extension = extensions.get(i); + if (extension.isEmpty()) { + continue; + } + + stringBuilder.append("'") + .append(extension) + .append("'"); + + if (i < limit) { + stringBuilder.append(","); + } + } + stringBuilder.append("]"); + + + if (SystemTray.DEBUG) { + logger.debug("Setting installed extensions to: {}", stringBuilder.toString()); + } + + // gsettings set org.gnome.shell enabled-extensions "['SystemTray@Dorkbox']" + // gsettings set org.gnome.shell enabled-extensions "['background-logo@fedorahosted.org']" + // gsettings set org.gnome.shell enabled-extensions "['background-logo@fedorahosted.org', 'SystemTray@Dorkbox']" + final ShellExecutor setGsettings = new ShellExecutor(); + setGsettings.setExecutable("gsettings"); + setGsettings.addArgument("set"); + setGsettings.addArgument("org.gnome.shell"); + setGsettings.addArgument("enabled-extensions"); + setGsettings.addArgument(stringBuilder.toString()); + setGsettings.start(); + } + + public static + void unInstall(String UID, String restartCommand) { + List enabledExtensions = getEnabledExtensions(); + if (enabledExtensions.contains(UID)) { + enabledExtensions.remove(UID); + + setEnabledExtensions(enabledExtensions); + + restartShell(restartCommand); + } + } + + public static + void restartShell(String restartCommand) { + // in some situations, you can no longer restart the shell in wayland. You must logout-login for shell modifications to apply + // https://mail.gnome.org/archives/commits-list/2015-March/msg01019.html + + // HOWEVER, with wayland, shell-extensions that DO NO MODIFY THE GUI (we don't, we just add icons to it) + // are enabled without a shell restart. + + // if there are still difficulties, you can use the following + // gnome-shell-extension-tool -e SystemTray@Dorkbox + + logger.info("Restarting gnome-shell via '{}' so tray notification changes can be applied.", restartCommand); + + // now we have to restart the gnome shell via bash in a background process + ShellAsyncExecutor.runShell(restartCommand); + + // We don't care when the shell restarts, since WHEN IT DOES restart, our extension will show our icon. + // Until then however, there will be errors which can be ignored, because the shell-restart means everything works. + } + + protected static + String readFile(final File metaDatafile) { + StringBuilder builder = new StringBuilder(256); + BufferedReader bin = null; + try { + bin = new BufferedReader(new FileReader(metaDatafile)); + String line; + while ((line = bin.readLine()) != null) { + builder.append(line) + .append("\n"); + } + } catch (Exception ignored) { + } finally { + if (bin != null) { + try { + bin.close(); + } catch (IOException e) { + logger.error("Error closing: {}", bin, e); + } + } + } + + return builder.toString(); + } + + /** + * @return true if successful, false if there was a failure + */ + protected static + boolean writeFile(final String metadata, final File metaDatafile) { + File file = metaDatafile.getParentFile(); + + // need to make the extension location + if (!file.isDirectory()) { + final boolean mkdirs = file.mkdirs(); + if (!mkdirs) { + final String msg = "Unable to create extension location: " + file; + logger.error(msg); + return false; + } + } + + // write out the metadata + BufferedWriter outputWriter = null; + try { + outputWriter = new BufferedWriter(new FileWriter(metaDatafile, false)); + // FileWriter always assumes default encoding is OK + outputWriter.write(metadata); + outputWriter.flush(); + outputWriter.close(); + } catch (IOException e) { + logger.error("Error installing extension metadata file", e); + return true; + } finally { + if (outputWriter != null) { + try { + outputWriter.close(); + } catch (IOException e) { + logger.error("Error closing: {}", outputWriter, e); + } + } + } + + + return true; + } + + /** + * @return true if successful, false if there was a failure + */ + protected static + boolean installFile(final String resourceName, final File targetDirectory) { + final File outputFile = new File(targetDirectory, resourceName); + + InputStream reader = null; + FileOutputStream fileOutputStream = null; + try { + reader = ExtensionSupport.class.getResourceAsStream(resourceName); + fileOutputStream = new FileOutputStream(outputFile); + + if (reader == null) { + logger.error("The {} file cannot be found. Something is severely wrong.", resourceName); + return false; + } + + IO.copyStream(reader, fileOutputStream); + + return true; + } catch (FileNotFoundException e) { + logger.error("Cannot find gnome-shell extension", e); + } catch (IOException e) { + logger.error("Unable to get gnome-shell extension", e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + logger.error("Error closing: {}", reader, e); + } + } + if (fileOutputStream != null) { + try { + fileOutputStream.close(); + } catch (IOException e) { + logger.error("Error closing: {}", fileOutputStream, e); + } + } + } + + return false; + } + + /** + * @return true if successful, false if there was a failure + */ + protected static + boolean installZip(final String zipResourceName, final File targetDirectory) { + ZipInputStream inputStream = null; + FileOutputStream fileOutputStream = null; + + try { + inputStream = new ZipInputStream(ExtensionSupport.class.getResourceAsStream(zipResourceName)); + + ZipEntry entry; + while ((entry = inputStream.getNextEntry()) != null) { + if (!entry.isDirectory()) { + String name = entry.getName(); + + try { + if (!entry.isDirectory()) { + File specificOutput = new File(targetDirectory, name); + File parentFile = specificOutput.getParentFile(); + + if (!parentFile.exists()) { + boolean mkdirs = parentFile.mkdirs(); + if (!mkdirs) { + SystemTray.logger.error("Error creating target directory '{}' for Zip support.", parentFile); + } + } + + fileOutputStream = new FileOutputStream(specificOutput); + + IO.copyStream(inputStream, fileOutputStream); + } + } catch (IOException e) { + SystemTray.logger.error("Error extracting zip contents to {}", targetDirectory); + } finally { + if (fileOutputStream != null) { + IO.closeQuietly(fileOutputStream); + fileOutputStream = null; + } + } + } + } + + return true; + } catch (FileNotFoundException e) { + logger.error("Cannot find gnome-shell extension", e); + } catch (IOException e) { + logger.error("Unable to get gnome-shell extension", e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + logger.error("Error closing: {}", inputStream, e); + } + } + if (fileOutputStream != null) { + try { + fileOutputStream.close(); + } catch (IOException e) { + logger.error("Error closing: {}", fileOutputStream, e); + } + } + } + + return false; + } + + + protected static + String createMetadata(final String uid, final String appVersion, final String gnomeVersion) { + return "{\n" + + " \"description\": \"Moves the java SystemTray icon from inside the notification drawer to alongside the clock.\",\n" + + " \"name\": \"Dorkbox SystemTray\",\n" + + " \"shell-version\": [\n" + + " \"" + gnomeVersion + "\"\n" + + " ],\n" + + " \"url\": \"https://git.dorkbox.com/dorkbox/SystemTray\",\n" + + " \"uuid\": \"" + uid + "\",\n" + + " \"version\": " + appVersion + "\n" + + "}\n"; + } + + protected static + String getGnomeVersion() { + String gnomeVersion = OSUtil.DesktopEnv.getGnomeVersion(); + if (gnomeVersion == null) { + return null; + } + + // We want "3.14" or "3.20" or whatever the latest version is (excluding the patch version info). + final int indexOf = gnomeVersion.indexOf('.'); + final int nextIndexOf = gnomeVersion.indexOf('.', indexOf + 1); + if (indexOf < nextIndexOf) { + return gnomeVersion.substring(0, nextIndexOf); // will be 3.14 (without the trailing '.1'), for example + } + + return gnomeVersion; + } + + /** + * @return true if we need to upgrade/re-install the extension, false if we do not need to do anything + */ + protected static + boolean needsUpgrade(final String metadata, final File metaDatafile) { + String existingMetadata = ExtensionSupport.readFile(metaDatafile); + + if (SystemTray.DEBUG) { + logger.debug("Extension already installed, checking for upgrade"); + } + + // have to check to see if the version is correct as well (otherwise we have to reinstall it) + // compat for java 1.6 + + // the metadata string we CHECK should equal the metadata string we PROVIDE + if (metadata.equals(existingMetadata)) { + // this means that our version info, etc. is the same - there is no need to update anything + if (!SystemTray.DEBUG) { + return false; + } else { + // if we are DEBUG, then we ALWAYS want to copy over our extension. We will have to manually restart the shell to see it + logger.debug("Always upgrading extension in DEBUG mode"); + } + } + else { + // this means that we need to reinstall our extension, since either GNOME or US have changed versions since + // we last installed the extension. + if (SystemTray.DEBUG) { + logger.debug("Need to upgrade extension"); + } + } + + return true; + } +} diff --git a/src/dorkbox/systemTray/gnomeShell/LegacyExtension.java b/src/dorkbox/systemTray/gnomeShell/LegacyExtension.java new file mode 100644 index 0000000..a45d7b3 --- /dev/null +++ b/src/dorkbox/systemTray/gnomeShell/LegacyExtension.java @@ -0,0 +1,165 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.gnomeShell; + +import static dorkbox.systemTray.SystemTray.logger; + +import java.io.File; +import java.util.List; + +import dorkbox.systemTray.SystemTray; +import dorkbox.util.OSUtil; + +@SuppressWarnings({"DanglingJavadoc", "WeakerAccess"}) +public +class LegacyExtension extends ExtensionSupport { + private static final String UID = "SystemTray@Dorkbox"; + public static final String DEFAULT_NAME = "SystemTray"; + + /** Command to restart the gnome-shell. It is recommended to start it in the background (hence '&') */ + private static final String SHELL_RESTART_COMMAND = "gnome-shell --replace &"; + + /** + * topIcons will convert ALL icons to be at the top of the screen, so there is no reason to have both installed + * + * @return true if that extension is installed + */ + public static + boolean isInstalled() { + List enabledExtensions = getEnabledExtensions(); + return enabledExtensions.contains("topIcons@adel.gadllah@gmail.com") || enabledExtensions.contains(UID); + } + + + /** + * Only install a version that specifically moves only our icon next to the clock + */ + public static + void install() { + if (OSUtil.DesktopEnv.isWayland()) { + if (SystemTray.DEBUG) { + SystemTray.logger.debug("Gnome-shell legacy extension not possible with wayland."); + } + + return; + } + + if (SystemTray.DEBUG) { + SystemTray.logger.debug("Installing the legacy gnome-shell extension."); + } + + boolean hasTopIcons; + boolean hasSystemTray; + + // should just be 3.14.1 or 3.20 or similar + String gnomeVersion = ExtensionSupport.getGnomeVersion(); + if (gnomeVersion == null) { + return; + } + + List enabledExtensions = getEnabledExtensions(); + hasTopIcons = enabledExtensions.contains("topIcons@adel.gadllah@gmail.com"); + hasSystemTray = enabledExtensions.contains(UID); + + if (hasTopIcons) { + // topIcons will convert ALL icons to be at the top of the screen, so there is no reason to have both installed + return; + } + + // have to copy the extension over and enable it. + String userHome = System.getProperty("user.home"); + + // where the extension is saved + final File directory = new File(userHome + "/.local/share/gnome-shell/extensions/" + UID); + final File metaDatafile = new File(directory, "metadata.json"); + + + // have to create the metadata.json file (and make it so that it's **always** current). + // we do this via getting the shell version + + String metadata = ExtensionSupport.createMetadata(UID, SystemTray.getVersion(), gnomeVersion); + + if (SystemTray.DEBUG) { + logger.debug("Checking the legacy gnome-shell extension"); + } + + if (hasSystemTray && !ExtensionSupport.needsUpgrade(metadata, metaDatafile)) { + // this means that our version info, etc. is the same - there is no need to update anything + return; + } + + + // we get here if we are NOT installed, or if we are installed and our metadata is NOT THE SAME. (so we need to reinstall) + if (SystemTray.DEBUG) { + logger.debug("Installing legacy gnome-shell extension"); + } + + + boolean success = ExtensionSupport.writeFile(metadata, metaDatafile); + + if (success && !hasSystemTray) { + // copies our provided extension files to the correct location on disk + ExtensionSupport.installFile("extension.js", directory); + + if (SystemTray.DEBUG) { + logger.debug("Enabling legacy gnome-shell extension"); + } + + if (!enabledExtensions.contains(UID)) { + enabledExtensions.add(UID); + } + setEnabledExtensions(enabledExtensions); + + restartShell(SHELL_RESTART_COMMAND); + } + } + + public static + void unInstall() { + if (OSUtil.DesktopEnv.isWayland()) { + if (OSUtil.Linux.isUbuntu() && OSUtil.Linux.getUbuntuVersion()[0] == 17) { + // ubuntu 17.04 is NOT WAYLAND (it's MIR) and ubuntu 17.10 is WAYLAND (and doesn't support this) + return; + } + else if (OSUtil.Linux.isFedora()) { + // fedora doesn't support this + return; + + } + else { + if (SystemTray.DEBUG) { + logger.warn("Trying to restart the shell with an unknown version of wayland. Please create an issue with OS and debug information."); + } + else { + logger.warn("Trying to restart the shell with an unknown version of wayland. Please set `SystemTray.DEBUG=true;` then create an issue " + + "with OS and debug information."); + } + } + } + + unInstall(UID, SHELL_RESTART_COMMAND); + } + + public static + void restartShell() { + if (SystemTray.DEBUG) { + logger.debug("DEBUG mode enabled. You need to log-out/in or manually restart the shell via '{}' to apply the changes.", SHELL_RESTART_COMMAND); + return; + } + + restartShell(SHELL_RESTART_COMMAND); + } +} diff --git a/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java b/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java index 774f418..b1cd3a1 100644 --- a/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java +++ b/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java @@ -23,7 +23,7 @@ import com.sun.jna.Pointer; import dorkbox.systemTray.MenuItem; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.Tray; -import dorkbox.systemTray.gnomeShell.Extension; +import dorkbox.systemTray.gnomeShell.LegacyExtension; import dorkbox.systemTray.util.ImageResizeUtil; import dorkbox.util.jna.linux.AppIndicator; import dorkbox.util.jna.linux.GObject; @@ -126,7 +126,7 @@ class _AppIndicatorNativeTray extends Tray { // in extension.js, so don't change it // additionally, this is required to be set HERE (not somewhere else) - appIndicator.app_indicator_set_title(Extension.DEFAULT_NAME); + appIndicator.app_indicator_set_title(LegacyExtension.DEFAULT_NAME); } } diff --git a/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java b/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java index 9036460..06478da 100644 --- a/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java +++ b/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java @@ -25,7 +25,7 @@ import com.sun.jna.Pointer; import dorkbox.systemTray.MenuItem; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.Tray; -import dorkbox.systemTray.gnomeShell.Extension; +import dorkbox.systemTray.gnomeShell.LegacyExtension; import dorkbox.util.JavaFX; import dorkbox.util.jna.linux.GEventCallback; import dorkbox.util.jna.linux.GObject; @@ -205,7 +205,7 @@ class _GtkStatusIconNativeTray extends Tray { // necessary for gnome icon detection/placement because we move tray icons around by title. This is hardcoded // in extension.js, so don't change it - Gtk2.gtk_status_icon_set_title(trayIcon, Extension.DEFAULT_NAME); + Gtk2.gtk_status_icon_set_title(trayIcon, LegacyExtension.DEFAULT_NAME); // can cause // Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed