platform: system notifications reworked

This commit is contained in:
Roman Shevchenko
2014-02-25 10:24:31 +01:00
parent 8b2e54759c
commit 4d26c0a72d
10 changed files with 132 additions and 243 deletions
Binary file not shown.
Binary file not shown.
@@ -1,52 +0,0 @@
#include "com_intellij_ui_LibNotifyWrapper.h"
#include <dlfcn.h>
/*
* Class: com_intellij_ui_LibNotifyWrapper
* Method: showNotification
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_intellij_ui_LibNotifyWrapper_showNotification
(JNIEnv * jEnv, jclass c, jstring jTitle, jstring jDescription, jstring jLogoPath) {
void *handle;
gboolean (*notify_init_func)(const char *app_name);
void (*notify_uninit_func)();
NotifyNotification* (*notify_notification_new_func) (const char *summary,
const char *body,
const char *icon);
gboolean (*notify_notification_show_func) (NotifyNotification *notification,
GError **error);
char *error;
dlerror();
handle = dlopen("libnotify.so", RTLD_LAZY);
if ((error = dlerror()) != NULL)
{
(*jEnv)->ThrowNew(jEnv, (*jEnv)->FindClass(jEnv, "java/lang/UnsatisfiedLinkError"), error);
return;
}
dlerror();
*(void **) (&notify_init_func) = dlsym(handle, "notify_init");
*(void **) (&notify_uninit_func) = dlsym(handle, "notify_init");
*(void **) (&notify_notification_new_func) = dlsym(handle, "notify_notification_new");
*(void **) (&notify_notification_show_func) = dlsym(handle, "notify_notification_show");
(*notify_init_func) ("JetBrains");
const char *title = (*jEnv)->GetStringUTFChars(jEnv, jTitle, 0);
const char *description = (*jEnv)->GetStringUTFChars(jEnv, jDescription, 0);
const char *logoPath = (*jEnv)->GetStringUTFChars(jEnv, jLogoPath, 0);
NotifyNotification * notification = (*notify_notification_new_func) (title, description, logoPath);
(*notify_notification_show_func) (notification, NULL);
(*notify_uninit_func)();
(*jEnv)->ReleaseStringUTFChars(jEnv, jTitle, title);
(*jEnv)->ReleaseStringUTFChars(jEnv, jDescription, description);
(*jEnv)->ReleaseStringUTFChars(jEnv, jLogoPath, logoPath);
dlclose(handle);
}
@@ -1,22 +0,0 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <libnotify/notify.h>
/* Header for class com_intellij_ui_LibNotifyWrapper */
#ifndef _Included_com_intellij_ui_LibNotifyWrapper
#define _Included_com_intellij_ui_LibNotifyWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_intellij_ui_LibNotifyWrapper
* Method: showNotification
* Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_intellij_ui_LibNotifyWrapper_showNotification
(JNIEnv *, jclass, jstring, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
@@ -27,56 +27,45 @@ import java.util.TreeSet;
/**
* @author mike
*/
class GrowlNotifications implements MacNotifications {
class GrowlNotifications implements SystemNotificationsImpl.Notifier {
private static final Logger LOG = Logger.getInstance("#com.intellij.ui.GrowlNotifications");
private static GrowlNotifications ourNotifications;
private final Set<String> myNotifications = new TreeSet<String>();
private Growl myGrowl;
public GrowlNotifications() {
this(ApplicationNamesInfo.getInstance().getFullProductName());
}
GrowlNotifications(String fullProductName) {
myGrowl = new Growl(fullProductName);
register();
}
private String[] getAllNotifications() {
return ArrayUtil.toStringArray(myNotifications);
}
public static synchronized GrowlNotifications getNotifications() {
public static synchronized GrowlNotifications getInstance() {
if (ourNotifications == null) {
ourNotifications = new GrowlNotifications();
}
return ourNotifications;
}
public void notify(Set<String> allNotifications, @NotNull String notificationName, String title, String description) {
if (!myNotifications.equals(allNotifications)) {
myNotifications.addAll(allNotifications);
register();
}
private final Growl myGrowl;
private final Set<String> myNotifications;
try {
myGrowl.notifyGrowlOf(notificationName, title, description);
}
catch (Exception e) {
LOG.error(e);
}
private GrowlNotifications() {
myGrowl = new Growl(ApplicationNamesInfo.getInstance().getFullProductName());
myNotifications = new TreeSet<String>();
register();
}
private void register() {
myGrowl.setAllowedNotifications(getAllNotifications());
try {
myGrowl.setDefaultNotifications(getAllNotifications());
}
catch (Exception e) {
LOG.error(e);
}
myGrowl.setAllowedNotifications(ArrayUtil.toStringArray(myNotifications));
myGrowl.setDefaultNotifications(ArrayUtil.toStringArray(myNotifications));
myGrowl.register();
}
@Override
public void notify(@NotNull Set<String> allNames, @NotNull String name, @NotNull String title, @NotNull String description) {
try {
if (!myNotifications.equals(allNames)) {
myNotifications.addAll(allNames);
register();
}
myGrowl.notifyGrowlOf(name, title, description);
}
catch (Exception e) {
LOG.warn(e);
}
}
}
@@ -15,53 +15,63 @@
*/
package com.intellij.ui;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.ide.AppLifecycleListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.PathManager;
import com.intellij.util.lang.UrlClassLoader;
import com.intellij.util.messages.MessageBusConnection;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* @author Denis Fokin
*/
public class LibNotifyWrapper {
class LibNotifyWrapper implements SystemNotificationsImpl.Notifier {
private static LibNotifyWrapper ourInstance;
private final static String message = "Looks like you have run 32-bit Java on a 64-bit version of OS " +
"or just have not installed appropriate libnotify.so library";
private static boolean available = true;
static{
UrlClassLoader.loadPlatformLibrary("notifywrapper");
}
native private static void showNotification(final String title, final String description, final String iconPath);
public static void show(final String title, final String description, final String iconPath) {
if (! available) return;
try {
showNotification(title, description, iconPath);
} catch (UnsatisfiedLinkError ule) {
available = false;
NotificationGroup.balloonGroup("Linux configuration messages");
Notifications.Bus.notify(
new Notification("Linux configuration messages",
"Notification library has not been installed",
message, NotificationType.INFORMATION)
);
public static synchronized LibNotifyWrapper getInstance() {
if (ourInstance == null) {
ourInstance = new LibNotifyWrapper();
}
return ourInstance;
}
/**
* Shows a libnotify notification with an icon from the ide bin directory.
* If there is no such icon a default information icon is shown.
* @param title notification title
* @param description notification description
*/
public static void showWithAppIcon(final String title, final String description) {
String iconPath = AppUIUtil.findIcon(PathManager.getBinPath());
show(title, description, (iconPath == null) ? "dialog-information" : iconPath);
@SuppressWarnings("SpellCheckingInspection")
private interface LibNotify extends Library {
int notify_init(String appName);
void notify_uninit();
Pointer notify_notification_new(String summary, String body, String icon);
int notify_notification_show(Pointer notification, Pointer error);
}
private final LibNotify myLibNotify;
private LibNotifyWrapper() {
myLibNotify = (LibNotify)Native.loadLibrary("notify", LibNotify.class);
String appName = ApplicationNamesInfo.getInstance().getProductName();
if (myLibNotify.notify_init(appName) == 0) {
throw new IllegalStateException("notify_init failed");
}
MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect();
connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener.Adapter() {
@Override
public void appClosing() {
myLibNotify.notify_uninit();
}
});
}
@Override
public void notify(@NotNull Set<String> allNames, @NotNull String name, @NotNull String title, @NotNull String description) {
String icon = AppUIUtil.findIcon(PathManager.getBinPath());
if (icon == null) icon = "dialog-information";
Pointer notification = myLibNotify.notify_notification_new(title, description, icon);
myLibNotify.notify_notification_show(notification, null);
}
}
@@ -1,27 +0,0 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* 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 com.intellij.ui;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
* @author Dennis.Ushakov
*/
public interface MacNotifications {
void notify(Set<String> allNotifications, @NotNull String notificationName, String title, String description);
}
@@ -33,19 +33,23 @@ import static com.intellij.ui.mac.foundation.Foundation.nsString;
/**
* @author Dennis.Ushakov
*/
public class MountainLionNotifications implements MacNotifications {
private static MountainLionNotifications ourNotifications;
class MountainLionNotifications implements SystemNotificationsImpl.Notifier {
private static MountainLionNotifications ourInstance;
public MountainLionNotifications() {
public static synchronized MountainLionNotifications getInstance() {
if (ourInstance == null) {
ourInstance = new MountainLionNotifications();
}
return ourInstance;
}
private MountainLionNotifications() {
final MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect();
connection.subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener() {
connection.subscribe(ApplicationActivationListener.TOPIC, new ApplicationActivationListener.Adapter() {
@Override
public void applicationActivated(IdeFrame ideFrame) {
cleanupDeliveredNotifications();
}
@Override
public void applicationDeactivated(IdeFrame ideFrame) {}
});
connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener.Adapter() {
@Override
@@ -55,24 +59,16 @@ public class MountainLionNotifications implements MacNotifications {
});
}
public static synchronized MacNotifications getNotifications() {
if (ourNotifications == null) {
ourNotifications = new MountainLionNotifications();
}
return ourNotifications;
}
@Override
public void notify(Set<String> allNotifications, @NotNull String notificationName, String title, String description) {
public void notify(@NotNull Set<String> allNames, @NotNull String name, @NotNull String title, @NotNull String description) {
final ID notification = invoke(Foundation.getObjcClass("NSUserNotification"), "new");
invoke(notification, "setTitle:", nsString(StringUtil.stripHtml(title == null ? "" : title, true).replace("%", "%%")));
invoke(notification, "setInformativeText:", nsString(StringUtil.stripHtml(description == null ? "" : description, true).replace("%", "%%")));
invoke(notification, "setTitle:", nsString(StringUtil.stripHtml(title, true).replace("%", "%%")));
invoke(notification, "setInformativeText:", nsString(StringUtil.stripHtml(description, true).replace("%", "%%")));
final ID center = invoke(Foundation.getObjcClass("NSUserNotificationCenter"), "defaultUserNotificationCenter");
invoke(center, "deliverNotification:", notification);
}
public static void cleanupDeliveredNotifications() {
private static void cleanupDeliveredNotifications() {
final ID center = invoke(Foundation.getObjcClass("NSUserNotificationCenter"), "defaultUserNotificationCenter");
invoke(center, "removeAllDeliveredNotifications");
}
@@ -20,6 +20,7 @@ import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.components.StoragePathMacros;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import org.jetbrains.annotations.NotNull;
@@ -33,65 +34,58 @@ import java.util.Set;
@State(
name = "SystemNotifications",
storages = {
@Storage(
file = StoragePathMacros.APP_CONFIG + "/other.xml"
)}
@Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml")
}
)
public class SystemNotificationsImpl extends SystemNotifications implements PersistentStateComponent<SystemNotificationsImpl.State> {
public static class State {
public Set<String> NOTIFICATIONS = new HashSet<String>();
}
interface Notifier {
void notify(@NotNull Set<String> allNames, @NotNull String name, @NotNull String title, @NotNull String description);
}
private final Notifier myNotifier = getPlatformNotifier();
private State myState = new State();
private boolean myGrowlDisabled = false;
public void notify(@NotNull String notificationName, @NotNull String title, @NotNull String text) {
if (!areNotificationsEnabled() || ApplicationManager.getApplication().isActive()) return;
if (SystemInfo.isLinux && Registry.is("ide.linux.gtk.notifications.enabled") ) {
LibNotifyWrapper.showWithAppIcon(title, text);
return;
}
final MacNotifications notifications;
try {
notifications = getMacNotifications();
}
catch (Throwable e) {
myGrowlDisabled = true;
return;
}
myState.NOTIFICATIONS.add(notificationName);
notifications.notify(myState.NOTIFICATIONS, notificationName, title, text);
}
private static MacNotifications getMacNotifications() {
return SystemInfo.isMacOSMountainLion && Registry.is("ide.mac.mountain.lion.notifications.enabled") ?
MountainLionNotifications.getNotifications() : GrowlNotifications.getNotifications();
}
private boolean areNotificationsEnabled() {
boolean enabled = false;
if (SystemInfo.isMac) {
enabled = !(myGrowlDisabled || "true".equalsIgnoreCase(System.getProperty("growl.disable")));
if (!enabled) {
enabled = SystemInfo.isMacOSMountainLion && Registry.is("ide.mac.mountain.lion.notifications.enabled");
}
} else {
enabled = SystemInfo.isLinux && Registry.is("ide.linux.gtk.notifications.enabled");
}
return enabled;
}
@Override
public State getState() {
return myState;
}
@Override
public void loadState(final State state) {
myState = state;
}
@Override
public void notify(@NotNull String notificationName, @NotNull String title, @NotNull String text) {
if (myNotifier != null && !ApplicationManager.getApplication().isActive()) {
myState.NOTIFICATIONS.add(notificationName);
myNotifier.notify(myState.NOTIFICATIONS, notificationName, title, text);
}
}
public static class State {
public Set<String> NOTIFICATIONS = new HashSet<String>();
private static Notifier getPlatformNotifier() {
try {
if (SystemInfo.isMac) {
if (SystemInfo.isMacOSMountainLion && Registry.is("ide.mac.mountain.lion.notifications.enabled")) {
return MountainLionNotifications.getInstance();
}
if (!Boolean.getBoolean("growl.disable")) {
return GrowlNotifications.getInstance();
}
}
if (SystemInfo.isXWindow && Registry.is("ide.libnotify.enabled") ) {
return LibNotifyWrapper.getInstance();
}
}
catch (Throwable t) {
Logger.getInstance(SystemNotifications.class).error(t);
}
return null;
}
}
@@ -354,8 +354,9 @@ bigger.font.in.project.view.description=Increases font size in Project View
darcula.use.native.fonts.on.linux=true
darcula.use.native.fonts.on.linux.description=If false, uses DejaVu Sans 13pt
idea.4.5.laf.enabled=false
ide.linux.gtk.notifications.enabled=true
ide.linux.gtk.notifications.enabled.description=Enables GTK notifications on linux
ide.libnotify.enabled=true
# suppress inspection "UnusedProperty"
ide.libnotify.enabled.description=Enables notifications via LibNotify
cvs.roots.refresh.uses.vfs=true
cvs.roots.refresh.uses.vfs.description=Should CVS roots refresh after update use VFS
editor.wrap.collapsed.region.at.line.start=false