From ac74d8bdd7b0f1b5cc06f20f7ba482e1ca48a92a Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 27 Oct 2015 02:31:15 +0100 Subject: [PATCH] GtkSystemTray now uses pure gtk for the menu --- .../util/tray/linux/GtkSystemTray.java | 259 +++++++----------- 1 file changed, 93 insertions(+), 166 deletions(-) diff --git a/src/dorkbox/util/tray/linux/GtkSystemTray.java b/src/dorkbox/util/tray/linux/GtkSystemTray.java index 545de20..c947a1b 100644 --- a/src/dorkbox/util/tray/linux/GtkSystemTray.java +++ b/src/dorkbox/util/tray/linux/GtkSystemTray.java @@ -16,20 +16,12 @@ package dorkbox.util.tray.linux; import com.sun.jna.Pointer; -import dorkbox.util.ScreenUtil; -import dorkbox.util.SwingUtil; import dorkbox.util.jna.linux.Gobject; import dorkbox.util.jna.linux.Gtk; -import dorkbox.util.jna.linux.Gtk.GdkEventButton; import dorkbox.util.jna.linux.GtkSupport; import dorkbox.util.tray.SystemTray; import dorkbox.util.tray.SystemTrayMenuAction; -import dorkbox.util.tray.SystemTrayMenuPopup; -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -45,10 +37,10 @@ class GtkSystemTray extends SystemTray { private static final Gobject libgobject = Gobject.INSTANCE; private static final Gtk libgtk = Gtk.INSTANCE; - final Map menuEntries = new HashMap(2); + private final Map menuEntries = new HashMap(2); - volatile SystemTrayMenuPopup jmenu; - volatile JMenuItem connectionStatusItem; + private volatile Pointer menu; + private volatile Pointer connectionStatusItem;; private volatile Pointer trayIcon; @@ -66,90 +58,34 @@ class GtkSystemTray extends SystemTray { @Override public void createTray(String iconName) { - SwingUtil.invokeAndWait(new Runnable() { - @Override - public - void run() { - GtkSystemTray.this.jmenu = new SystemTrayMenuPopup(); - } - }); - libgtk.gdk_threads_enter(); - final Pointer trayIcon = libgtk.gtk_status_icon_new(); - this.trayIcon = trayIcon; - libgtk.gtk_status_icon_set_from_file(trayIcon, iconPath(iconName)); - libgtk.gtk_status_icon_set_tooltip(trayIcon, this.appName); - libgtk.gtk_status_icon_set_visible(trayIcon, true); + this.menu = libgtk.gtk_menu_new(); + + final Pointer trayIcon = libgtk.gtk_status_icon_new(); + libgtk.gtk_status_icon_set_from_file(trayIcon, iconPath(iconName)); - // have to make this a field, to prevent GC on this object this.gtkCallback = new Gobject.GEventCallback() { @Override public - void callback(Pointer system_tray, final GdkEventButton event) { + void callback(Pointer system_tray, final Gtk.GdkEventButton event) { // BUTTON_PRESS only (any mouse click) if (event.type == 4) { - SwingUtil.invokeLater(new Runnable() { - @Override - public - void run() { - // test this using cinnamon (which still uses status icon) - - final SystemTrayMenuPopup jmenu = GtkSystemTray.this.jmenu; - if (jmenu.isVisible()) { - jmenu.setVisible(false); - } - else { - Dimension size = jmenu.getPreferredSize(); - - int x = (int) event.x_root; - int y = (int) event.y_root; - - Point point = new Point(x, y); - Rectangle bounds = ScreenUtil.getScreenBoundsAt(point); - - 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 - } - - // SMALL problem, is that on linux, the popup is BEHIND the tray bar! - // to solve the problem, we anchor the popup above (or below) the tray bar - int distanceToEdgeOfTray = (int) event.y; - // System.err.println(" distance: " + distanceToEdgeOfTray); - // we are at the top of the screen - if (y < 100) { - y += distanceToEdgeOfTray + 4; - } - else { - y -= distanceToEdgeOfTray + 4; - } - - jmenu.setInvoker(jmenu); - jmenu.setLocation(x, y); - jmenu.setVisible(true); - jmenu.requestFocus(); - } - } - }); + libgtk.gtk_menu_popup(menu, null, null, Gtk.gtk_status_icon_position_menu, system_tray, 0, event.time); } } }; - // all the clicks. This is because native menu popups are a pain to figure out, so we cheat and use some java bits to do the popup - libgobject.g_signal_connect_data(trayIcon, "button_press_event", this.gtkCallback, null, null, 0); + + libgobject.g_signal_connect_data(trayIcon, "button_press_event", gtkCallback, menu, null, 0); + +// This is unreliable to use in our gnome-shell notification hook, because of race conditions, it will only sometimes be correct +// libgtk.gtk_status_icon_set_title(trayIcon, "something"); + + libgtk.gtk_status_icon_set_tooltip(trayIcon, this.appName); + libgtk.gtk_status_icon_set_visible(trayIcon, true); + + this.trayIcon = trayIcon; + libgtk.gdk_threads_leave(); this.active = true; @@ -174,43 +110,39 @@ class GtkSystemTray extends SystemTray { this.trayIcon = null; this.widgets.clear(); + // unrefs the children too + libgobject.g_object_unref(this.menu); + this.menu = null; + synchronized (this.menuEntries) { this.menuEntries.clear(); } - this.jmenu.setVisible(false); - this.jmenu.setEnabled(false); - - this.jmenu = null; this.connectionStatusItem = null; GtkSupport.shutdownGTK(); - libgtk.gdk_threads_leave(); + libgtk.gdk_threads_leave(); super.removeTray(); } - @SuppressWarnings("FieldRepeatedlyAccessedInMethod") + @SuppressWarnings({"FieldRepeatedlyAccessedInMethod", "Duplicates"}) @Override public void setStatus(final String infoString, String iconName) { - SwingUtil.invokeAndWait(new Runnable() { - @Override - public - void run() { - if (GtkSystemTray.this.connectionStatusItem == null) { - final JMenuItem connectionStatusItem = new JMenuItem(infoString); - GtkSystemTray.this.connectionStatusItem = connectionStatusItem; - connectionStatusItem.setEnabled(false); - GtkSystemTray.this.jmenu.add(connectionStatusItem); - } - else { - GtkSystemTray.this.connectionStatusItem.setText(infoString); - } - } - }); - libgtk.gdk_threads_enter(); + if (this.connectionStatusItem == null) { + this.connectionStatusItem = libgtk.gtk_menu_item_new_with_label(infoString); + this.widgets.add(this.connectionStatusItem); + libgtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE); + libgtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem); + } + else { + libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString); + } + + libgtk.gtk_widget_show_all(this.connectionStatusItem); + libgtk.gtk_status_icon_set_from_file(GtkSystemTray.this.trayIcon, iconPath(iconName)); libgtk.gdk_threads_leave(); } @@ -218,93 +150,88 @@ class GtkSystemTray extends SystemTray { /** * Will add a new menu entry, or update one if it already exists */ + @SuppressWarnings("Duplicates") @Override public void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) { - SwingUtil.invokeAndWait(new Runnable() { - @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") - @Override - public - void run() { - final Map menuEntries2 = GtkSystemTray.this.menuEntries; + synchronized (this.menuEntries) { + MenuEntry menuEntry = this.menuEntries.get(menuText); - synchronized (menuEntries2) { - JMenuItem menuEntry = menuEntries2.get(menuText); + if (menuEntry == null) { + libgtk.gdk_threads_enter(); - if (menuEntry == null) { - SystemTrayMenuPopup menu = GtkSystemTray.this.jmenu; + Pointer dashboardItem = libgtk.gtk_menu_item_new_with_label(menuText); - menuEntry = new JMenuItem(menuText); - menuEntry.addActionListener(new ActionListener() { + // have to watch out! These can get garbage collected! + Gobject.GCallback gtkCallback = new Gobject.GCallback() { + @Override + public + void callback(Pointer instance, Pointer data) { + GtkSystemTray.this.callbackExecutor.execute(new Runnable() { @Override public - void actionPerformed(ActionEvent e) { -// SystemTrayMenuPopup source = (SystemTrayMenuPopup) ((JMenuItem)e.getSource()).getParent(); - - GtkSystemTray.this.callbackExecutor.execute(new Runnable() { - @Override - public - void run() { - callback.onClick(GtkSystemTray.this); - } - }); + void run() { + callback.onClick(GtkSystemTray.this); } }); - menu.add(menuEntry); + } + }; - menuEntries2.put(menuText, menuEntry); - } - else { - updateMenuEntry(menuText, menuText, callback); - } - } + libgobject.g_signal_connect_data(dashboardItem, "activate", gtkCallback, null, null, 0); + libgtk.gtk_menu_shell_append(this.menu, dashboardItem); + libgtk.gtk_widget_show_all(dashboardItem); + + libgtk.gdk_threads_leave(); + + menuEntry = new MenuEntry(); + menuEntry.dashboardItem = dashboardItem; + menuEntry.gtkCallback = gtkCallback; + + this.menuEntries.put(menuText, menuEntry); } - }); + else { + updateMenuEntry(menuText, menuText, callback); + } + } } /** * Will update an already existing menu entry (or add a new one, if it doesn't exist) */ + @SuppressWarnings("Duplicates") @Override public void updateMenuEntry(final String origMenuText, final String newMenuText, final SystemTrayMenuAction newCallback) { - SwingUtil.invokeAndWait(new Runnable() { - @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") - @Override - public - void run() { - final Map menuEntries2 = GtkSystemTray.this.menuEntries; + synchronized (this.menuEntries) { + MenuEntry menuEntry = this.menuEntries.get(origMenuText); - synchronized (menuEntries2) { - JMenuItem menuEntry = menuEntries2.get(origMenuText); + if (menuEntry != null) { + libgtk.gdk_threads_enter(); + libgtk.gtk_menu_item_set_label(menuEntry.dashboardItem, newMenuText); - if (menuEntry != null) { - ActionListener[] actionListeners = menuEntry.getActionListeners(); - for (ActionListener l : actionListeners) { - menuEntry.removeActionListener(l); - } - - menuEntry.addActionListener(new ActionListener() { + // have to watch out! These can get garbage collected! + menuEntry.gtkCallback = new Gobject.GCallback() { + @Override + public + void callback(Pointer instance, Pointer data) { + GtkSystemTray.this.callbackExecutor.execute(new Runnable() { @Override public - void actionPerformed(ActionEvent e) { - GtkSystemTray.this.callbackExecutor.execute(new Runnable() { - @Override - public - void run() { - newCallback.onClick(GtkSystemTray.this); - } - }); + void run() { + newCallback.onClick(GtkSystemTray.this); } }); - menuEntry.setText(newMenuText); - menuEntry.revalidate(); } - else { - addMenuEntry(origMenuText, newCallback); - } - } + }; + + libgobject.g_signal_connect_data(menuEntry.dashboardItem, "activate", menuEntry.gtkCallback, null, null, 0); + + libgtk.gtk_widget_show_all(menuEntry.dashboardItem); + libgtk.gdk_threads_leave(); } - }); + else { + addMenuEntry(origMenuText, newCallback); + } + } } }