forked from dorkbox/SystemTray
WIP AppIndicators using swing menu
This commit is contained in:
parent
b936a4cd76
commit
fbf528d0ca
@ -34,8 +34,6 @@ class AppIndicator {
|
|||||||
public static boolean isVersion3 = false;
|
public static boolean isVersion3 = false;
|
||||||
private static boolean isLoaded = false;
|
private static boolean isLoaded = false;
|
||||||
|
|
||||||
private static final boolean VERBOSE_DEBUG = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader for AppIndicator, because it is absolutely mindboggling how those whom maintain the standard, can't agree to what that
|
* Loader for AppIndicator, because it is absolutely mindboggling how those whom maintain the standard, can't agree to what that
|
||||||
* standard library naming convention or features/API set is. We just try until we find one that work, and are able to map the
|
* standard library naming convention or features/API set is. We just try until we find one that work, and are able to map the
|
||||||
@ -69,11 +67,8 @@ class AppIndicator {
|
|||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (VERBOSE_DEBUG) {
|
if (SystemTray.DEBUG) {
|
||||||
logger.debug("Error loading library: {}", "appindicator1", e);
|
logger.debug("Error loading GTK2 explicit appindicator1. {}", e.getMessage());
|
||||||
}
|
|
||||||
else if (SystemTray.DEBUG) {
|
|
||||||
logger.debug("Error loading GTK2 explicit appindicator1");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +99,7 @@ class AppIndicator {
|
|||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (SystemTray.DEBUG) {
|
if (SystemTray.DEBUG) {
|
||||||
logger.debug("Error loading library: '{}'", nameToCheck1, e);
|
logger.debug("Error loading library: '{}'. \n{}", nameToCheck1, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +136,7 @@ class AppIndicator {
|
|||||||
break;
|
break;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (SystemTray.DEBUG) {
|
if (SystemTray.DEBUG) {
|
||||||
logger.debug("Error loading library: '{}'", "appindicator" + i, e);
|
logger.debug("Error loading library: '{}'. \n{}", "appindicator" + i, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,7 +163,7 @@ class AppIndicator {
|
|||||||
break;
|
break;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (SystemTray.DEBUG) {
|
if (SystemTray.DEBUG) {
|
||||||
logger.debug("Error loading library: '{}'", "appindicator" + i, e);
|
logger.debug("Error loading library: '{}'. \n{}", "appindicator" + i, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,7 +191,7 @@ class AppIndicator {
|
|||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (SystemTray.DEBUG) {
|
if (SystemTray.DEBUG) {
|
||||||
logger.debug("Error loading library: '{}'", nameToCheck1, e);
|
logger.debug("Error loading library: '{}'. \n{}", nameToCheck1, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +203,7 @@ class AppIndicator {
|
|||||||
isLoaded = true;
|
isLoaded = true;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
if (SystemTray.DEBUG) {
|
if (SystemTray.DEBUG) {
|
||||||
logger.debug("Error loading library: '{}'", nameToCheck2, e);
|
logger.debug("Error loading library: '{}'. \n{}", nameToCheck2, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,4 +228,5 @@ class AppIndicator {
|
|||||||
public static native void app_indicator_set_status(AppIndicatorInstanceStruct self, int status);
|
public static native void app_indicator_set_status(AppIndicatorInstanceStruct self, int status);
|
||||||
public static native void app_indicator_set_menu(AppIndicatorInstanceStruct self, Pointer menu);
|
public static native void app_indicator_set_menu(AppIndicatorInstanceStruct self, Pointer menu);
|
||||||
public static native void app_indicator_set_icon(AppIndicatorInstanceStruct self, String icon_name);
|
public static native void app_indicator_set_icon(AppIndicatorInstanceStruct self, String icon_name);
|
||||||
|
public static native void app_indicator_set_label(AppIndicatorInstanceStruct self, String label, String notused);
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package dorkbox.systemTray.linux.jna;
|
|||||||
import com.sun.jna.Callback;
|
import com.sun.jna.Callback;
|
||||||
import com.sun.jna.NativeLong;
|
import com.sun.jna.NativeLong;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bindings for libgobject-2.0
|
* bindings for libgobject-2.0
|
||||||
@ -32,6 +33,8 @@ class Gobject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static native void g_object_get(Pointer object, String objectName, PointerByReference objectVal, Pointer nullValue);
|
||||||
|
|
||||||
public static native void g_free(Pointer object);
|
public static native void g_free(Pointer object);
|
||||||
public static native void g_object_unref(Pointer object);
|
public static native void g_object_unref(Pointer object);
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ class Gtk {
|
|||||||
|
|
||||||
// there is ONLY a single thread EVER setting this value!!
|
// there is ONLY a single thread EVER setting this value!!
|
||||||
private static volatile boolean isDispatch = false;
|
private static volatile boolean isDispatch = false;
|
||||||
|
public static boolean isKDE = false;
|
||||||
|
|
||||||
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk
|
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk
|
||||||
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
|
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
|
||||||
|
291
src/dorkbox/systemTray/swing/AppIndicatorTray.java
Normal file
291
src/dorkbox/systemTray/swing/AppIndicatorTray.java
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014 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.swing;
|
||||||
|
|
||||||
|
import static dorkbox.systemTray.SystemTray.TIMEOUT;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.MouseInfo;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import javax.swing.JPopupMenu;
|
||||||
|
|
||||||
|
import com.sun.jna.NativeLong;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||||
|
import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct;
|
||||||
|
import dorkbox.systemTray.linux.jna.GEventCallback;
|
||||||
|
import dorkbox.systemTray.linux.jna.GdkEventButton;
|
||||||
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
|
import dorkbox.util.ScreenUtil;
|
||||||
|
import dorkbox.util.SwingUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for handling all system tray interactions.
|
||||||
|
* specialization for using app indicators in ubuntu unity
|
||||||
|
*
|
||||||
|
* Derived from
|
||||||
|
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
|
||||||
|
*
|
||||||
|
* AppIndicators DO NOT support anything other than plain gtk-menus, because of how they use dbus so no tooltips AND no custom widgets
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* As a result of this decision by Canonical, we have to resort to hacks to get it to do what we want. BY NO MEANS IS THIS PERFECT.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* We still cannot have tooltips, but we *CAN* have custom widgets in the menu (because it's our swing menu now...)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* It would be too much work to re-implement AppIndicators, or even to use LD_PRELOAD + restart service to do what we want.
|
||||||
|
*
|
||||||
|
* As a result, we have some wicked little hacks which are rather effective (but have a small side-effect of very briefly
|
||||||
|
* showing a blank menu)
|
||||||
|
*
|
||||||
|
* // What are AppIndicators?
|
||||||
|
* http://unity.ubuntu.com/projects/appindicators/
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* // Entry-point into appindicators
|
||||||
|
* http://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/services/panel-main.c
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* // The idiocy of appindicators
|
||||||
|
* https://bugs.launchpad.net/screenlets/+bug/522152
|
||||||
|
*
|
||||||
|
* // Code of how the dbus menus work
|
||||||
|
* http://bazaar.launchpad.net/~dbusmenu-team/libdbusmenu/trunk.16.10/view/head:/libdbusmenu-gtk/client.c
|
||||||
|
* https://developer.ubuntu.com/api/devel/ubuntu-12.04/c/dbusmenugtk/index.html
|
||||||
|
*
|
||||||
|
* // more info about trying to put widgets into GTK menus
|
||||||
|
* http://askubuntu.com/questions/16431/putting-an-arbitrary-gtk-widget-into-an-appindicator-indicator
|
||||||
|
*
|
||||||
|
* // possible idea on how to get GTK widgets into GTK menus
|
||||||
|
* https://launchpad.net/ido
|
||||||
|
* http://bazaar.launchpad.net/~canonical-dx-team/ido/trunk/view/head:/src/idoentrymenuitem.c
|
||||||
|
* http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class AppIndicatorTray extends SwingGenericTray {
|
||||||
|
private AppIndicatorInstanceStruct appIndicator;
|
||||||
|
private boolean isActive = false;
|
||||||
|
|
||||||
|
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
||||||
|
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
||||||
|
|
||||||
|
private volatile NativeLong nativeLong;
|
||||||
|
private volatile GEventCallback gtkCallback;
|
||||||
|
private Pointer dummyMenu;
|
||||||
|
private final Runnable popupRunnable;
|
||||||
|
|
||||||
|
public
|
||||||
|
AppIndicatorTray(final SystemTray systemTray) {
|
||||||
|
super(systemTray,null, new SwingSystemTrayMenuPopup());
|
||||||
|
|
||||||
|
if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) {
|
||||||
|
// if we force GTK type system tray, don't attempt to load AppIndicator libs
|
||||||
|
throw new IllegalArgumentException("Unable to start AppIndicator if 'SystemTray.FORCE_TRAY_TYPE' is set to GtkStatusIcon");
|
||||||
|
}
|
||||||
|
|
||||||
|
JPopupMenu popupMenu = (JPopupMenu) _native;
|
||||||
|
popupMenu.pack();
|
||||||
|
popupMenu.setFocusable(true);
|
||||||
|
|
||||||
|
popupRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
Dimension size = _native.getPreferredSize();
|
||||||
|
|
||||||
|
Point point = MouseInfo.getPointerInfo()
|
||||||
|
.getLocation();
|
||||||
|
Rectangle bounds = ScreenUtil.getScreenBoundsAt(point);
|
||||||
|
|
||||||
|
int x = point.x;
|
||||||
|
int y = point.y;
|
||||||
|
|
||||||
|
if (y < bounds.y) {
|
||||||
|
y = bounds.y;
|
||||||
|
}
|
||||||
|
else if (y + size.height > bounds.y + bounds.height) {
|
||||||
|
// our menu cannot have the top-edge snap to the mouse
|
||||||
|
// so we make the bottom-edge snap to the mouse
|
||||||
|
y -= size.height; // snap to edge of mouse
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x < bounds.x) {
|
||||||
|
x = bounds.x;
|
||||||
|
|
||||||
|
x -= 32; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
||||||
|
}
|
||||||
|
else if (x + size.width > bounds.x + bounds.width) {
|
||||||
|
// our menu cannot have the left-edge snap to the mouse
|
||||||
|
// so we make the right-edge snap to the mouse
|
||||||
|
x -= size.width; // snap to edge of mouse
|
||||||
|
|
||||||
|
x += 32; // display over the stupid appindicator menu (which has to show, this is a major hack!)
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingSystemTrayMenuPopup popupMenu = (SwingSystemTrayMenuPopup) _native;
|
||||||
|
popupMenu.doShow(x, y);
|
||||||
|
|
||||||
|
// Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed.
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
createAppIndicatorMenu();
|
||||||
|
hookMenuOpen();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// appindicators DO NOT support anything other than PLAIN gtk-menus
|
||||||
|
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||||
|
// trayIcon.setToolTip(SwingSystemTray.this.appName);
|
||||||
|
|
||||||
|
Gtk.startGui();
|
||||||
|
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
// we initialize with a blank image
|
||||||
|
File image = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
|
||||||
|
String id = System.nanoTime() + "DBST";
|
||||||
|
appIndicator = AppIndicator.app_indicator_new(id, image.getAbsolutePath(), AppIndicator.CATEGORY_APPLICATION_STATUS);
|
||||||
|
|
||||||
|
createAppIndicatorMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Gtk.waitForStartup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
void hookMenuOpen() {
|
||||||
|
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set
|
||||||
|
PointerByReference menuServer = new PointerByReference();
|
||||||
|
PointerByReference rootMenuItem = new PointerByReference();
|
||||||
|
|
||||||
|
Gobject.g_object_get(appIndicator.getPointer(), "dbus-menu-server", menuServer, null);
|
||||||
|
Gobject.g_object_get(menuServer.getValue(), "root-node", rootMenuItem, null);
|
||||||
|
|
||||||
|
gtkCallback = new GEventCallback() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void callback(Pointer notUsed, final GdkEventButton event) {
|
||||||
|
Gtk.gtk_widget_destroy(dummyMenu);
|
||||||
|
SwingUtil.invokeLater(popupRunnable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeLong = Gobject.g_signal_connect_object(rootMenuItem.getValue(), "about-to-show", gtkCallback, null, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAppIndicatorMenu() {
|
||||||
|
dummyMenu = Gtk.gtk_menu_new();
|
||||||
|
Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic("");
|
||||||
|
Gtk.gtk_menu_shell_append(dummyMenu, item);
|
||||||
|
Gtk.gtk_widget_show_all(item);
|
||||||
|
|
||||||
|
AppIndicator.app_indicator_set_menu(appIndicator, dummyMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void shutdown() {
|
||||||
|
if (!shuttingDown.getAndSet(true)) {
|
||||||
|
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
try {
|
||||||
|
// STATUS_PASSIVE hides the indicator
|
||||||
|
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
|
||||||
|
Pointer p = appIndicator.getPointer();
|
||||||
|
Gobject.g_object_unref(p);
|
||||||
|
|
||||||
|
appIndicator = null;
|
||||||
|
} finally {
|
||||||
|
countDownLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI
|
||||||
|
// thread occur in REASONABLE time-frames, and alert the user if not.
|
||||||
|
try {
|
||||||
|
if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) {
|
||||||
|
SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to shutdown. Please adjust " +
|
||||||
|
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
SystemTray.logger.error("Error waiting for shutdown dispatch to complete.", new Exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.shutdownGui();
|
||||||
|
|
||||||
|
// uses EDT
|
||||||
|
super.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void setImage_(final File imageFile) {
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
AppIndicator.app_indicator_set_icon(appIndicator, imageFile.getAbsolutePath());
|
||||||
|
|
||||||
|
if (!isActive) {
|
||||||
|
isActive = true;
|
||||||
|
|
||||||
|
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
||||||
|
|
||||||
|
// kindof lame, but necessary for KDE
|
||||||
|
if (Gtk.isKDE) {
|
||||||
|
AppIndicator.app_indicator_set_label(appIndicator, "SystemTray", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set
|
||||||
|
hookMenuOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
((SwingSystemTrayMenuPopup) _native).setTitleBarImage(imageFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -33,13 +33,11 @@ import javax.swing.JPopupMenu;
|
|||||||
import com.sun.jna.NativeLong;
|
import com.sun.jna.NativeLong;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
import dorkbox.systemTray.MenuEntry;
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.linux.jna.GEventCallback;
|
import dorkbox.systemTray.linux.jna.GEventCallback;
|
||||||
import dorkbox.systemTray.linux.jna.GdkEventButton;
|
import dorkbox.systemTray.linux.jna.GdkEventButton;
|
||||||
import dorkbox.systemTray.linux.jna.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
import dorkbox.systemTray.util.ImageUtils;
|
|
||||||
import dorkbox.util.ScreenUtil;
|
import dorkbox.util.ScreenUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,7 +47,7 @@ import dorkbox.util.ScreenUtil;
|
|||||||
* swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it.
|
* swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it.
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class GtkStatusIconTray extends SwingMenu {
|
class GtkStatusIconTray extends SwingGenericTray {
|
||||||
private volatile Pointer trayIcon;
|
private volatile Pointer trayIcon;
|
||||||
|
|
||||||
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
||||||
@ -72,7 +70,6 @@ class GtkStatusIconTray extends SwingMenu {
|
|||||||
throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator");
|
throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
JPopupMenu popupMenu = (JPopupMenu) _native;
|
JPopupMenu popupMenu = (JPopupMenu) _native;
|
||||||
popupMenu.pack();
|
popupMenu.pack();
|
||||||
popupMenu.setFocusable(true);
|
popupMenu.setFocusable(true);
|
||||||
@ -117,7 +114,6 @@ class GtkStatusIconTray extends SwingMenu {
|
|||||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||||
// trayIcon.setToolTip(SwingSystemTray.this.appName);
|
// trayIcon.setToolTip(SwingSystemTray.this.appName);
|
||||||
|
|
||||||
ImageUtils.determineIconSize();
|
|
||||||
Gtk.startGui();
|
Gtk.startGui();
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
@ -131,6 +127,7 @@ class GtkStatusIconTray extends SwingMenu {
|
|||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void callback(Pointer notUsed, final GdkEventButton event) {
|
void callback(Pointer notUsed, final GdkEventButton event) {
|
||||||
|
// show the swing menu on the EDT
|
||||||
// BUTTON_PRESS only (any mouse click)
|
// BUTTON_PRESS only (any mouse click)
|
||||||
if (event.type == 4) {
|
if (event.type == 4) {
|
||||||
// show the swing menu on the EDT
|
// show the swing menu on the EDT
|
||||||
@ -214,6 +211,7 @@ class GtkStatusIconTray extends SwingMenu {
|
|||||||
|
|
||||||
Gtk.shutdownGui();
|
Gtk.shutdownGui();
|
||||||
|
|
||||||
|
// uses EDT
|
||||||
super.remove();
|
super.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,53 +230,12 @@ class GtkStatusIconTray extends SwingMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
String getStatus() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
MenuEntry menuEntry = menuEntries.get(0);
|
|
||||||
if (menuEntry instanceof SwingEntryStatus) {
|
|
||||||
return menuEntry.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
public
|
|
||||||
void setStatus(final String statusText) {
|
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
synchronized (menuEntries) {
|
((SwingSystemTrayMenuPopup) _native).setTitleBarImage(iconFile);
|
||||||
// status is ALWAYS at 0 index...
|
|
||||||
SwingEntry menuEntry = null;
|
|
||||||
if (!menuEntries.isEmpty()) {
|
|
||||||
menuEntry = (SwingEntry) menuEntries.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menuEntry instanceof SwingEntryStatus) {
|
|
||||||
// set the text or delete...
|
|
||||||
|
|
||||||
if (statusText == null) {
|
|
||||||
// delete
|
|
||||||
remove(menuEntry);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// set text
|
|
||||||
menuEntry.setText(statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// create a new one
|
|
||||||
menuEntry = new SwingEntryStatus(GtkStatusIconTray.this, statusText);
|
|
||||||
// status is ALWAYS at 0 index...
|
|
||||||
menuEntries.add(0, menuEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
77
src/dorkbox/systemTray/swing/SwingGenericTray.java
Normal file
77
src/dorkbox/systemTray/swing/SwingGenericTray.java
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package dorkbox.systemTray.swing;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.Menu;
|
||||||
|
import dorkbox.systemTray.MenuEntry;
|
||||||
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract
|
||||||
|
class SwingGenericTray extends SwingMenu {
|
||||||
|
/**
|
||||||
|
* Called in the EDT
|
||||||
|
*
|
||||||
|
* @param systemTray
|
||||||
|
* the system tray (which is the object that sits in the system tray)
|
||||||
|
* @param parent
|
||||||
|
* @param _native
|
||||||
|
*/
|
||||||
|
SwingGenericTray(final SystemTray systemTray, final Menu parent, final JComponent _native) {
|
||||||
|
super(systemTray, parent, _native);
|
||||||
|
|
||||||
|
ImageUtils.determineIconSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
String getStatus() {
|
||||||
|
synchronized (menuEntries) {
|
||||||
|
MenuEntry menuEntry = menuEntries.get(0);
|
||||||
|
if (menuEntry instanceof SwingEntryStatus) {
|
||||||
|
return menuEntry.getText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
void setStatus(final String statusText) {
|
||||||
|
final SwingMenu _this = this;
|
||||||
|
dispatchAndWait(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
synchronized (menuEntries) {
|
||||||
|
// status is ALWAYS at 0 index...
|
||||||
|
SwingEntry menuEntry = null;
|
||||||
|
if (!menuEntries.isEmpty()) {
|
||||||
|
menuEntry = (SwingEntry) menuEntries.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuEntry instanceof SwingEntryStatus) {
|
||||||
|
// set the text or delete...
|
||||||
|
|
||||||
|
if (statusText == null) {
|
||||||
|
// delete
|
||||||
|
remove(menuEntry);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// set text
|
||||||
|
menuEntry.setText(statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// create a new one
|
||||||
|
menuEntry = new SwingEntryStatus(_this, statusText);
|
||||||
|
// status is ALWAYS at 0 index...
|
||||||
|
menuEntries.add(0, menuEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,6 @@ import javax.swing.ImageIcon;
|
|||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
|
|
||||||
import dorkbox.systemTray.MenuEntry;
|
import dorkbox.systemTray.MenuEntry;
|
||||||
import dorkbox.systemTray.util.ImageUtils;
|
|
||||||
import dorkbox.util.ScreenUtil;
|
import dorkbox.util.ScreenUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,7 +42,7 @@ import dorkbox.util.ScreenUtil;
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||||
public
|
public
|
||||||
class SwingSystemTray extends SwingMenu {
|
class SwingSystemTray extends SwingGenericTray {
|
||||||
volatile SystemTray tray;
|
volatile SystemTray tray;
|
||||||
volatile TrayIcon trayIcon;
|
volatile TrayIcon trayIcon;
|
||||||
|
|
||||||
@ -52,8 +51,6 @@ class SwingSystemTray extends SwingMenu {
|
|||||||
SwingSystemTray(final dorkbox.systemTray.SystemTray systemTray) {
|
SwingSystemTray(final dorkbox.systemTray.SystemTray systemTray) {
|
||||||
super(systemTray, null, new SwingSystemTrayMenuPopup());
|
super(systemTray, null, new SwingSystemTrayMenuPopup());
|
||||||
|
|
||||||
ImageUtils.determineIconSize();
|
|
||||||
|
|
||||||
SwingSystemTray.this.tray = SystemTray.getSystemTray();
|
SwingSystemTray.this.tray = SystemTray.getSystemTray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,55 +74,6 @@ class SwingSystemTray extends SwingMenu {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public
|
|
||||||
String getStatus() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
MenuEntry menuEntry = menuEntries.get(0);
|
|
||||||
if (menuEntry instanceof SwingEntryStatus) {
|
|
||||||
return menuEntry.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
public
|
|
||||||
void setStatus(final String statusText) {
|
|
||||||
dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
// status is ALWAYS at 0 index...
|
|
||||||
SwingEntry menuEntry = null;
|
|
||||||
if (!menuEntries.isEmpty()) {
|
|
||||||
menuEntry = (SwingEntry) menuEntries.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menuEntry instanceof SwingEntryStatus) {
|
|
||||||
// set the text or delete...
|
|
||||||
|
|
||||||
if (statusText == null) {
|
|
||||||
// delete
|
|
||||||
remove(menuEntry);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// set text
|
|
||||||
menuEntry.setText(statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// create a new one
|
|
||||||
menuEntry = new SwingEntryStatus(SwingSystemTray.this, statusText);
|
|
||||||
// status is ALWAYS at 0 index...
|
|
||||||
menuEntries.add(0, menuEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
public
|
||||||
void setImage_(final File iconFile) {
|
void setImage_(final File iconFile) {
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@ -185,14 +133,14 @@ class SwingSystemTray extends SwingMenu {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
tray.add(trayIcon);
|
tray.add(trayIcon);
|
||||||
((SwingSystemTrayMenuPopup) _native).setIcon(iconFile);
|
|
||||||
} catch (AWTException e) {
|
} catch (AWTException e) {
|
||||||
dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e);
|
dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
((SwingSystemTrayMenuPopup) _native).setIcon(iconFile);
|
|
||||||
trayIcon.setImage(trayImage);
|
trayIcon.setImage(trayImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
((SwingSystemTrayMenuPopup) _native).setTitleBarImage(iconFile);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ import dorkbox.util.OS;
|
|||||||
*
|
*
|
||||||
* This is our "golden standard" since we have 100% control over it.
|
* This is our "golden standard" since we have 100% control over it.
|
||||||
*/
|
*/
|
||||||
|
public
|
||||||
class SwingSystemTrayMenuPopup extends JPopupMenu {
|
class SwingSystemTrayMenuPopup extends JPopupMenu {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ class SwingSystemTrayMenuPopup extends JPopupMenu {
|
|||||||
private volatile File iconFile;
|
private volatile File iconFile;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
public
|
||||||
SwingSystemTrayMenuPopup() {
|
SwingSystemTrayMenuPopup() {
|
||||||
super();
|
super();
|
||||||
setFocusable(true);
|
setFocusable(true);
|
||||||
@ -113,24 +115,25 @@ class SwingSystemTrayMenuPopup extends JPopupMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the icon for the title-bar, so IF it shows in the task-bar, it will have the corresponding icon as the SystemTray icon
|
* Sets the image for the title-bar, so IF it shows in the task-bar, it will have the corresponding image as the SystemTray image
|
||||||
*/
|
*/
|
||||||
void setIcon(final File iconFile) {
|
void setTitleBarImage(final File imageFile) {
|
||||||
if (this.iconFile == null || !this.iconFile.equals(iconFile)) {
|
if (this.iconFile == null || !this.iconFile.equals(imageFile)) {
|
||||||
this.iconFile = iconFile;
|
this.iconFile = imageFile;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Image image = new ImageIcon(ImageIO.read(iconFile)).getImage();
|
Image image = new ImageIcon(ImageIO.read(imageFile)).getImage();
|
||||||
image.flush();
|
image.flush();
|
||||||
|
|
||||||
// we set the dialog window to have the same icon as what is on the system tray
|
// we set the dialog window to have the same icon as what is on the system tray
|
||||||
hiddenDialog.setIconImage(image);
|
hiddenDialog.setIconImage(image);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
SystemTray.logger.error("Error setting the icon for the popup menu task tray dialog");
|
SystemTray.logger.error("Error setting the title-bar image for the popup menu task tray dialog");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public
|
||||||
void doShow(final int x, final int y) {
|
void doShow(final int x, final int y) {
|
||||||
// critical to get the keyboard listeners working for the popup menu
|
// critical to get the keyboard listeners working for the popup menu
|
||||||
setInvoker(hiddenDialog.getContentPane());
|
setInvoker(hiddenDialog.getContentPane());
|
||||||
|
Loading…
Reference in New Issue
Block a user