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 <slusnucky@gmail.com>
This commit is contained in:
Tomas Slusny
2017-12-02 03:40:47 +01:00
committed by Adam
parent 5178c159a8
commit 1c86c30e1a
3 changed files with 191 additions and 40 deletions

View File

@@ -75,17 +75,6 @@
<artifactId>gson</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>fr.jcgay.send-notification</groupId>
<artifactId>send-notification</artifactId>
<version>0.14.0</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.pushingpixels</groupId>
<artifactId>substance</artifactId>

View File

@@ -0,0 +1,189 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* 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<String> 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<String> 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<String> 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";
}
}
}

View File

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