diff --git a/src/dorkbox/systemTray/Entry.java b/src/dorkbox/systemTray/Entry.java index ce58250..f632d96 100644 --- a/src/dorkbox/systemTray/Entry.java +++ b/src/dorkbox/systemTray/Entry.java @@ -81,7 +81,7 @@ class Entry { } /** - * Removes this menu entry from the menu and releases all system resources associated with this menu entry + * Removes this menu entry from the menu and releases all system resources associated with this menu entry. */ public void remove() { diff --git a/src/dorkbox/systemTray/Menu.java b/src/dorkbox/systemTray/Menu.java index bbc75dd..87eb828 100644 --- a/src/dorkbox/systemTray/Menu.java +++ b/src/dorkbox/systemTray/Menu.java @@ -408,33 +408,15 @@ class Menu extends MenuItem { } } - /** - * This removes all menu entries from this menu - */ - public - void clear() { - List copy; - synchronized (menuEntries) { - // access on this object must be synchronized for object visibility - // a copy is made to prevent deadlocks from occurring when operating in different threads - // have to make copy because we are deleting all of them, and sub-menus remove themselves from parents - copy = new ArrayList(menuEntries); - menuEntries.clear(); - } - - for (Entry entry : copy) { - entry.remove(); - } - } - - /** * This removes all menu entries from this menu AND this menu from it's parent */ @Override public void remove() { - clear(); + synchronized (menuEntries) { + menuEntries.clear(); + } super.remove(); } diff --git a/src/dorkbox/systemTray/MenuItem.java b/src/dorkbox/systemTray/MenuItem.java index 29737bb..f00e10c 100644 --- a/src/dorkbox/systemTray/MenuItem.java +++ b/src/dorkbox/systemTray/MenuItem.java @@ -387,16 +387,4 @@ class MenuItem extends Entry { String getTooltip() { return this.tooltip; } - - @Override - public - void remove() { - if (peer != null) { - setImage_(null); - setText(null); - setCallback(null); - } - - super.remove(); - } } diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index d1fb04a..afb25a6 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -44,6 +44,7 @@ import dorkbox.systemTray.ui.gtk._AppIndicatorNativeTray; import dorkbox.systemTray.ui.gtk._GtkStatusIconNativeTray; import dorkbox.systemTray.ui.swing.SwingUIFactory; import dorkbox.systemTray.ui.swing._SwingTray; +import dorkbox.systemTray.util.EventDispatch; import dorkbox.systemTray.util.ImageResizeUtil; import dorkbox.systemTray.util.LinuxSwingUI; import dorkbox.systemTray.util.SizeAndScalingUtil; @@ -226,7 +227,7 @@ class SystemTray { OSUtil.DesktopEnv.Env de = OSUtil.DesktopEnv.get(); if (DEBUG) { - logger.debug("Currently using the '{}' desktop environment", de); + logger.debug("Currently using the '{}' desktop environment" + OS.LINE_SEPARATOR + OSUtil.Linux.getInfo(), de); } switch (de) { @@ -310,6 +311,19 @@ class SystemTray { // kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that } case Unity: { + try { + String ubuntuVersion = OSUtil.Linux.getUbuntuVersion(); + String[] split = ubuntuVersion.split("."); + int major = Integer.parseInt(split[0]); + int minor = Integer.parseInt(split[1]); + + // <=16.04 it is better to use GtkStatusIcons. + if (major < 16 || (major == 16 && minor <= 4)) { + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + } catch (Exception ignored) { + } + // Ubuntu Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell". return selectTypeQuietly(TrayType.AppIndicator); } @@ -393,6 +407,19 @@ class SystemTray { logger.error("Error detecting gnome version", e); } } + + if (OS.isLinux()) { + // now just blanket query what we are to guess... + if (OSUtil.Linux.isUbuntu()) { + return selectTypeQuietly(TrayType.AppIndicator); + } + else if (OSUtil.Linux.isFedora()) { + return selectTypeQuietly(TrayType.AppIndicator); + } else { + // AppIndicators are now the "default" for most linux distro's. + return selectTypeQuietly(TrayType.AppIndicator); + } + } } return null; @@ -1025,13 +1052,14 @@ class SystemTray { */ public void shutdown() { - // this will call "dispatchAndWait()" behind the scenes, so it is thread-safe + // this is thread-safe final Menu menu = systemTrayMenu; if (menu != null) { menu.remove(); } systemTrayMenu = null; + EventDispatch.shutdown(); } /** diff --git a/src/dorkbox/systemTray/ui/gtk/GtkBaseMenuItem.java b/src/dorkbox/systemTray/ui/gtk/GtkBaseMenuItem.java index 9ef764c..72b69c9 100644 --- a/src/dorkbox/systemTray/ui/gtk/GtkBaseMenuItem.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkBaseMenuItem.java @@ -30,7 +30,7 @@ abstract class GtkBaseMenuItem implements EntryPeer { // these are necessary BECAUSE GTK menus look funky as hell when there are some menu entries WITH icons and some WITHOUT private static final File transparentIcon = ImageResizeUtil.getTransparentImage(); - private volatile boolean hasLegitImage = true; + private volatile boolean hasLegitImage = false; // default is to not have an image assigned // these have to be volatile, because they can be changed from any thread private volatile Pointer spacerImage; @@ -51,6 +51,39 @@ class GtkBaseMenuItem implements EntryPeer { hasLegitImage = isLegit; } + /** + * always remove a spacer image. + *

+ * called on the DISPATCH thread + */ + protected + void removeSpacerImage() { + if (spacerImage != null) { + Gtk2.gtk_container_remove(_native, spacerImage); // will automatically get destroyed if no other references to it + spacerImage = null; + Gtk2.gtk_widget_show_all(_native); + } + } + + + /** + * always add a spacer image. + *

+ * called on the DISPATCH thread + */ + protected + void addSpacerImage() { + if (spacerImage == null) { + spacerImage = Gtk2.gtk_image_new_from_file(transparentIcon.getAbsolutePath()); + Gtk2.gtk_image_menu_item_set_image(_native, spacerImage); + + // must always re-set always-show after setting the image + Gtk2.gtk_image_menu_item_set_always_show_image(_native, true); + } + } + + + /** * the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images. * This is primarily only with AppIndicators, although not always. @@ -64,18 +97,10 @@ class GtkBaseMenuItem implements EntryPeer { return; } - if (spacerImage != null) { - Gtk2.gtk_container_remove(_native, spacerImage); // will automatically get destroyed if no other references to it - spacerImage = null; - Gtk2.gtk_widget_show_all(_native); - } + removeSpacerImage(); if (everyoneElseHasImages) { - spacerImage = Gtk2.gtk_image_new_from_file(transparentIcon.getAbsolutePath()); - Gtk2.gtk_image_menu_item_set_image(_native, spacerImage); - - // must always re-set always-show after setting the image - Gtk2.gtk_image_menu_item_set_always_show_image(_native, true); + addSpacerImage(); } Gtk2.gtk_widget_show_all(_native); diff --git a/src/dorkbox/systemTray/ui/gtk/GtkMenu.java b/src/dorkbox/systemTray/ui/gtk/GtkMenu.java index be32bde..55b8244 100644 --- a/src/dorkbox/systemTray/ui/gtk/GtkMenu.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenu.java @@ -197,7 +197,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { @Override public void add(final Menu parentMenu, final Entry entry, final int index) { - // must always be called on the GTK dispatch. This must be dispatchAndWait + // must always be called on the GTK dispatch. This must be dispatchAndWait() so it will properly executed immediately GtkEventDispatch.dispatchAndWait(new Runnable() { @Override public @@ -206,35 +206,50 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { // To work around this issue, we destroy then recreate the menu every time something is changed. deleteMenu(); + GtkBaseMenuItem item = null; + if (entry instanceof Menu) { // some implementations of appindicator, do NOT like having a menu added, which has no menu items yet. // see: https://bugs.launchpad.net/glipper/+bug/1203888 - GtkMenu item = new GtkMenu(GtkMenu.this); + item = new GtkMenu(GtkMenu.this); menuEntries.add(index, item); - ((Menu) entry).bind(item, parentMenu, parentMenu.getSystemTray()); } else if (entry instanceof Separator) { - GtkMenuItemSeparator item = new GtkMenuItemSeparator(GtkMenu.this); + item = new GtkMenuItemSeparator(GtkMenu.this); menuEntries.add(index, item); - entry.bind(item, parentMenu, parentMenu.getSystemTray()); } else if (entry instanceof Checkbox) { - GtkMenuItemCheckbox item = new GtkMenuItemCheckbox(GtkMenu.this); + item = new GtkMenuItemCheckbox(GtkMenu.this); menuEntries.add(index, item); - ((Checkbox) entry).bind(item, parentMenu, parentMenu.getSystemTray()); } else if (entry instanceof Status) { - GtkMenuItemStatus item = new GtkMenuItemStatus(GtkMenu.this); + item = new GtkMenuItemStatus(GtkMenu.this); menuEntries.add(index, item); - ((Status) entry).bind(item, parentMenu, parentMenu.getSystemTray()); } else if (entry instanceof MenuItem) { - GtkMenuItem item = new GtkMenuItem(GtkMenu.this); + item = new GtkMenuItem(GtkMenu.this); menuEntries.add(index, item); - ((MenuItem) entry).bind(item, parentMenu, parentMenu.getSystemTray()); } createMenu(); + + // we must create the menu BEFORE binding the menu, otherwise the menus' children's GTK element can be added before + // their parent GTK elements are added (and the menu won't show up) + if (entry instanceof Menu) { + ((Menu) entry).bind((GtkMenu) item, parentMenu, parentMenu.getSystemTray()); + } + else if (entry instanceof Separator) { + ((Separator)entry).bind((GtkMenuItemSeparator) item, parentMenu, parentMenu.getSystemTray()); + } + else if (entry instanceof Checkbox) { + ((Checkbox) entry).bind((GtkMenuItemCheckbox) item, parentMenu, parentMenu.getSystemTray()); + } + else if (entry instanceof Status) { + ((Status) entry).bind((GtkMenuItemStatus) item, parentMenu, parentMenu.getSystemTray()); + } + else if (entry instanceof MenuItem) { + ((MenuItem) entry).bind((GtkMenuItem) item, parentMenu, parentMenu.getSystemTray()); + } } }); } @@ -252,7 +267,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { // is overridden by system tray setLegitImage(menuItem.getImage() != null); - Runnable runnable = new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -263,8 +278,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { } if (menuItem.getImage() != null) { - image = Gtk2.gtk_image_new_from_file(menuItem.getImage() - .getAbsolutePath()); + image = Gtk2.gtk_image_new_from_file(menuItem.getImage().getAbsolutePath()); Gtk2.gtk_image_menu_item_set_image(_native, image); // must always re-set always-show after setting the image @@ -273,14 +287,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { Gtk2.gtk_widget_show_all(_native); } - }; - - if (GtkEventDispatch.isDispatch.get()) { - runnable.run(); - } - else { - GtkEventDispatch.dispatch(runnable); - } + }); } // is overridden in tray impl @@ -385,7 +392,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { @Override public void remove() { - Runnable runnable = new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -408,13 +415,6 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { parent.createMenu(); // must be on EDT } } - }; - - if (GtkEventDispatch.isDispatch.get()) { - runnable.run(); - } - else { - GtkEventDispatch.dispatch(runnable); - } + }); } } diff --git a/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java index ae5cc11..f68277d 100644 --- a/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java @@ -73,6 +73,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { @Override public void setImage(final MenuItem menuItem) { + final boolean hadImage = hasImage(); setLegitImage(menuItem.getImage() != null); GtkEventDispatch.dispatch(new Runnable() { @@ -86,6 +87,9 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { } if (menuItem.getImage() != null) { + // always remove the spacer image in case it's there. The spacer image will correctly added when the menu is created. + removeSpacerImage(); + image = Gtk2.gtk_image_new_from_file(menuItem.getImage() .getAbsolutePath()); Gtk2.gtk_image_menu_item_set_image(_native, image); @@ -93,6 +97,11 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { // must always re-set always-show after setting the image Gtk2.gtk_image_menu_item_set_always_show_image(_native, true); } + else if (hadImage) { + // if at one point, we had an image, we should set the spacer image back, so that menu spacing looks correct. + // since we USED to have an image, it is safe to assume that we should have a spacer image. + addSpacerImage(); + } Gtk2.gtk_widget_show_all(_native); } @@ -205,14 +214,16 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { @Override public void remove() { - Runnable runnable = new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { - Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it - GtkMenuItem.super.remove(); + callback = null; + + Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it + if (image != null) { Gtk2.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it image = null; @@ -220,13 +231,6 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { parent.remove(GtkMenuItem.this); } - }; - - if (GtkEventDispatch.isDispatch.get()) { - runnable.run(); - } - else { - GtkEventDispatch.dispatch(runnable); - } + }); } } diff --git a/src/dorkbox/systemTray/ui/gtk/GtkMenuItemCheckbox.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemCheckbox.java index 8dfcef9..222bfe2 100644 --- a/src/dorkbox/systemTray/ui/gtk/GtkMenuItemCheckbox.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemCheckbox.java @@ -297,14 +297,16 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall @Override public void remove() { - Runnable runnable = new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { - Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it - GtkMenuItemCheckbox.super.remove(); + callback = null; + + Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it + if (image != null) { Gtk2.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it image = null; @@ -312,13 +314,6 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall parent.remove(GtkMenuItemCheckbox.this); } - }; - - if (GtkEventDispatch.isDispatch.get()) { - runnable.run(); - } - else { - GtkEventDispatch.dispatch(runnable); - } + }); } } diff --git a/src/dorkbox/systemTray/ui/gtk/GtkMenuItemSeparator.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemSeparator.java index 91e8c86..bbd16fe 100644 --- a/src/dorkbox/systemTray/ui/gtk/GtkMenuItemSeparator.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemSeparator.java @@ -17,10 +17,10 @@ package dorkbox.systemTray.ui.gtk; import static dorkbox.util.jna.linux.Gtk.Gtk2; -import dorkbox.systemTray.peer.EntryPeer; +import dorkbox.systemTray.peer.SeparatorPeer; import dorkbox.util.jna.linux.GtkEventDispatch; -class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer { +class GtkMenuItemSeparator extends GtkBaseMenuItem implements SeparatorPeer { private final GtkMenu parent; @@ -37,7 +37,7 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer { @Override public void remove() { - Runnable runnable = new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -45,14 +45,7 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer { parent.remove(GtkMenuItemSeparator.this); } - }; - - if (GtkEventDispatch.isDispatch.get()) { - runnable.run(); - } - else { - GtkEventDispatch.dispatch(runnable); - } + }); } @Override diff --git a/src/dorkbox/systemTray/ui/gtk/GtkMenuItemStatus.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemStatus.java index 717cd1e..5ea179e 100644 --- a/src/dorkbox/systemTray/ui/gtk/GtkMenuItemStatus.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemStatus.java @@ -61,23 +61,16 @@ class GtkMenuItemStatus extends GtkBaseMenuItem implements StatusPeer { @Override public void remove() { - Runnable runnable = new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { - Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it - GtkMenuItemStatus.super.remove(); + Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it + parent.remove(GtkMenuItemStatus.this); } - }; - - if (GtkEventDispatch.isDispatch.get()) { - runnable.run(); - } - else { - GtkEventDispatch.dispatch(runnable); - } + }); } } diff --git a/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java b/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java index 95bb737..9ee2f2d 100644 --- a/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java +++ b/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java @@ -197,25 +197,24 @@ class _AppIndicatorNativeTray extends Tray { void remove() { // This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...) if (!shuttingDown.getAndSet(true)) { - // must happen asap, so our hook properly notices we are in shutdown mode - final AppIndicatorInstanceStruct savedAppIndicator = appIndicator; - appIndicator = null; + super.remove(); GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { + // must happen asap, so our hook properly notices we are in shutdown mode + final AppIndicatorInstanceStruct savedAppIndicator = appIndicator; + appIndicator = null; + // STATUS_PASSIVE hides the indicator AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE); Pointer p = savedAppIndicator.getPointer(); Gobject.g_object_unref(p); + + GtkEventDispatch.shutdownGui(); } }); - - super.remove(); - - // does not need to be called on the dispatch (it does that) - GtkEventDispatch.shutdownGui(); } } };