From 2e9201f69256ed5786ff34cb1e95b49d6ef63c0d Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 9 Oct 2016 22:40:28 +0200 Subject: [PATCH] Code polish/refactor. Removed GTK menu stuff --- src/dorkbox/systemTray/SystemTray.java | 183 +++---- .../systemTray/linux/AppIndicatorTray.java | 132 ----- src/dorkbox/systemTray/linux/GtkEntry.java | 218 -------- .../systemTray/linux/GtkEntryItem.java | 192 ------- .../systemTray/linux/GtkEntrySeparator.java | 66 --- .../systemTray/linux/GtkEntryStatus.java | 57 --- src/dorkbox/systemTray/linux/GtkMenu.java | 483 ------------------ .../systemTray/linux/GtkTypeSystemTray.java | 87 ---- .../systemTray/linux/jna/AppIndicator.java | 12 +- .../systemTray/linux/jna/GCallback.java | 29 -- src/dorkbox/systemTray/linux/jna/Gobject.java | 4 - src/dorkbox/systemTray/linux/jna/Gtk.java | 42 -- .../systemTray/swing/AdjustedJMenu.java | 1 - .../swing/{SwingEntry.java => Entry.java} | 6 +- .../{SwingEntryItem.java => EntryItem.java} | 4 +- ...ntrySeparator.java => EntrySeparator.java} | 4 +- ...SwingEntryStatus.java => EntryStatus.java} | 4 +- ...SwingGenericTray.java => GenericTray.java} | 37 +- .../systemTray/swing/GtkStatusIconTray.java | 242 --------- src/dorkbox/systemTray/swing/SwingMenu.java | 14 +- ...ystemTrayMenuPopup.java => TrayPopup.java} | 74 ++- ...icatorTray.java => _AppIndicatorTray.java} | 96 +--- .../_GtkStatusIconTray.java} | 69 ++- .../{SwingSystemTray.java => _SwingTray.java} | 46 +- src/dorkbox/systemTray/util/ImageUtils.java | 127 +++-- 25 files changed, 369 insertions(+), 1860 deletions(-) delete mode 100644 src/dorkbox/systemTray/linux/AppIndicatorTray.java delete mode 100644 src/dorkbox/systemTray/linux/GtkEntry.java delete mode 100644 src/dorkbox/systemTray/linux/GtkEntryItem.java delete mode 100644 src/dorkbox/systemTray/linux/GtkEntrySeparator.java delete mode 100644 src/dorkbox/systemTray/linux/GtkEntryStatus.java delete mode 100644 src/dorkbox/systemTray/linux/GtkMenu.java delete mode 100644 src/dorkbox/systemTray/linux/GtkTypeSystemTray.java delete mode 100644 src/dorkbox/systemTray/linux/jna/GCallback.java rename src/dorkbox/systemTray/swing/{SwingEntry.java => Entry.java} (98%) rename src/dorkbox/systemTray/swing/{SwingEntryItem.java => EntryItem.java} (95%) rename src/dorkbox/systemTray/swing/{SwingEntrySeparator.java => EntrySeparator.java} (91%) rename src/dorkbox/systemTray/swing/{SwingEntryStatus.java => EntryStatus.java} (92%) rename src/dorkbox/systemTray/swing/{SwingGenericTray.java => GenericTray.java} (61%) delete mode 100644 src/dorkbox/systemTray/swing/GtkStatusIconTray.java rename src/dorkbox/systemTray/swing/{SwingSystemTrayMenuPopup.java => TrayPopup.java} (74%) rename src/dorkbox/systemTray/swing/{AppIndicatorTray.java => _AppIndicatorTray.java} (71%) rename src/dorkbox/systemTray/{linux/GtkSystemTray.java => swing/_GtkStatusIconTray.java} (74%) rename src/dorkbox/systemTray/swing/{SwingSystemTray.java => _SwingTray.java} (64%) diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index eacab9e5..0992c8df 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -28,12 +28,12 @@ import java.net.URL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import dorkbox.systemTray.linux.AppIndicatorTray; import dorkbox.systemTray.linux.GnomeShellExtension; -import dorkbox.systemTray.linux.GtkSystemTray; import dorkbox.systemTray.linux.jna.AppIndicator; import dorkbox.systemTray.linux.jna.Gtk; -import dorkbox.systemTray.swing.SwingSystemTray; +import dorkbox.systemTray.swing._AppIndicatorTray; +import dorkbox.systemTray.swing._GtkStatusIconTray; +import dorkbox.systemTray.swing._SwingTray; import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.JavaFX; import dorkbox.systemTray.util.Swt; @@ -42,6 +42,7 @@ import dorkbox.util.CacheUtil; import dorkbox.util.IO; import dorkbox.util.OS; import dorkbox.util.Property; +import dorkbox.util.SwingUtil; import dorkbox.util.process.ShellProcessBuilder; @@ -104,7 +105,7 @@ class SystemTray extends Menu { *

* This is an advanced feature, and it is recommended to leave at 0. */ - public static int FORCE_TRAY_TYPE = 0; + public static int FORCE_TRAY_TYPE = 2; @Property /** @@ -280,19 +281,19 @@ class SystemTray extends Menu { if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_GTK_STATUSICON) { try { - trayType = GtkSystemTray.class; + trayType = _GtkStatusIconTray.class; } catch (Throwable e1) { if (DEBUG) { - logger.error("Cannot initialize GtkSystemTray", e1); + logger.error("Cannot initialize _GtkStatusIconTray", e1); } } } else if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) { try { - trayType = AppIndicatorTray.class; + trayType = _AppIndicatorTray.class; } catch (Throwable e1) { if (DEBUG) { - logger.error("Cannot initialize AppIndicatorTray", e1); + logger.error("Cannot initialize _AppIndicatorTray", e1); } } } @@ -335,16 +336,26 @@ class SystemTray extends Menu { if (DEBUG) { logger.debug("Currently using the '{}' desktop", XDG); + +// Properties properties = System.getProperties(); +// for (Map.Entry entry : properties.entrySet()) { +// logger.debug(entry.getKey() + " : " + entry.getValue()); +// } + } + + // must always be set in case of forced tray types + if ("kde".equalsIgnoreCase(XDG)) { + isKDE = true; } if (trayType == null) { if ("unity".equalsIgnoreCase(XDG)) { try { - trayType = AppIndicatorTray.class; + trayType = _AppIndicatorTray.class; } catch (Throwable e) { if (DEBUG) { - logger.error("Cannot initialize AppIndicatorTray", e); + logger.error("Cannot initialize _AppIndicatorTray", e); } } } @@ -353,75 +364,31 @@ class SystemTray extends Menu { // see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/ // see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25 - // XFCE4 is OK to use appindicator, XFCE4 inclusive try { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196); - PrintStream outputStream = new PrintStream(byteArrayOutputStream); - - // ps aux | grep [x]fce - final ShellProcessBuilder shell = new ShellProcessBuilder(outputStream); - shell.setExecutable("ps"); - shell.addArgument("aux"); - shell.start(); - - String output = ShellProcessBuilder.getOutput(byteArrayOutputStream); - // should last us the next 20 years or so. XFCE development is glacially slow. - isNewXFCE = output.contains("/xfce4/") || output.contains("/xfce5/") || - output.contains("/xfce6/") || output.contains("/xfce7/"); - } catch (Throwable e) { + trayType = _GtkStatusIconTray.class; + } catch (Throwable e1) { if (DEBUG) { - logger.error("Cannot detect what version of XFCE is running", e); - } - } - - if (DEBUG) { - logger.error("Is 'new' version of XFCE? {}", isNewXFCE); - } - - if (isNewXFCE) { - try { - trayType = AppIndicatorTray.class; - } catch (Throwable e) { - if (DEBUG) { - logger.error("Cannot initialize AppIndicatorTray", e); - } - - // we can fail on AppIndicator, so this is the fallback - try { - trayType = GtkSystemTray.class; - } catch (Throwable e1) { - if (DEBUG) { - logger.error("Cannot initialize GtkSystemTray", e1); - } - } - } - } else { - try { - trayType = GtkSystemTray.class; - } catch (Throwable e1) { - if (DEBUG) { - logger.error("Cannot initialize GtkSystemTray", e1); - } + logger.error("Cannot initialize _GtkStatusIconTray", e1); } } } else if ("lxde".equalsIgnoreCase(XDG)) { try { - trayType = GtkSystemTray.class; + trayType = _GtkStatusIconTray.class; } catch (Throwable e) { if (DEBUG) { - logger.error("Cannot initialize GtkSystemTray", e); + logger.error("Cannot initialize _GtkStatusIconTray", e); } } } else if ("kde".equalsIgnoreCase(XDG)) { - isKDE = true; + // kde (at least, plasma 5.5.6) requires appindicator try { - trayType = AppIndicatorTray.class; + trayType = _AppIndicatorTray.class; } catch (Throwable e) { if (DEBUG) { - logger.error("Cannot initialize AppIndicatorTray", e); + logger.error("Cannot initialize _AppIndicatorTray", e); } } } @@ -435,34 +402,35 @@ class SystemTray extends Menu { if ("cinnamon".equalsIgnoreCase(GDM)) { try { - trayType = GtkSystemTray.class; + trayType = _GtkStatusIconTray.class; } catch (Throwable e) { if (DEBUG) { - logger.error("Cannot initialize GtkSystemTray", e); + logger.error("Cannot initialize _GtkStatusIconTray", e); } } } else if ("gnome-classic".equalsIgnoreCase(GDM)) { try { - trayType = GtkSystemTray.class; + trayType = _GtkStatusIconTray.class; } catch (Throwable e) { if (DEBUG) { - logger.error("Cannot initialize GtkSystemTray", e); + logger.error("Cannot initialize _GtkStatusIconTray", e); } } } else if ("gnome-fallback".equalsIgnoreCase(GDM)) { try { - trayType = GtkSystemTray.class; + trayType = _GtkStatusIconTray.class; } catch (Throwable e) { if (DEBUG) { - logger.error("Cannot initialize GtkSystemTray", e); + logger.error("Cannot initialize _GtkStatusIconTray", e); } } } else if ("ubuntu".equalsIgnoreCase(GDM)) { // have to install the gnome extension AND customize the restart command trayType = null; + // unity panel service?? GnomeShellExtension.SHELL_RESTART_COMMAND = "unity --replace &"; } } @@ -495,7 +463,7 @@ class SystemTray extends Menu { GnomeShellExtension.install(output); // we might be running gnome-shell, we MIGHT NOT. If we are forced to be app-indicator or swing, don't do this. if (trayType == null) { - trayType = GtkSystemTray.class; + trayType = _GtkStatusIconTray.class; } } } catch (Throwable e) { @@ -533,7 +501,7 @@ class SystemTray extends Menu { if (readLine != null && readLine.contains("indicator-app")) { // make sure we can also load the library (it might be the wrong version) try { - trayType = AppIndicatorTray.class; + trayType = _AppIndicatorTray.class; } catch (Throwable e) { if (DEBUG) { logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", e); @@ -558,7 +526,7 @@ class SystemTray extends Menu { // fallback... if (trayType == null) { - trayType = GtkSystemTray.class; + trayType = _GtkStatusIconTray.class; logger.error("Unable to load the system tray native library. Please write an issue and include your OS type and " + "configuration"); } @@ -575,7 +543,7 @@ class SystemTray extends Menu { if (trayType == null && java.awt.SystemTray.isSupported()) { try { java.awt.SystemTray.getSystemTray(); - trayType = SwingSystemTray.class; + trayType = _SwingTray.class; } catch (Throwable e) { if (DEBUG) { logger.error("Maybe you should grant the AWTPermission `accessSystemTray` in the SecurityManager.", e); @@ -591,7 +559,7 @@ class SystemTray extends Menu { systemTrayMenu = null; } else { - Menu menu_ = null; + final Menu[] menuReference = new Menu[1]; /* * appIndicator/gtk require strings (which is the path) @@ -604,17 +572,17 @@ class SystemTray extends Menu { try { if (OS.isLinux() && - trayType == AppIndicatorTray.class && + trayType == _AppIndicatorTray.class && Gtk.isGtk2 && AppIndicator.isVersion3) { try { - trayType = GtkSystemTray.class; + trayType = _GtkStatusIconTray.class; logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " + "Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'"); } catch (Throwable e) { if (DEBUG) { - logger.error("Cannot initialize GtkSystemTray", e); + logger.error("Cannot initialize _GtkStatusIconTray", e); } logger.error("AppIndicator3 detected with GTK2 and unable to fallback to using GTK2 system tray type." + "AppIndicator3 requires GTK3 to be fully functional, and while this will work -- " + @@ -623,14 +591,29 @@ class SystemTray extends Menu { } } - menu_ = (Menu) trayType.getConstructors()[0].newInstance(systemTray); + // need to set this + Gtk.isKDE = isKDE; - logger.info("Successfully Loaded: {}", trayType.getSimpleName()); + // have to construct swing stuff inside the swing EDT + // this is the safest way to do this. + final Class finalTrayType = trayType; + SwingUtil.invokeAndWait(new Runnable() { + @Override + public + void run() { + try { + menuReference[0] = (Menu) finalTrayType.getConstructors()[0].newInstance(systemTray); + logger.info("Successfully Loaded: {}", finalTrayType.getSimpleName()); + } catch (Exception e) { + logger.error("Unable to create tray type: '" + finalTrayType.getSimpleName() + "'", e); + } + } + }); } catch (Exception e) { logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e); } - systemTrayMenu = menu_; + systemTrayMenu = menuReference[0]; // These install a shutdown hook in JavaFX/SWT, so that when the main window is closed -- the system tray is ALSO closed. @@ -698,14 +681,14 @@ class SystemTray extends Menu { void shutdown() { final Menu menu = systemTrayMenu; - if (menu instanceof AppIndicatorTray) { - ((AppIndicatorTray) menu).shutdown(); + if (menu instanceof _AppIndicatorTray) { + ((_AppIndicatorTray) menu).shutdown(); } - else if (menu instanceof GtkSystemTray) { - ((GtkSystemTray) menu).shutdown(); + else if (menu instanceof _GtkStatusIconTray) { + ((_GtkStatusIconTray) menu).shutdown(); } else { // swing - ((SwingSystemTray) menu).shutdown(); + ((_SwingTray) menu).shutdown(); } } @@ -715,14 +698,14 @@ class SystemTray extends Menu { public String getStatus() { final Menu menu = systemTrayMenu; - if (menu instanceof AppIndicatorTray) { - return ((AppIndicatorTray) menu).getStatus(); + if (menu instanceof _AppIndicatorTray) { + return ((_AppIndicatorTray) menu).getStatus(); } - else if (menu instanceof GtkSystemTray) { - return ((GtkSystemTray) menu).getStatus(); + else if (menu instanceof _GtkStatusIconTray) { + return ((_GtkStatusIconTray) menu).getStatus(); } else { // swing - return ((SwingSystemTray) menu).getStatus(); + return ((_SwingTray) menu).getStatus(); } } @@ -734,28 +717,28 @@ class SystemTray extends Menu { public void setStatus(String statusText) { final Menu menu = systemTrayMenu; - if (menu instanceof AppIndicatorTray) { - ((AppIndicatorTray) menu).setStatus(statusText); + if (menu instanceof _AppIndicatorTray) { + ((_AppIndicatorTray) menu).setStatus(statusText); } - else if (menu instanceof GtkSystemTray) { - ((GtkSystemTray) menu).setStatus(statusText); + else if (menu instanceof _GtkStatusIconTray) { + ((_GtkStatusIconTray) menu).setStatus(statusText); } else { // swing - ((SwingSystemTray) menu).setStatus(statusText); + ((_SwingTray) menu).setStatus(statusText); } } protected void setImage_(File iconPath) { final Menu menu = systemTrayMenu; - if (menu instanceof AppIndicatorTray) { - ((AppIndicatorTray) menu).setImage_(iconPath); + if (menu instanceof _AppIndicatorTray) { + ((_AppIndicatorTray) menu).setImage_(iconPath); } - else if (menu instanceof GtkSystemTray) { - ((GtkSystemTray) menu).setImage_(iconPath); + else if (menu instanceof _GtkStatusIconTray) { + ((_GtkStatusIconTray) menu).setImage_(iconPath); } else { // swing (windows/mac) - ((SwingSystemTray) menu).setImage_(iconPath); + ((_SwingTray) menu).setImage_(iconPath); } } diff --git a/src/dorkbox/systemTray/linux/AppIndicatorTray.java b/src/dorkbox/systemTray/linux/AppIndicatorTray.java deleted file mode 100644 index 11194e82..00000000 --- a/src/dorkbox/systemTray/linux/AppIndicatorTray.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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.linux; - -import java.io.File; -import java.util.concurrent.atomic.AtomicBoolean; - -import com.sun.jna.Pointer; - -import dorkbox.systemTray.SystemTray; -import dorkbox.systemTray.linux.jna.AppIndicator; -import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct; -import dorkbox.systemTray.linux.jna.Gobject; -import dorkbox.systemTray.linux.jna.Gtk; -import dorkbox.systemTray.util.ImageUtils; - -/** - * 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) - * - * http://unity.ubuntu.com/projects/appindicators/ - * - * http://askubuntu.com/questions/16431/putting-an-arbitrary-gtk-widget-into-an-appindicator-indicator - * - * If we re-implement appindicators dbus functionality, we could POTENTIALLY do whatever we want... - * - * https://developer.ubuntu.com/api/devel/ubuntu-12.04/c/dbusmenugtk/index.html - * 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 GtkTypeSystemTray { - 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(); - - public - AppIndicatorTray(final SystemTray systemTray) { - super(systemTray); - - 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"); - } - - ImageUtils.determineIconSize(); - Gtk.startGui(); - - 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); - } - }); - - Gtk.waitForStartup(); - } - - @Override - public - void shutdown() { - if (!shuttingDown.getAndSet(true)) { - dispatch(new Runnable() { - @Override - public - void run() { - // 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; - } - }); - - super.shutdown(); - } - } - - public - void setImage_(final File iconFile) { - dispatch(new Runnable() { - @Override - public - void run() { - AppIndicator.app_indicator_set_icon(appIndicator, iconFile.getAbsolutePath()); - - if (!isActive) { - isActive = true; - - AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE); - } - } - }); - } - - /** - * MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu - */ - protected - void onMenuAdded(final Pointer menu) { - // see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247 - AppIndicator.app_indicator_set_menu(appIndicator, menu); - } -} diff --git a/src/dorkbox/systemTray/linux/GtkEntry.java b/src/dorkbox/systemTray/linux/GtkEntry.java deleted file mode 100644 index 491f83ac..00000000 --- a/src/dorkbox/systemTray/linux/GtkEntry.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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.linux; - -import java.io.File; -import java.io.InputStream; -import java.net.URL; - -import com.sun.jna.Pointer; - -import dorkbox.systemTray.Menu; -import dorkbox.systemTray.MenuEntry; -import dorkbox.systemTray.linux.jna.Gtk; -import dorkbox.systemTray.util.ImageUtils; - -abstract -class GtkEntry implements MenuEntry { - private final int id = Menu.MENU_ID_COUNTER.getAndIncrement(); - - private final GtkMenu parent; - final Pointer _native; - - // this have to be volatile, because they can be changed from any thread - private volatile String text; - - /** - * called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it! - * this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref - */ - GtkEntry(final GtkMenu parent, final Pointer menuItem) { - this.parent = parent; - this._native = menuItem; - } - - public - Menu getParent() { - return parent; - } - - /** - * the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images - * - * always called on the DISPATCH thread - */ - abstract - void setSpacerImage(final boolean everyoneElseHasImages); - - /** - * must always be called in the GTK thread - */ - abstract - void renderText(final String text); - - /** - * must always be called in the GTK thread - */ - abstract - void setImage_(final File imageFile); - - /** - * must always be called in the GTK thread - * called when this item is removed. Necessary to cleanup/remove itself - */ - abstract - void removePrivate(); - - /** - * Enables, or disables the sub-menu entry. - */ - @Override - public - void setEnabled(final boolean enabled) { - if (enabled) { - Gtk.gtk_widget_set_sensitive(_native, Gtk.TRUE); - } else { - Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE); - } - } - - @Override - public - void setShortcut(final char key) { - } - - @Override - public - String getText() { - return text; - } - - @Override - public final - void setText(final String newText) { - text = newText; - - Gtk.dispatch(new Runnable() { - @Override - public - void run() { - renderText(text); - } - }); - } - - @Override - public - void setImage(final File imageFile) { - if (imageFile == null) { - setImage_(null); - } - else { - setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile)); - } - } - - @Override - public final - void setImage(final String imagePath) { - if (imagePath == null) { - setImage_(null); - } - else { - setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath)); - } - } - - @Override - public final - void setImage(final URL imageUrl) { - if (imageUrl == null) { - setImage_(null); - } - else { - setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl)); - } - } - - @Override - public final - void setImage(final String cacheName, final InputStream imageStream) { - if (imageStream == null) { - setImage_(null); - } - else { - setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream)); - } - } - - @Override - public final - void setImage(final InputStream imageStream) { - if (imageStream == null) { - setImage_(null); - } - else { - setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream)); - } - } - - // a child will always remove itself from the parent. - @Override - public final - void remove() { - parent.dispatchAndWait(new Runnable() { - @Override - public - void run() { - Gtk.gtk_container_remove(parent._native, _native); - Gtk.gtk_menu_shell_deactivate(parent._native, _native); - - removePrivate(); - - Gtk.gtk_widget_destroy(_native); - - // have to rebuild the menu now... - parent.deleteMenu(); - parent.createMenu(); - } - }); - } - - @Override - public final - int hashCode() { - return id; - } - - - @Override - public final - boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - - GtkEntry other = (GtkEntry) obj; - return this.id == other.id; - } -} diff --git a/src/dorkbox/systemTray/linux/GtkEntryItem.java b/src/dorkbox/systemTray/linux/GtkEntryItem.java deleted file mode 100644 index f556ba05..00000000 --- a/src/dorkbox/systemTray/linux/GtkEntryItem.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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.linux; - -import java.io.File; - -import com.sun.jna.NativeLong; -import com.sun.jna.Pointer; - -import dorkbox.systemTray.SystemTrayMenuAction; -import dorkbox.systemTray.linux.jna.GCallback; -import dorkbox.systemTray.linux.jna.Gobject; -import dorkbox.systemTray.linux.jna.Gtk; -import dorkbox.systemTray.util.ImageUtils; - -class GtkEntryItem extends GtkEntry implements GCallback { - private static File transparentIcon = null; - - @SuppressWarnings({"FieldCanBeLocal", "unused"}) - private final NativeLong nativeLong; - - // these have to be volatile, because they can be changed from any thread - private volatile SystemTrayMenuAction callback; - private volatile Pointer image; - - // these are necessary BECAUSE GTK menus look funky as hell when there are some menu entries WITH icons and some WITHOUT - protected volatile boolean hasLegitIcon = true; - - // The mnemonic will ONLY show-up once a menu entry is selected. IT WILL NOT show up before then! - // AppIndicators will only show if you use the keyboard to navigate - // GtkStatusIconTray will show on mouse+keyboard movement - private volatile char mnemonicKey = 0; - - /** - * called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it! - * this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref - */ - GtkEntryItem(final GtkMenu parent, final SystemTrayMenuAction callback) { - super(parent, Gtk.gtk_image_menu_item_new_with_mnemonic("")); - this.callback = callback; - - // cannot be done in a static initializer, because the tray icon size might not yet have been determined - if (transparentIcon == null) { - transparentIcon = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE); - } - - if (callback != null) { - Gtk.gtk_widget_set_sensitive(_native, Gtk.TRUE); - nativeLong = Gobject.g_signal_connect_object(_native, "activate", this, null, 0); - } - else { - Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE); - nativeLong = null; - } - } - - @Override - public - void setShortcut(final char key) { - this.mnemonicKey = Character.toLowerCase(key); - - Gtk.dispatch(new Runnable() { - @Override - public - void run() { - renderText(getText()); - } - }); - } - - @Override - public - void setCallback(final SystemTrayMenuAction callback) { - this.callback = callback; - } - - // called by native code - @Override - public - int callback(final Pointer instance, final Pointer data) { - final SystemTrayMenuAction cb = this.callback; - if (cb != null) { - Gtk.proxyClick(getParent(), GtkEntryItem.this, cb); - } - - return Gtk.TRUE; - } - - @Override - public - boolean hasImage() { - return hasLegitIcon; - } - - /** - * 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. - *

- * called on the DISPATCH thread - */ - void setSpacerImage(final boolean everyoneElseHasImages) { - if (hasLegitIcon) { - // we have a legit icon, so there is nothing else we can do. - return; - } - - if (image != null) { - Gtk.gtk_widget_destroy(image); - image = null; - Gtk.gtk_widget_show_all(_native); - } - - if (everyoneElseHasImages) { - image = Gtk.gtk_image_new_from_file(transparentIcon.getAbsolutePath()); - Gtk.gtk_image_menu_item_set_image(_native, image); - - // must always re-set always-show after setting the image - Gtk.gtk_image_menu_item_set_always_show_image(_native, Gtk.TRUE); - } - - Gtk.gtk_widget_show_all(_native); - } - - /** - * must always be called in the GTK thread - */ - void renderText(String text) { - if (this.mnemonicKey != 0) { - // they are CASE INSENSITIVE! - int i = text.toLowerCase() - .indexOf(this.mnemonicKey); - - if (i >= 0) { - text = text.substring(0, i) + "_" + text.substring(i); - } - } - - Gtk.gtk_menu_item_set_label(_native, text); - Gtk.gtk_widget_show_all(_native); - } - - // NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted. - // see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/ - // see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25 - void setImage_(final File imageFile) { - hasLegitIcon = imageFile != null; - - Gtk.dispatch(new Runnable() { - @Override - public - void run() { - if (image != null) { - Gtk.gtk_widget_destroy(image); - image = null; - Gtk.gtk_widget_show_all(_native); - } - - if (imageFile != null) { - image = Gtk.gtk_image_new_from_file(imageFile.getAbsolutePath()); - Gtk.gtk_image_menu_item_set_image(_native, image); - - // must always re-set always-show after setting the image - Gtk.gtk_image_menu_item_set_always_show_image(_native, Gtk.TRUE); - } - - Gtk.gtk_widget_show_all(_native); - } - }); - } - - void removePrivate() { - callback = null; - - if (image != null) { - Gtk.gtk_widget_destroy(image); - image = null; - } - } -} diff --git a/src/dorkbox/systemTray/linux/GtkEntrySeparator.java b/src/dorkbox/systemTray/linux/GtkEntrySeparator.java deleted file mode 100644 index 7b5e4817..00000000 --- a/src/dorkbox/systemTray/linux/GtkEntrySeparator.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.linux; - -import java.io.File; - -import dorkbox.systemTray.MenuSpacer; -import dorkbox.systemTray.SystemTrayMenuAction; -import dorkbox.systemTray.linux.jna.Gtk; - -class GtkEntrySeparator extends GtkEntry implements MenuSpacer { - - /** - * called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it! - * this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref - */ - GtkEntrySeparator(final GtkMenu parent) { - super(parent, Gtk.gtk_separator_menu_item_new()); - } - - @Override - void setSpacerImage(final boolean everyoneElseHasImages) { - } - - // called in the GTK thread - @Override - void renderText(final String text) { - } - - @Override - void setImage_(final File imageFile) { - } - - @Override - void removePrivate() { - } - - @Override - public - boolean hasImage() { - return false; - } - - @Override - public - void setCallback(final SystemTrayMenuAction callback) { - } - - @Override - public - void setEnabled(final boolean enabled) { - } -} diff --git a/src/dorkbox/systemTray/linux/GtkEntryStatus.java b/src/dorkbox/systemTray/linux/GtkEntryStatus.java deleted file mode 100644 index b0b5a9ac..00000000 --- a/src/dorkbox/systemTray/linux/GtkEntryStatus.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.linux; - -import dorkbox.systemTray.SystemTrayMenuAction; -import dorkbox.systemTray.linux.jna.Gtk; - -// you might wonder WHY this extends MenuEntryItem -- the reason is that an AppIndicator "status" will be offset from everyone else, -// where a GtkStatusIconTray + SwingTray will have everything lined up. (with or without icons). This is to normalize how it looks -class GtkEntryStatus extends GtkEntryItem { - - /** - * called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it! - * this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref - */ - GtkEntryStatus(final GtkMenu parent, final String text) { - super(parent, null); - // need that extra space so it matches windows/mac - hasLegitIcon = false; - setText(text); - } - - // called in the GTK thread - @Override - void renderText(final String text) { - // AppIndicator strips out markup text. - // https://mail.gnome.org/archives/commits-list/2016-March/msg05444.html - - Gtk.gtk_menu_item_set_label(_native, text); - Gtk.gtk_widget_show_all(_native); - - Gtk.gtk_widget_set_sensitive(_native, Gtk.FALSE); - } - - @Override - public - void setCallback(final SystemTrayMenuAction callback) { - } - - @Override - public - void setEnabled(final boolean enabled) { - } -} diff --git a/src/dorkbox/systemTray/linux/GtkMenu.java b/src/dorkbox/systemTray/linux/GtkMenu.java deleted file mode 100644 index 0a41f5d0..00000000 --- a/src/dorkbox/systemTray/linux/GtkMenu.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * 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.linux; - -import static dorkbox.systemTray.SystemTray.TIMEOUT; - -import java.io.File; -import java.io.InputStream; -import java.net.URL; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import com.sun.jna.Pointer; - -import dorkbox.systemTray.Menu; -import dorkbox.systemTray.MenuEntry; -import dorkbox.systemTray.SystemTray; -import dorkbox.systemTray.SystemTrayMenuAction; -import dorkbox.systemTray.linux.jna.Gobject; -import dorkbox.systemTray.linux.jna.Gtk; - -@SuppressWarnings("ForLoopReplaceableByForEach") -class GtkMenu extends Menu { - // menu entry that this menu is attached to. Will be NULL when it's the system tray - private final GtkEntryItem menuEntry; - - // must ONLY be created at the end of delete! - volatile Pointer _native; - - // have to make sure no other methods can call obliterate, delete, or create menu once it's already started - private boolean obliterateInProgress = false; - - // called on dispatch - GtkMenu(final SystemTray systemTray, final GtkMenu parent) { - super(systemTray, parent); - - if (parent != null) { - this.menuEntry = new GtkEntryItem(parent, null); - // by default, no callback on a menu entry means it's DISABLED. we have to undo that, because we don't have a callback for menus - menuEntry.setEnabled(true); - } else { - this.menuEntry = null; - } - } - - /** - * Necessary to guarantee all updates occur on the dispatch thread - */ - @Override - protected - void dispatch(final Runnable runnable) { - Gtk.dispatch(runnable); - } - - /** - * Necessary to guarantee all updates occur on the dispatch thread - */ - @Override - protected - void dispatchAndWait(final Runnable runnable) { - final CountDownLatch countDownLatch = new CountDownLatch(1); - - Gtk.dispatch(new Runnable() { - @Override - public - void run() { - try { - runnable.run(); - } 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)) { - if (SystemTray.DEBUG) { - SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " + - "`SystemTray.TIMEOUT` to a value which better suites your environment."); - } else { - throw new RuntimeException("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " + - "`SystemTray.TIMEOUT` to a value which better suites your environment."); - } - } - } catch (InterruptedException e) { - SystemTray.logger.error("Error waiting for dispatch to complete.", new Exception()); - } - } - - - public - void shutdown() { - dispatchAndWait(new Runnable() { - @Override - public - void run() { - obliterateMenu(); - - Gtk.shutdownGui(); - } - }); - } - - /** - * Enables, or disables the sub-menu entry. - */ - @Override - public - void setEnabled(final boolean enabled) { - if (enabled) { - Gtk.gtk_widget_set_sensitive(menuEntry._native, Gtk.TRUE); - } else { - Gtk.gtk_widget_set_sensitive(menuEntry._native, Gtk.FALSE); - } - } - - @Override - public - void addSeparator() { - dispatch(new Runnable() { - @Override - public - void run() { - // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time something is changed. - synchronized (menuEntries) { - // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time something is changed. - deleteMenu(); - - GtkEntry menuEntry = new GtkEntrySeparator(GtkMenu.this); - menuEntries.add(menuEntry); - - createMenu(); - } - } - }); - } - - @Override - public - void setShortcut(final char key) { - menuEntry.setShortcut(key); - } - - @Override - public - String getText() { - return menuEntry.getText(); - } - - @Override - public - void setText(final String newText) { - menuEntry.setText(newText); - } - - @Override - public - void setImage(final File imageFile) { - menuEntry.setImage(imageFile); - } - - @Override - public - void setImage(final String imagePath) { - menuEntry.setImage(imagePath); - } - - @Override - public - void setImage(final URL imageUrl) { - menuEntry.setImage(imageUrl); - } - - @Override - public - void setImage(final String cacheName, final InputStream imageStream) { - menuEntry.setImage(cacheName, imageStream); - } - - @Override - public - void setImage(final InputStream imageStream) { - menuEntry.setImage(imageStream); - } - - @Override - public - boolean hasImage() { - return menuEntry.hasImage(); - } - - // NO OP. - @Override - public - void setCallback(final SystemTrayMenuAction callback) { - } - - /** - * Called inside the gdk_threads block - */ - protected - void onMenuAdded(final Pointer menu) { - // only needed for AppIndicator - } - - // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time something is changed. - /** - * Deletes the menu, and unreferences everything in it. ALSO recreates ONLY the menu object. - */ - void deleteMenu() { - if (obliterateInProgress) { - return; - } - - if (_native != null) { - // have to remove all other menu entries - synchronized (menuEntries) { - for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) { - final MenuEntry menuEntry__ = menuEntries.get(i); - if (menuEntry__ instanceof GtkEntry) { - GtkEntry entry = (GtkEntry) menuEntry__; - - Gobject.g_object_force_floating(entry._native); - Gtk.gtk_container_remove(_native, entry._native); - } - else if (menuEntry__ instanceof GtkMenu) { - GtkMenu subMenu = (GtkMenu) menuEntry__; - - Gobject.g_object_force_floating(subMenu.menuEntry._native); - Gtk.gtk_container_remove(_native, subMenu.menuEntry._native); - } - } - - Gtk.gtk_widget_destroy(_native); - } - } - - if (getParent() != null) { - ((GtkMenu) getParent()).deleteMenu(); - } - - // makes a new one - _native = Gtk.gtk_menu_new(); - - // binds sub-menu to entry (if it exists! it does not for the root menu) - if (menuEntry != null) { - Gtk.gtk_menu_item_set_submenu(menuEntry._native, _native); - } - } - - // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time something is changed. - void createMenu() { - if (obliterateInProgress) { - return; - } - - if (getParent() != null) { - ((GtkMenu) getParent()).createMenu(); - } - - boolean hasImages = false; - - // now add back other menu entries - synchronized (menuEntries) { - for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) { - final MenuEntry menuEntry__ = menuEntries.get(i); - hasImages |= menuEntry__.hasImage(); - } - - for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) { - final MenuEntry menuEntry__ = menuEntries.get(i); - // the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images - if (menuEntry__ instanceof GtkEntry) { - GtkEntry entry = (GtkEntry) menuEntry__; - entry.setSpacerImage(hasImages); - - // will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu' - Gtk.gtk_menu_shell_append(this._native, entry._native); - Gobject.g_object_ref_sink(entry._native); // undoes "floating" - } - else if (menuEntry__ instanceof GtkMenu) { - GtkMenu subMenu = (GtkMenu) menuEntry__; - - // will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu' - Gtk.gtk_menu_shell_append(this._native, subMenu.menuEntry._native); - Gobject.g_object_ref_sink(subMenu.menuEntry._native); // undoes "floating" - - if (subMenu.getParent() != GtkMenu.this) { - // we don't want to "createMenu" on our sub-menu that is assigned to us directly, as they are already doing it - subMenu.createMenu(); - } - } - } - - onMenuAdded(_native); - Gtk.gtk_widget_show_all(_native); - } - } - - /** - * must be called on the dispatch thread - * - * Completely obliterates the menu, no possible way to reconstruct it. - */ - private - void obliterateMenu() { - if (_native != null && !obliterateInProgress) { - obliterateInProgress = true; - - // have to remove all other menu entries - synchronized (menuEntries) { - // a copy is made because sub-menus remove themselves from parents when .remove() is called. If we don't - // do this, errors will be had because indices don't line up anymore. - ArrayList menuEntriesCopy = new ArrayList(this.menuEntries); - - for (int i = 0, menuEntriesSize = menuEntriesCopy.size(); i < menuEntriesSize; i++) { - final MenuEntry menuEntry__ = menuEntriesCopy.get(i); - menuEntry__.remove(); - } - this.menuEntries.clear(); - menuEntriesCopy.clear(); - - Gtk.gtk_widget_destroy(_native); - } - - obliterateInProgress = false; - } - } - - - /** - * Will add a new menu entry, or update one if it already exists - */ - protected - MenuEntry addEntry_(final String menuText, final File imagePath, final SystemTrayMenuAction callback) { - // 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 - - if (menuText == null) { - throw new NullPointerException("Menu text cannot be null"); - } - - // have to wait for the value - final AtomicReference value = new AtomicReference(); - - dispatchAndWait(new Runnable() { - @Override - public - void run() { - synchronized (menuEntries) { - MenuEntry menuEntry = get(menuText); - if (menuEntry == null) { - // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time something is changed. - deleteMenu(); - - menuEntry = new GtkEntryItem(GtkMenu.this, callback); - menuEntry.setText(menuText); - menuEntry.setImage(imagePath); - menuEntries.add(menuEntry); - - createMenu(); - } else if (menuEntry instanceof GtkEntryItem) { - menuEntry.setText(menuText); - menuEntry.setImage(imagePath); - } - - value.set(menuEntry); - } - } - }); - - return value.get(); - } - - /** - * Will add a new menu entry, or update one if it already exists - */ - protected - Menu addMenu_(final String menuText, final File imagePath) { - // 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 - - if (menuText == null) { - throw new NullPointerException("Menu text cannot be null"); - } - - final AtomicReference

value = new AtomicReference(); - - dispatchAndWait(new Runnable() { - @Override - public - void run() { - synchronized (menuEntries) { - MenuEntry menuEntry = get(menuText); - if (menuEntry == null) { - // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time something is changed. - deleteMenu(); - - GtkMenu subMenu = new GtkMenu(getSystemTray(), GtkMenu.this); - subMenu.setText(menuText); - subMenu.setImage(imagePath); - - menuEntries.add(subMenu); - - value.set(subMenu); - - createMenu(); - } else if (menuEntry instanceof GtkMenu) { - menuEntry.setText(menuText); - menuEntry.setImage(imagePath); - - value.set(((GtkMenu) menuEntry)); - } - } - } - }); - - return value.get(); - } - - - // a child will always remove itself from the parent. - @Override - public - void remove() { - dispatchAndWait(new Runnable() { - @Override - public - void run() { - GtkMenu parent = (GtkMenu) getParent(); - - // have to remove from the parent.menuEntries first - for (Iterator iterator = parent.menuEntries.iterator(); iterator.hasNext(); ) { - final MenuEntry entry = iterator.next(); - if (entry == GtkMenu.this) { - iterator.remove(); - break; - } - } - - // cleans up the menu - parent.remove__(null); - - // delete all of the children of this submenu (must happen before the menuEntry is removed) - obliterateMenu(); - - // remove the gtk entry item from our parent menu NATIVE components - // NOTE: this will rebuild the parent menu - if (menuEntry != null) { - menuEntry.remove(); - } else { - // have to rebuild the menu now... - parent.deleteMenu(); - parent.createMenu(); - } - } - }); - } -} diff --git a/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java b/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java deleted file mode 100644 index 1a45d988..00000000 --- a/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2015 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.linux; - -import dorkbox.systemTray.MenuEntry; -import dorkbox.systemTray.SystemTray; - -/** - * Derived from - * Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc. - */ -abstract -class GtkTypeSystemTray extends GtkMenu { - - GtkTypeSystemTray(final SystemTray systemTray) { - super(systemTray, null); - } - - public - String getStatus() { - synchronized (menuEntries) { - MenuEntry menuEntry = menuEntries.get(0); - if (menuEntry instanceof GtkEntryStatus) { - return menuEntry.getText(); - } - } - - return null; - } - - public - void setStatus(final String statusText) { - dispatch(new Runnable() { - @Override - public - void run() { - // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time something is changed. - synchronized (menuEntries) { - // status is ALWAYS at 0 index... - GtkEntry menuEntry = null; - if (!menuEntries.isEmpty()) { - menuEntry = (GtkEntry) menuEntries.get(0); - } - - if (menuEntry instanceof GtkEntryStatus) { - // always delete... - remove(menuEntry); - } - - // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time something is changed. - deleteMenu(); - - if (menuEntry == null) { - menuEntry = new GtkEntryStatus(GtkTypeSystemTray.this, statusText); - // status is ALWAYS at 0 index... - menuEntries.add(0, menuEntry); - } - else if (menuEntry instanceof GtkEntryStatus) { - // change the text? - if (statusText != null) { - menuEntry = new GtkEntryStatus(GtkTypeSystemTray.this, statusText); - menuEntries.add(0, menuEntry); - } - } - - createMenu(); - } - } - }); - } -} diff --git a/src/dorkbox/systemTray/linux/jna/AppIndicator.java b/src/dorkbox/systemTray/linux/jna/AppIndicator.java index e2ffd356..02fd7ec6 100644 --- a/src/dorkbox/systemTray/linux/jna/AppIndicator.java +++ b/src/dorkbox/systemTray/linux/jna/AppIndicator.java @@ -28,7 +28,7 @@ import dorkbox.systemTray.SystemTray; * * Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md */ -@SuppressWarnings("Duplicates") +@SuppressWarnings({"Duplicates", "SameParameterValue", "DanglingJavadoc"}) public class AppIndicator { public static boolean isVersion3 = false; @@ -213,14 +213,14 @@ class AppIndicator { // See: https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12 public static final int CATEGORY_APPLICATION_STATUS = 0; - public static final int CATEGORY_COMMUNICATIONS = 1; - public static final int CATEGORY_SYSTEM_SERVICES = 2; - public static final int CATEGORY_HARDWARE = 3; - public static final int CATEGORY_OTHER = 4; +// public static final int CATEGORY_COMMUNICATIONS = 1; +// public static final int CATEGORY_SYSTEM_SERVICES = 2; +// public static final int CATEGORY_HARDWARE = 3; +// public static final int CATEGORY_OTHER = 4; public static final int STATUS_PASSIVE = 0; public static final int STATUS_ACTIVE = 1; - public static final int STATUS_ATTENTION = 2; +// public static final int STATUS_ATTENTION = 2; public static native AppIndicatorInstanceStruct app_indicator_new(String id, String icon_name, int category); diff --git a/src/dorkbox/systemTray/linux/jna/GCallback.java b/src/dorkbox/systemTray/linux/jna/GCallback.java deleted file mode 100644 index a4d2df67..00000000 --- a/src/dorkbox/systemTray/linux/jna/GCallback.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2015 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.linux.jna; - -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import dorkbox.util.Keep; - -@Keep -public -interface GCallback extends Callback { - /** - * @return Gtk.TRUE if we handled this event - */ - int callback(Pointer instance, Pointer data); -} diff --git a/src/dorkbox/systemTray/linux/jna/Gobject.java b/src/dorkbox/systemTray/linux/jna/Gobject.java index cd993dcc..1179eca0 100644 --- a/src/dorkbox/systemTray/linux/jna/Gobject.java +++ b/src/dorkbox/systemTray/linux/jna/Gobject.java @@ -35,11 +35,7 @@ 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_object_unref(Pointer object); - public static native void g_object_force_floating(Pointer object); - public static native void g_object_ref_sink(Pointer object); - public static native NativeLong g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags); } diff --git a/src/dorkbox/systemTray/linux/jna/Gtk.java b/src/dorkbox/systemTray/linux/jna/Gtk.java index e42be929..863c7245 100644 --- a/src/dorkbox/systemTray/linux/jna/Gtk.java +++ b/src/dorkbox/systemTray/linux/jna/Gtk.java @@ -24,10 +24,7 @@ import java.util.concurrent.TimeUnit; import com.sun.jna.Function; import com.sun.jna.Pointer; -import dorkbox.systemTray.Menu; -import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.SystemTray; -import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.util.JavaFX; import dorkbox.systemTray.util.Swt; @@ -379,23 +376,6 @@ class Gtk { }); } - /** - * required to properly setup the dispatch flag - * @param callback will never be null. - */ - public static - void proxyClick(final Menu parent, final MenuEntry menuEntry, final SystemTrayMenuAction callback) { - Gtk.isDispatch = true; - - try { - callback.onClick(parent.getSystemTray(), parent, menuEntry); - } catch (Throwable throwable) { - SystemTray.logger.error("Error calling menu entry {} click event.", menuEntry.getText(), throwable); - } - - Gtk.isDispatch = false; - } - /** * This would NORMALLY have a 2nd argument that is a String[] -- however JNA direct-mapping DOES NOT support this. We are lucky * enough that we just pass 'null' as the second argument, therefore, we don't have to define that parameter here. @@ -421,22 +401,10 @@ class Gtk { public static native Pointer gtk_menu_new(); - public static native Pointer gtk_menu_item_set_submenu(Pointer menuEntry, Pointer menu); - - - - public static native Pointer gtk_separator_menu_item_new(); - - // to create a menu entry WITH an icon. - public static native Pointer gtk_image_new_from_file(String iconPath); // uses '_' to define which key is the mnemonic public static native Pointer gtk_image_menu_item_new_with_mnemonic(String label); - public static native void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image); - - public static native void gtk_image_menu_item_set_always_show_image(Pointer menu_item, int forceShow); - public static native Pointer gtk_status_icon_new(); public static native void gtk_status_icon_set_from_file(Pointer widget, String label); @@ -450,18 +418,8 @@ class Gtk { public static native void gtk_status_icon_set_name(Pointer widget, String name); - public static native void gtk_menu_popup(Pointer menu, Pointer widget, Pointer bla, Function func, Pointer data, int button, int time); - - public static native void gtk_menu_item_set_label(Pointer menu_item, String label); - public static native void gtk_menu_shell_append(Pointer menu_shell, Pointer child); - public static native void gtk_menu_shell_deactivate(Pointer menu_shell, Pointer child); - - public static native void gtk_widget_set_sensitive(Pointer widget, int sensitive); - - public static native void gtk_container_remove(Pointer menu, Pointer subItem); - public static native void gtk_widget_show_all(Pointer widget); public static native void gtk_widget_destroy(Pointer widget); diff --git a/src/dorkbox/systemTray/swing/AdjustedJMenu.java b/src/dorkbox/systemTray/swing/AdjustedJMenu.java index f85831e5..d4b2e1ea 100644 --- a/src/dorkbox/systemTray/swing/AdjustedJMenu.java +++ b/src/dorkbox/systemTray/swing/AdjustedJMenu.java @@ -22,7 +22,6 @@ import javax.swing.JPopupMenu; import javax.swing.border.EmptyBorder; class AdjustedJMenu extends JMenu { - AdjustedJMenu() { } diff --git a/src/dorkbox/systemTray/swing/SwingEntry.java b/src/dorkbox/systemTray/swing/Entry.java similarity index 98% rename from src/dorkbox/systemTray/swing/SwingEntry.java rename to src/dorkbox/systemTray/swing/Entry.java index 886b2e81..62687a9f 100644 --- a/src/dorkbox/systemTray/swing/SwingEntry.java +++ b/src/dorkbox/systemTray/swing/Entry.java @@ -30,7 +30,7 @@ import dorkbox.systemTray.util.ImageUtils; import dorkbox.util.SwingUtil; abstract -class SwingEntry implements MenuEntry { +class Entry implements MenuEntry { private final int id = Menu.MENU_ID_COUNTER.getAndIncrement(); private final SwingMenu parent; @@ -40,7 +40,7 @@ class SwingEntry implements MenuEntry { private volatile String text; // this is ALWAYS called on the EDT. - SwingEntry(final SwingMenu parent, final JComponent menuItem) { + Entry(final SwingMenu parent, final JComponent menuItem) { this.parent = parent; this._native = menuItem; @@ -204,7 +204,7 @@ class SwingEntry implements MenuEntry { return false; } - SwingEntry other = (SwingEntry) obj; + Entry other = (Entry) obj; return this.id == other.id; } diff --git a/src/dorkbox/systemTray/swing/SwingEntryItem.java b/src/dorkbox/systemTray/swing/EntryItem.java similarity index 95% rename from src/dorkbox/systemTray/swing/SwingEntryItem.java rename to src/dorkbox/systemTray/swing/EntryItem.java index 8fcb16f4..f9cf1814 100644 --- a/src/dorkbox/systemTray/swing/SwingEntryItem.java +++ b/src/dorkbox/systemTray/swing/EntryItem.java @@ -25,7 +25,7 @@ import javax.swing.JMenuItem; import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.util.SwingUtil; -class SwingEntryItem extends SwingEntry { +class EntryItem extends Entry { private final ActionListener swingCallback; @@ -33,7 +33,7 @@ class SwingEntryItem extends SwingEntry { private volatile SystemTrayMenuAction callback; // this is ALWAYS called on the EDT. - SwingEntryItem(final SwingMenu parent, final SystemTrayMenuAction callback) { + EntryItem(final SwingMenu parent, final SystemTrayMenuAction callback) { super(parent, new AdjustedJMenuItem()); this.callback = callback; diff --git a/src/dorkbox/systemTray/swing/SwingEntrySeparator.java b/src/dorkbox/systemTray/swing/EntrySeparator.java similarity index 91% rename from src/dorkbox/systemTray/swing/SwingEntrySeparator.java rename to src/dorkbox/systemTray/swing/EntrySeparator.java index b995e1e8..9f07f58b 100644 --- a/src/dorkbox/systemTray/swing/SwingEntrySeparator.java +++ b/src/dorkbox/systemTray/swing/EntrySeparator.java @@ -22,10 +22,10 @@ import javax.swing.JSeparator; import dorkbox.systemTray.MenuSpacer; import dorkbox.systemTray.SystemTrayMenuAction; -class SwingEntrySeparator extends SwingEntry implements MenuSpacer { +class EntrySeparator extends Entry implements MenuSpacer { // this is ALWAYS called on the EDT. - SwingEntrySeparator(final SwingMenu parent) { + EntrySeparator(final SwingMenu parent) { super(parent, new JSeparator(JSeparator.HORIZONTAL)); } diff --git a/src/dorkbox/systemTray/swing/SwingEntryStatus.java b/src/dorkbox/systemTray/swing/EntryStatus.java similarity index 92% rename from src/dorkbox/systemTray/swing/SwingEntryStatus.java rename to src/dorkbox/systemTray/swing/EntryStatus.java index 3edb61a2..355080f3 100644 --- a/src/dorkbox/systemTray/swing/SwingEntryStatus.java +++ b/src/dorkbox/systemTray/swing/EntryStatus.java @@ -22,10 +22,10 @@ import javax.swing.JMenuItem; import dorkbox.systemTray.MenuStatus; import dorkbox.systemTray.SystemTrayMenuAction; -class SwingEntryStatus extends SwingEntry implements MenuStatus { +class EntryStatus extends Entry implements MenuStatus { // this is ALWAYS called on the EDT. - SwingEntryStatus(final SwingMenu parent, final String label) { + EntryStatus(final SwingMenu parent, final String label) { super(parent, new JMenuItem()); setText(label); } diff --git a/src/dorkbox/systemTray/swing/SwingGenericTray.java b/src/dorkbox/systemTray/swing/GenericTray.java similarity index 61% rename from src/dorkbox/systemTray/swing/SwingGenericTray.java rename to src/dorkbox/systemTray/swing/GenericTray.java index c3d6379a..f11c0f9c 100644 --- a/src/dorkbox/systemTray/swing/SwingGenericTray.java +++ b/src/dorkbox/systemTray/swing/GenericTray.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016 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 javax.swing.JComponent; @@ -7,20 +22,12 @@ import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.util.ImageUtils; -/** - * - */ public abstract -class SwingGenericTray extends SwingMenu { +class GenericTray 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) { + GenericTray(final SystemTray systemTray, final Menu parent, final JComponent _native) { super(systemTray, parent, _native); ImageUtils.determineIconSize(); @@ -30,7 +37,7 @@ class SwingGenericTray extends SwingMenu { String getStatus() { synchronized (menuEntries) { MenuEntry menuEntry = menuEntries.get(0); - if (menuEntry instanceof SwingEntryStatus) { + if (menuEntry instanceof EntryStatus) { return menuEntry.getText(); } } @@ -47,12 +54,12 @@ class SwingGenericTray extends SwingMenu { void run() { synchronized (menuEntries) { // status is ALWAYS at 0 index... - SwingEntry menuEntry = null; + Entry menuEntry = null; if (!menuEntries.isEmpty()) { - menuEntry = (SwingEntry) menuEntries.get(0); + menuEntry = (Entry) menuEntries.get(0); } - if (menuEntry instanceof SwingEntryStatus) { + if (menuEntry instanceof EntryStatus) { // set the text or delete... if (statusText == null) { @@ -66,7 +73,7 @@ class SwingGenericTray extends SwingMenu { } else { // create a new one - menuEntry = new SwingEntryStatus(_this, statusText); + menuEntry = new EntryStatus(_this, statusText); // status is ALWAYS at 0 index... menuEntries.add(0, menuEntry); } diff --git a/src/dorkbox/systemTray/swing/GtkStatusIconTray.java b/src/dorkbox/systemTray/swing/GtkStatusIconTray.java deleted file mode 100644 index f29793b4..00000000 --- a/src/dorkbox/systemTray/swing/GtkStatusIconTray.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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.ArrayList; -import java.util.List; -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 dorkbox.systemTray.SystemTray; -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.util.ScreenUtil; - -/** - * Class for handling all system tray interactions via GTK. - *

- * This is the "old" way to do it, and does not work with some desktop environments. This is a hybrid class, because we want to show the - * swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it. - */ -public -class GtkStatusIconTray extends SwingGenericTray { - private volatile Pointer trayIcon; - - // http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c - // https://github.com/djdeath/glib/blob/master/gobject/gobject.c - - // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) - private final List gtkCallbacks = new ArrayList(); - - // 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 boolean isActive = false; - - // called on the EDT - public - GtkStatusIconTray(final SystemTray systemTray) { - super(systemTray, null, new SwingSystemTrayMenuPopup()); - if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) { - // if we force GTK type system tray, don't attempt to load AppIndicator libs - throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator"); - } - - JPopupMenu popupMenu = (JPopupMenu) _native; - popupMenu.pack(); - popupMenu.setFocusable(true); - - final Runnable 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; - } - 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 - } - - SwingSystemTrayMenuPopup popupMenu = (SwingSystemTrayMenuPopup) _native; - popupMenu.doShow(x, y); - } - }; - - // 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() { - final Pointer trayIcon_ = Gtk.gtk_status_icon_new(); - trayIcon = trayIcon_; - - final GEventCallback gtkCallback = new GEventCallback() { - @Override - public - void callback(Pointer notUsed, final GdkEventButton event) { - // show the swing menu on the EDT - // BUTTON_PRESS only (any mouse click) - if (event.type == 4) { - // show the swing menu on the EDT - dispatch(popupRunnable); - } - } - }; - final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback, - null, 0); - - // have to do this to prevent GC on these objects - gtkCallbacks.add(gtkCallback); - gtkCallbacks.add(button_press_event); - } - }); - - Gtk.waitForStartup(); - - // we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work - Gtk.dispatch(new Runnable() { - @Override - public - void run() { - // by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that. - // If you change "SystemTray" to something else, make sure to change it in extension.js as well - - // necessary for gnome icon detection/placement because we move tray icons around by title. This is hardcoded - // in extension.js, so don't change it - Gtk.gtk_status_icon_set_title(trayIcon, "SystemTray"); - - // can cause - // Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed - // Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed - - // ... so, bizzaro things going on here. These errors DO NOT happen if JavaFX is dispatching the events. - // BUT this is REQUIRED when running JavaFX. For unknown reasons, the title isn't pushed to GTK, so our - // gnome-shell extension cannot see our tray icon -- so naturally, it won't move it to the "top" area and - // we appear broken. - if (SystemTray.isJavaFxLoaded) { - Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray"); - } - } - }); - } - - - @SuppressWarnings("FieldRepeatedlyAccessedInMethod") - public - void shutdown() { - if (!shuttingDown.getAndSet(true)) { - final CountDownLatch countDownLatch = new CountDownLatch(1); - - Gtk.dispatch(new Runnable() { - @Override - public - void run() { - try { - // this hides the indicator - Gtk.gtk_status_icon_set_visible(trayIcon, false); - Gobject.g_object_unref(trayIcon); - - // mark for GC - trayIcon = null; - gtkCallbacks.clear(); - } 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 iconFile) { - Gtk.dispatch(new Runnable() { - @Override - public - void run() { - Gtk.gtk_status_icon_set_from_file(trayIcon, iconFile.getAbsolutePath()); - - if (!isActive) { - isActive = true; - Gtk.gtk_status_icon_set_visible(trayIcon, true); - } - } - }); - - dispatch(new Runnable() { - @Override - public - void run() { - ((SwingSystemTrayMenuPopup) _native).setTitleBarImage(iconFile); - } - }); - } -} diff --git a/src/dorkbox/systemTray/swing/SwingMenu.java b/src/dorkbox/systemTray/swing/SwingMenu.java index d28485ea..f159cdbe 100644 --- a/src/dorkbox/systemTray/swing/SwingMenu.java +++ b/src/dorkbox/systemTray/swing/SwingMenu.java @@ -16,7 +16,7 @@ package dorkbox.systemTray.swing; -import static dorkbox.systemTray.swing.SwingEntry.getVkKey; +import static dorkbox.systemTray.swing.Entry.getVkKey; import java.io.File; import java.io.InputStream; @@ -38,7 +38,7 @@ import dorkbox.util.SwingUtil; class SwingMenu extends Menu { // sub-menu = AdjustedJMenu - // systemtray = SwingSystemTrayMenuPopup + // systemtray = TrayPopup volatile JComponent _native; // this have to be volatile, because they can be changed from any thread @@ -84,7 +84,7 @@ class SwingMenu extends Menu { void run() { synchronized (menuEntries) { synchronized (menuEntries) { - MenuEntry menuEntry = new SwingEntrySeparator(SwingMenu.this); + MenuEntry menuEntry = new EntrySeparator(SwingMenu.this); menuEntries.add(menuEntry); } } @@ -121,12 +121,12 @@ class SwingMenu extends Menu { if (menuEntry == null) { // must always be called on the EDT - menuEntry = new SwingEntryItem(SwingMenu.this, callback); + menuEntry = new EntryItem(SwingMenu.this, callback); menuEntry.setText(menuText); menuEntry.setImage(imagePath); menuEntries.add(menuEntry); - } else if (menuEntry instanceof SwingEntryItem) { + } else if (menuEntry instanceof EntryItem) { menuEntry.setText(menuText); menuEntry.setImage(imagePath); } @@ -317,8 +317,8 @@ class SwingMenu extends Menu { public void run() { _native.setVisible(false); - if (_native instanceof SwingSystemTrayMenuPopup) { - ((SwingSystemTrayMenuPopup) _native).close(); + if (_native instanceof TrayPopup) { + ((TrayPopup) _native).close(); } SwingMenu parent = (SwingMenu) getParent(); diff --git a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java b/src/dorkbox/systemTray/swing/TrayPopup.java similarity index 74% rename from src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java rename to src/dorkbox/systemTray/swing/TrayPopup.java index 7e220b8f..58835452 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java +++ b/src/dorkbox/systemTray/swing/TrayPopup.java @@ -15,8 +15,11 @@ */ package dorkbox.systemTray.swing; +import java.awt.Dimension; import java.awt.Frame; import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; import java.io.File; @@ -33,23 +36,23 @@ import javax.swing.event.PopupMenuListener; import dorkbox.systemTray.SystemTray; import dorkbox.util.OS; +import dorkbox.util.ScreenUtil; /** * This custom popup is required if we want to be able to show images on the menu, * * This is our "golden standard" since we have 100% control over it. */ -public -class SwingSystemTrayMenuPopup extends JPopupMenu { +class TrayPopup extends JPopupMenu { private static final long serialVersionUID = 1L; // NOTE: we can use the "hidden dialog" focus window trick... private JDialog hiddenDialog; private volatile File iconFile; + private volatile Runnable runnable; @SuppressWarnings("unchecked") - public - SwingSystemTrayMenuPopup() { + TrayPopup() { super(); setFocusable(true); // setBorder(new BorderUIResource.EmptyBorderUIResource(0, 0, 0, 0)); // borderUI resource border type will get changed! @@ -106,7 +109,7 @@ class SwingSystemTrayMenuPopup extends JPopupMenu { hiddenDialog.addWindowFocusListener(new WindowFocusListener() { @Override public void windowLostFocus (WindowEvent we ) { - SwingSystemTrayMenuPopup.this.setVisible(false); + TrayPopup.this.setVisible(false); } @Override public void windowGainedFocus (WindowEvent we) { @@ -133,8 +136,62 @@ class SwingSystemTrayMenuPopup extends JPopupMenu { } } + void setOnHideRunnable(final Runnable runnable) { + this.runnable = runnable; + } + + @Override public - void doShow(final int x, final int y) { + void setVisible(final boolean b) { + if (!b) { + Runnable r = this.runnable; + if (r != null) { + r.run(); + } + } + super.setVisible(b); + } + + void close() { + hiddenDialog.setVisible(false); + hiddenDialog.dispatchEvent(new WindowEvent(hiddenDialog, WindowEvent.WINDOW_CLOSING)); + } + + void doShow(final Point point, final int offset) { + + Dimension size = getPreferredSize(); + + 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 -= offset; // 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 right edge of menu to mouse + + x += offset; // display over the stupid appindicator menu (which has to show, this is a major hack!) + } else { + x -= offset; // display over the stupid appindicator menu (which has to show, this is a major hack!) + } + + System.err.println("SHOWING POPUP @" + x + "," + y); + + // critical to get the keyboard listeners working for the popup menu setInvoker(hiddenDialog.getContentPane()); @@ -146,8 +203,5 @@ class SwingSystemTrayMenuPopup extends JPopupMenu { requestFocusInWindow(); } - void close() { - hiddenDialog.setVisible(false); - hiddenDialog.dispatchEvent(new WindowEvent(hiddenDialog, WindowEvent.WINDOW_CLOSING)); - } + } diff --git a/src/dorkbox/systemTray/swing/AppIndicatorTray.java b/src/dorkbox/systemTray/swing/_AppIndicatorTray.java similarity index 71% rename from src/dorkbox/systemTray/swing/AppIndicatorTray.java rename to src/dorkbox/systemTray/swing/_AppIndicatorTray.java index dc9ddd5e..0b37d70f 100644 --- a/src/dorkbox/systemTray/swing/AppIndicatorTray.java +++ b/src/dorkbox/systemTray/swing/_AppIndicatorTray.java @@ -15,19 +15,11 @@ */ 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; @@ -40,7 +32,6 @@ 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; /** @@ -89,7 +80,7 @@ import dorkbox.util.SwingUtil; * http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files */ public -class AppIndicatorTray extends SwingGenericTray { +class _AppIndicatorTray extends GenericTray { private AppIndicatorInstanceStruct appIndicator; private boolean isActive = false; @@ -102,56 +93,21 @@ class AppIndicatorTray extends SwingGenericTray { private final Runnable popupRunnable; public - AppIndicatorTray(final SystemTray systemTray) { - super(systemTray,null, new SwingSystemTrayMenuPopup()); + _AppIndicatorTray(final SystemTray systemTray) { + super(systemTray,null, new TrayPopup()); 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; + TrayPopup popupMenu = (TrayPopup) _native; popupMenu.pack(); popupMenu.setFocusable(true); - - popupRunnable = new Runnable() { + popupMenu.setOnHideRunnable(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 @@ -162,11 +118,23 @@ class AppIndicatorTray extends SwingGenericTray { } }); } + }); + + popupRunnable = new Runnable() { + @Override + public + void run() { + Point point = MouseInfo.getPointerInfo() + .getLocation(); + + TrayPopup popupMenu = (TrayPopup) _native; + popupMenu.doShow(point, SystemTray.DEFAULT_TRAY_SIZE); + } }; // 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); + // trayIcon.setToolTip(_SwingTray.this.appName); Gtk.startGui(); @@ -219,36 +187,20 @@ class AppIndicatorTray extends SwingGenericTray { 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); + // 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(); - } + appIndicator = null; } }); - // 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 @@ -284,7 +236,7 @@ class AppIndicatorTray extends SwingGenericTray { @Override public void run() { - ((SwingSystemTrayMenuPopup) _native).setTitleBarImage(imageFile); + ((TrayPopup) _native).setTitleBarImage(imageFile); } }); } diff --git a/src/dorkbox/systemTray/linux/GtkSystemTray.java b/src/dorkbox/systemTray/swing/_GtkStatusIconTray.java similarity index 74% rename from src/dorkbox/systemTray/linux/GtkSystemTray.java rename to src/dorkbox/systemTray/swing/_GtkStatusIconTray.java index 764b0a45..12092a20 100644 --- a/src/dorkbox/systemTray/linux/GtkSystemTray.java +++ b/src/dorkbox/systemTray/swing/_GtkStatusIconTray.java @@ -13,13 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dorkbox.systemTray.linux; +package dorkbox.systemTray.swing; +import java.awt.MouseInfo; +import java.awt.Point; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import javax.swing.JPopupMenu; + import com.sun.jna.NativeLong; import com.sun.jna.Pointer; @@ -28,15 +32,15 @@ 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; /** * Class for handling all system tray interactions via GTK. *

- * This is the "old" way to do it, and does not work with some desktop environments. + * This is the "old" way to do it, and does not work with some desktop environments. This is a hybrid class, because we want to show the + * swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it. */ public -class GtkSystemTray extends GtkTypeSystemTray { +class _GtkStatusIconTray extends GenericTray { private volatile Pointer trayIcon; // http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c @@ -50,18 +54,38 @@ class GtkSystemTray extends GtkTypeSystemTray { private volatile boolean isActive = false; + // called on the EDT public - GtkSystemTray(final SystemTray systemTray) { - super(systemTray); + _GtkStatusIconTray(final SystemTray systemTray) { + super(systemTray, null, new TrayPopup()); if (SystemTray.FORCE_TRAY_TYPE == SystemTray.TYPE_APP_INDICATOR) { // if we force GTK type system tray, don't attempt to load AppIndicator libs throw new IllegalArgumentException("Unable to start GtkStatusIcon if 'SystemTray.FORCE_TRAY_TYPE' is set to AppIndicator"); } - ImageUtils.determineIconSize(); + JPopupMenu popupMenu = (JPopupMenu) _native; + popupMenu.pack(); + popupMenu.setFocusable(true); + + final Runnable popupRunnable = new Runnable() { + @Override + public + void run() { + Point point = MouseInfo.getPointerInfo() + .getLocation(); + + TrayPopup popupMenu = (TrayPopup) _native; + popupMenu.doShow(point, 0); + } + }; + + // 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(_SwingTray.this.appName); + Gtk.startGui(); - dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { @@ -72,14 +96,16 @@ class GtkSystemTray extends GtkTypeSystemTray { @Override public void callback(Pointer notUsed, final GdkEventButton event) { + // show the swing menu on the EDT // BUTTON_PRESS only (any mouse click) if (event.type == 4) { - Gtk.gtk_menu_popup(_native, null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time); + // show the swing menu on the EDT + dispatch(popupRunnable); } } }; - final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback, - null, 0); + final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event", + gtkCallback, null, 0); // have to do this to prevent GC on these objects gtkCallbacks.add(gtkCallback); @@ -90,7 +116,7 @@ class GtkSystemTray extends GtkTypeSystemTray { Gtk.waitForStartup(); // we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work - dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { @@ -118,11 +144,11 @@ class GtkSystemTray extends GtkTypeSystemTray { @SuppressWarnings("FieldRepeatedlyAccessedInMethod") - @Override public void shutdown() { if (!shuttingDown.getAndSet(true)) { - dispatchAndWait(new Runnable() { + + Gtk.dispatch(new Runnable() { @Override public void run() { @@ -136,13 +162,16 @@ class GtkSystemTray extends GtkTypeSystemTray { } }); - super.shutdown(); + Gtk.shutdownGui(); + + // uses EDT + super.remove(); } } public void setImage_(final File iconFile) { - dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { @@ -154,5 +183,13 @@ class GtkSystemTray extends GtkTypeSystemTray { } } }); + + dispatch(new Runnable() { + @Override + public + void run() { + ((TrayPopup) _native).setTitleBarImage(iconFile); + } + }); } } diff --git a/src/dorkbox/systemTray/swing/SwingSystemTray.java b/src/dorkbox/systemTray/swing/_SwingTray.java similarity index 64% rename from src/dorkbox/systemTray/swing/SwingSystemTray.java rename to src/dorkbox/systemTray/swing/_SwingTray.java index 0ea57d7f..af0f7b7f 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTray.java +++ b/src/dorkbox/systemTray/swing/_SwingTray.java @@ -16,10 +16,7 @@ package dorkbox.systemTray.swing; import java.awt.AWTException; -import java.awt.Dimension; import java.awt.Image; -import java.awt.Point; -import java.awt.Rectangle; import java.awt.SystemTray; import java.awt.TrayIcon; import java.awt.event.MouseAdapter; @@ -30,7 +27,6 @@ import javax.swing.ImageIcon; import javax.swing.JPopupMenu; import dorkbox.systemTray.MenuEntry; -import dorkbox.util.ScreenUtil; /** * Class for handling all system tray interaction, via SWING. @@ -42,16 +38,16 @@ import dorkbox.util.ScreenUtil; */ @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"}) public -class SwingSystemTray extends SwingGenericTray { +class _SwingTray extends GenericTray { volatile SystemTray tray; volatile TrayIcon trayIcon; // Called in the EDT public - SwingSystemTray(final dorkbox.systemTray.SystemTray systemTray) { - super(systemTray, null, new SwingSystemTrayMenuPopup()); + _SwingTray(final dorkbox.systemTray.SystemTray systemTray) { + super(systemTray, null, new TrayPopup()); - SwingSystemTray.this.tray = SystemTray.getSystemTray(); + _SwingTray.this.tray = SystemTray.getSystemTray(); } public @@ -94,40 +90,14 @@ class SwingSystemTray extends SwingGenericTray { // 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); + // trayIcon.setToolTip(_SwingTray.this.appName); trayIcon.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { - Dimension size = _native.getPreferredSize(); - - Point point = e.getPoint(); - 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; - } - 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 - } - - SwingSystemTrayMenuPopup popupMenu = (SwingSystemTrayMenuPopup) _native; - popupMenu.doShow(x, y); + TrayPopup popupMenu = (TrayPopup) _native; + popupMenu.doShow(e.getPoint(), 0); } }); @@ -140,7 +110,7 @@ class SwingSystemTray extends SwingGenericTray { trayIcon.setImage(trayImage); } - ((SwingSystemTrayMenuPopup) _native).setTitleBarImage(iconFile); + ((TrayPopup) _native).setTitleBarImage(iconFile); } }); } diff --git a/src/dorkbox/systemTray/util/ImageUtils.java b/src/dorkbox/systemTray/util/ImageUtils.java index 3f48e3b2..5e263dbf 100644 --- a/src/dorkbox/systemTray/util/ImageUtils.java +++ b/src/dorkbox/systemTray/util/ImageUtils.java @@ -36,6 +36,7 @@ import javax.imageio.stream.ImageInputStream; import javax.swing.ImageIcon; import dorkbox.systemTray.SystemTray; +import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.util.CacheUtil; import dorkbox.util.FileUtil; import dorkbox.util.LocationResolver; @@ -89,6 +90,11 @@ class ImageUtils { } } + // 1 = 16 + // 2 = 32 + // 4 = 64 + // 8 = 128 + if (windowsVersion.startsWith("5.1")) { // Windows XP 5.1.2600 scalingFactor = 1; @@ -139,46 +145,97 @@ class ImageUtils { } else if (OS.isLinux()) { // GtkStatusIcon will USUALLY automatically scale the icon // AppIndicator MIGHT scale the icon (depends on the OS) - try { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196); - PrintStream outputStream = new PrintStream(byteArrayOutputStream); - // gsettings get org.gnome.desktop.interface scaling-factor - final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream); - shellVersion.setExecutable("gsettings"); - shellVersion.addArgument("get"); - shellVersion.addArgument("org.gnome.desktop.interface"); - shellVersion.addArgument("scaling-factor"); - shellVersion.start(); - String output = ShellProcessBuilder.getOutput(byteArrayOutputStream); + // KDE is bonkers. + if (Gtk.isKDE) { + try { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196); + PrintStream outputStream = new PrintStream(byteArrayOutputStream); - if (!output.isEmpty()) { - if (SystemTray.DEBUG) { - SystemTray.logger.debug("Checking scaling factor for GTK environment, should start with 'uint32', value: '{}'", output); + // plasma-desktop -v + // plasmashell --version + final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream); + shellVersion.setExecutable("plasmashell"); + shellVersion.addArgument("--version"); + shellVersion.start(); + + String output = ShellProcessBuilder.getOutput(byteArrayOutputStream); + + if (!output.isEmpty()) { + if (SystemTray.DEBUG) { + SystemTray.logger.debug("Checking plasma KDE environment, should start with 'plasmashell', value: '{}'", output); + } + + // DEFAULT icon size is 16. KDE is bananas on what they did with tray icon scale + // should be: plasmashell 5.6.5 or something + String s = "plasmashell "; + if (output.contains(s)) { + String value = output.substring(output.indexOf(s) + s.length(), output.length() - 1); + + // 1 = 16 + // 2 = 32 + // 4 = 64 + // 8 = 128 + if (value.startsWith("4")) { + scalingFactor = 2; + } else if (value.startsWith("5")) { + scalingFactor = 8; // it is insane how large the icon is + } else { + // assume very low version of plasmashell, default 32 + scalingFactor = 2; + } + } } - - // DEFAULT icon size is 16. HiDpi changes this scale, so we should use it as well. - // should be: uint32 0 or something - if (output.contains("uint32")) { - String value = output.substring(output.indexOf("uint")+7, output.length()-1); - scalingFactor = Integer.parseInt(value); - - // 0 is disabled (no scaling) - // 1 is enabled (default scale) - // 2 is 2x scale - // 3 is 3x scale - // etc - - - // A setting of 2, 3, etc, which is all you can do with scaling-factor - // To enable HiDPI, use gsettings: - // gsettings set org.gnome.desktop.interface scaling-factor 2 + } catch (Throwable e) { + if (SystemTray.DEBUG) { + SystemTray.logger.error("Cannot check plasmashell version", e); } } - } catch (Throwable e) { - if (SystemTray.DEBUG) { - SystemTray.logger.error("Cannot check scaling factor", e); + } else { + // it's a GTK environment or something + + try { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196); + PrintStream outputStream = new PrintStream(byteArrayOutputStream); + + // gsettings get org.gnome.desktop.interface scaling-factor + final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream); + shellVersion.setExecutable("gsettings"); + shellVersion.addArgument("get"); + shellVersion.addArgument("org.gnome.desktop.interface"); + shellVersion.addArgument("scaling-factor"); + shellVersion.start(); + + String output = ShellProcessBuilder.getOutput(byteArrayOutputStream); + + if (!output.isEmpty()) { + if (SystemTray.DEBUG) { + SystemTray.logger.debug("Checking scaling factor for GTK environment, should start with 'uint32', value: '{}'", output); + } + + // DEFAULT icon size is 16. HiDpi changes this scale, so we should use it as well. + // should be: uint32 0 or something + if (output.contains("uint32")) { + String value = output.substring(output.indexOf("uint")+7, output.length()-1); + scalingFactor = Integer.parseInt(value); + + // 0 is disabled (no scaling) + // 1 is enabled (default scale) + // 2 is 2x scale + // 3 is 3x scale + // etc + + + // A setting of 2, 3, etc, which is all you can do with scaling-factor + // To enable HiDPI, use gsettings: + // gsettings set org.gnome.desktop.interface scaling-factor 2 + } + } + } catch (Throwable e) { + if (SystemTray.DEBUG) { + SystemTray.logger.error("Cannot check scaling factor", e); + } } } } else if (OS.isMacOsX()) { @@ -222,6 +279,7 @@ class ImageUtils { } + @SuppressWarnings("ResultOfMethodCallIgnored") public static File getTransparentImage(final int size) { File newFile = new File(TEMP_DIR, size + "_empty.png").getAbsoluteFile(); @@ -243,6 +301,7 @@ class ImageUtils { return newFile; } + @SuppressWarnings("WeakerAccess") public static BufferedImage getTransparentImageAsImage(final int size) { BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);