From 1c86c30e1a32620a2a798c936d2a140c96a3fa19 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Sat, 2 Dec 2017 03:40:47 +0100 Subject: [PATCH] Make native notifications lightweight Remove dependency on external notification library and rewrite notifications to use lightweight native system notification implementations: * Linux - notify-send * OS X - apple script - display notification * Windows - TrayIcon Signed-off-by: Tomas Slusny --- runelite-client/pom.xml | 11 - .../java/net/runelite/client/Notifier.java | 189 ++++++++++++++++++ .../java/net/runelite/client/RuneLite.java | 31 +-- 3 files changed, 191 insertions(+), 40 deletions(-) create mode 100644 runelite-client/src/main/java/net/runelite/client/Notifier.java diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index 4bae77ec9e..caea2fc76b 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -75,17 +75,6 @@ gson 2.4 - - fr.jcgay.send-notification - send-notification - 0.14.0 - - - com.squareup.okhttp - okhttp - - - org.pushingpixels substance diff --git a/runelite-client/src/main/java/net/runelite/client/Notifier.java b/runelite-client/src/main/java/net/runelite/client/Notifier.java new file mode 100644 index 0000000000..393448493c --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/Notifier.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2016-2017, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client; + +import com.google.common.escape.Escaper; +import com.google.common.escape.Escapers; +import java.awt.TrayIcon; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Notifier +{ + private static enum OSType + { + Windows, MacOS, Linux, Other + }; + + private static final String DOUBLE_QUOTE = "\""; + private static final Escaper SHELL_ESCAPE; + private static final OSType DETECTED_OS; + + static + { + final Escapers.Builder builder = Escapers.builder(); + builder.addEscape('"', "'"); + SHELL_ESCAPE = builder.build(); + + final String OS = System + .getProperty("os.name", "generic") + .toLowerCase(); + + if ((OS.contains("mac")) || (OS.contains("darwin"))) + { + DETECTED_OS = OSType.MacOS; + } + else if (OS.contains("win")) + { + DETECTED_OS = OSType.Windows; + } + else if (OS.contains("nux")) + { + DETECTED_OS = OSType.Linux; + } + else + { + DETECTED_OS = OSType.Other; + } + + log.debug("Detect OS: {}", DETECTED_OS); + } + + private final TrayIcon trayIcon; + + public Notifier(final TrayIcon trayIcon) + { + this.trayIcon = trayIcon; + } + + public void sendNotification( + final String title, + final String message, + final TrayIcon.MessageType type, + final String subtitle) + { + final String escapedTitle = SHELL_ESCAPE.escape(title); + final String escapedMessage = SHELL_ESCAPE.escape(message); + final String escapedSubtitle = subtitle != null ? SHELL_ESCAPE.escape(subtitle) : null; + + switch (DETECTED_OS) + { + case Linux: + sendLinuxNotification(escapedTitle, escapedMessage, type); + break; + case MacOS: + sendMacNotification(escapedTitle, escapedMessage, escapedSubtitle); + break; + default: + sendTrayNotification(title, message, type); + } + } + + private void sendTrayNotification( + final String title, + final String message, + final TrayIcon.MessageType type) + { + if (trayIcon != null) + { + trayIcon.displayMessage(title, message, type); + } + } + + private void sendLinuxNotification( + final String title, + final String message, + final TrayIcon.MessageType type) + { + final List commands = new ArrayList<>(); + commands.add("notify-send"); + commands.add(title); + commands.add(message); + commands.add("-u"); + commands.add(toUrgency(type)); + sendCommand(commands); + } + + private void sendMacNotification( + final String title, + final String message, + final String subtitle) + { + final List commands = new ArrayList<>(); + commands.add("osascript"); + commands.add("-e"); + + final StringBuilder script = new StringBuilder("display notification "); + + script.append(DOUBLE_QUOTE) + .append(message) + .append(DOUBLE_QUOTE); + + script.append(" with title ") + .append(DOUBLE_QUOTE) + .append(title) + .append(DOUBLE_QUOTE); + + if (subtitle != null) + { + script.append(" subtitle ") + .append(DOUBLE_QUOTE) + .append(subtitle) + .append(DOUBLE_QUOTE); + } + + commands.add(script.toString()); + sendCommand(commands); + } + + private void sendCommand(final List commands) + { + try + { + new ProcessBuilder(commands.toArray(new String[commands.size()])) + .redirectErrorStream(true) + .start(); + } + catch (IOException ex) + { + log.warn(null, ex); + } + } + + private static String toUrgency(TrayIcon.MessageType type) + { + switch (type) + { + case WARNING: + case ERROR: + return "critical"; + default: + return "normal"; + } + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index c96fb1297e..3fce666eb2 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -52,12 +52,6 @@ import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; - -import fr.jcgay.notification.Application; -import fr.jcgay.notification.Icon; -import fr.jcgay.notification.Notification; -import fr.jcgay.notification.Notifier; -import fr.jcgay.notification.SendNotification; import joptsimple.OptionParser; import joptsimple.OptionSet; import lombok.extern.slf4j.Slf4j; @@ -84,7 +78,6 @@ public class RuneLite public static final String APP_NAME = "RuneLite"; public static Image ICON; - public static Icon NOTIFY_ICON; private static Injector injector; @@ -117,14 +110,12 @@ public class RuneLite private Notifier notifier; - static { try { final URL icon = ClientUI.class.getResource("/runelite.png"); ICON = ImageIO.read(icon.openStream()); - NOTIFY_ICON = Icon.create(icon, APP_NAME); } catch (IOException ex) { @@ -177,14 +168,7 @@ public class RuneLite eventBus.register(menuManager); // Setup the notifier - notifier = new SendNotification() - .setApplication(Application - .builder() - .icon(NOTIFY_ICON) - .name(APP_NAME) - .id(APP_NAME) - .build()) - .initNotifier(); + notifier = new Notifier(trayIcon); // Load the plugins, but does not start them yet. // This will initialize configuration @@ -414,18 +398,7 @@ public class RuneLite public void notify(String message, TrayIcon.MessageType type) { - final Notification.Level notificationLevel = Notification.Level - .valueOf((TrayIcon.MessageType.NONE.equals(type) - ? TrayIcon.MessageType.INFO - : type).toString()); - - notifier.send(Notification.builder() - .title(APP_NAME) - .message(message) - .icon(NOTIFY_ICON) - .level(notificationLevel) - .build()); - + notifier.sendNotification(APP_NAME, message, type, null); Toolkit.getDefaultToolkit().beep(); }