linux-menubar: implemented global menu for unity
authorArtem Bochkarev <artem.bochkarev@jetbrains.com>
Mon, 17 Sep 2018 07:46:08 +0000 (14:46 +0700)
committerArtem Bochkarev <artem.bochkarev@jetbrains.com>
Tue, 18 Sep 2018 05:50:45 +0000 (12:50 +0700)
15 files changed:
bin/linux/libdbm64.so [new file with mode: 0755]
build/groovy/org/jetbrains/intellij/build/IntelliJCoreArtifactsBuilder.groovy
lib/jayatana-1.2.4.jar [deleted file]
native/LinuxGlobalMenu/CMakeLists.txt [new file with mode: 0644]
native/LinuxGlobalMenu/DbusMenuWrapper.c [new file with mode: 0644]
native/LinuxGlobalMenu/DbusMenuWrapper.h [new file with mode: 0644]
native/LinuxGlobalMenu/test.cc [new file with mode: 0644]
native/LinuxGlobalMenu/test.png [new file with mode: 0644]
platform/build-scripts/groovy/org/jetbrains/intellij/build/CommunityLibraryLicenses.groovy
platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.java
platform/platform-impl/src/com/intellij/openapi/ui/FrameWrapper.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java [new file with mode: 0644]
platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeFrameImpl.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeMenuBar.java
platform/platform-impl/src/com/intellij/openapi/wm/impl/WindowManagerImpl.java

diff --git a/bin/linux/libdbm64.so b/bin/linux/libdbm64.so
new file mode 100755 (executable)
index 0000000..bd86a39
Binary files /dev/null and b/bin/linux/libdbm64.so differ
index 7fd8cfafd005421e7aef53b86a1f2adb5f95e61d..7daa41cf614640ac4f6d911bfc732fbd2747181b 100644 (file)
@@ -92,7 +92,7 @@ class IntelliJCoreArtifactsBuilder {
         }
 
         [
-          "ASM", "Guava", "picocontainer", "Trove4j", "cli-parser", "lz4-java", "jayatana", "imgscalr", "batik", "xmlgraphics-commons",
+          "ASM", "Guava", "picocontainer", "Trove4j", "cli-parser", "lz4-java", "imgscalr", "batik", "xmlgraphics-commons",
          "OroMatcher", "jna", "Log4J", "StreamEx"
         ].each {
           projectLibrary(it)
diff --git a/lib/jayatana-1.2.4.jar b/lib/jayatana-1.2.4.jar
deleted file mode 100644 (file)
index e7ac9f3..0000000
Binary files a/lib/jayatana-1.2.4.jar and /dev/null differ
diff --git a/native/LinuxGlobalMenu/CMakeLists.txt b/native/LinuxGlobalMenu/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b14a492
--- /dev/null
@@ -0,0 +1,59 @@
+cmake_minimum_required(VERSION 2.6.0)
+project(dbm)
+
+include(CheckCXXSourceCompiles)
+include (CheckCXXCompilerFlag)
+
+check_cxx_compiler_flag(-fvisibility=hidden __DBUSMENU_HAVE_GCC_VISIBILITY)
+if (__DBUSMENU_HAVE_GCC_VISIBILITY AND NOT WIN32)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
+endif (__DBUSMENU_HAVE_GCC_VISIBILITY AND NOT WIN32)
+
+check_cxx_compiler_flag(-Woverloaded-virtual __DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL)
+if (__DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual")
+endif (__DBUSMENU_HAVE_W_OVERLOADED_VIRTUAL)
+
+check_cxx_compiler_flag(-Wall __DBUSMENU_HAVE_W_ALL)
+if (__DBUSMENU_HAVE_W_ALL)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+endif (__DBUSMENU_HAVE_W_ALL)
+
+
+find_library(LIB_GLIB NAMES glib libglib libglib-2.0.so.0 PATHS /lib/x86_64-linux-gnu)
+MESSAGE("LIB_GLIB: " ${LIB_GLIB})
+
+find_library(LIB_DBUSMENU NAMES libdbusmenu-glib.so PATHS /usr/lib/x86_64-linux-gnu)
+MESSAGE("LIB_DBUSMENU: " ${LIB_DBUSMENU})
+
+find_library(LIB_GIO NAMES libgio-2.0.so.0 PATHS /usr/lib/x86_64-linux-gnu)
+MESSAGE("LIB_GIO: " ${LIB_GIO})
+
+find_library(LIB_GOBJ NAMES libgobject-2.0.so.0 PATHS /usr/lib/x86_64-linux-gnu)
+MESSAGE("LIB_GOBJ: " ${LIB_GOBJ})
+
+set(GLIB_INCLUDE_DIRS /usr/include/glib-2.0 /usr/lib/x86_64-linux-gnu/glib-2.0/include)
+set(DBUSMENU_GLIB_INCLUDE_DIRS /usr/include/libdbusmenu-glib-0.4)
+
+include_directories(
+        ${GLIB_INCLUDE_DIRS}
+        ${DBUSMENU_GLIB_INCLUDE_DIRS}
+)
+
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
+
+set(SOURCE_FILES DbusMenuWrapper.c)
+
+add_library(dbm SHARED ${SOURCE_FILES})
+target_link_libraries(dbm ${LIB_GLIB} ${LIB_GIO} ${LIB_DBUSMENU} ${LIB_GOBJ})
+
+add_executable(dbmexec test.cc)
+target_link_libraries(dbmexec dbm ${LIB_GLIB} ${LIB_GIO} ${LIB_DBUSMENU} ${LIB_DBUSMENU_GTK} ${LIB_GOBJ} ${LIB_GTK} ${LIB_GDK})
+
+add_custom_command(TARGET dbm POST_BUILD
+        COMMAND ${CMAKE_COMMAND} -E copy
+        $<TARGET_FILE:dbm> /home/parallels/projects/IDEA/community/bin/linux/libdbm64.so)
+add_custom_command(TARGET dbm POST_BUILD
+        COMMAND ${CMAKE_COMMAND} -E copy
+        $<TARGET_FILE:dbm> /home/parallels/IdeaProjects/TestMenu/libdbm.so)
diff --git a/native/LinuxGlobalMenu/DbusMenuWrapper.c b/native/LinuxGlobalMenu/DbusMenuWrapper.c
new file mode 100644 (file)
index 0000000..c2b0bfb
--- /dev/null
@@ -0,0 +1,411 @@
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <gio/gio.h>
+#include <libdbusmenu-glib/server.h>
+
+#include "DbusMenuWrapper.h"
+
+#define  DBUS_NAME   "com.canonical.AppMenu.Registrar"
+#define  REG_IFACE  "com.canonical.AppMenu.Registrar"
+#define  REG_OBJECT "/com/canonical/AppMenu/Registrar"
+
+#define  MENUITEM_JHANDLER_PROPERTY "com.intellij.idea.globalmenu.jhandler"
+#define  MENUITEM_UID_PROPERTY "com.intellij.idea.globalmenu.uid"
+
+static GMainLoop *_ourMainLoop = NULL;
+static jlogger _ourLogger = NULL;
+
+typedef struct _WndInfo {
+  guint32 xid;
+  char *menuPath;
+  GDBusProxy *registrar;
+  DbusmenuServer *server;
+  DbusmenuMenuitem *menuroot;
+  jeventcallback jhandler;
+} WndInfo;
+
+static void _error(const char *msg) {
+  if (_ourLogger != NULL)
+    (*_ourLogger)(LOG_LEVEL_ERROR, msg);
+}
+
+static void _info(const char *msg) {
+  if (_ourLogger != NULL)
+    (*_ourLogger)(LOG_LEVEL_INFO, msg);
+}
+
+static void _logmsg(int level, const char *format, ...) {
+  if (_ourLogger == NULL)
+    return;
+
+  va_list args;
+  va_start(args, format);
+
+  char buf[1024];
+  vsnprintf(buf, 1024, format, args);
+  (*_ourLogger)(level, buf);
+
+  va_end(args);
+}
+
+static void _printWndInfo(const WndInfo *wi, char *out, int outLen) {
+  if (out == NULL || outLen <= 0) return;
+  if (wi == NULL) {
+    out[0] = 0;
+    return;
+  }
+
+  snprintf(out, outLen, "xid=0x%X menuPath='%s' registrar=0x%p server=0x%p menuroot=0x%p", wi->xid, wi->menuPath,
+           wi->registrar, wi->server, wi->menuroot);
+}
+
+void runDbusServer(jlogger jlog) {
+  // NOTE: main-loop is necessary for communication with dbus (via glib and it's signals)
+  _ourLogger = jlog;
+  _ourMainLoop = g_main_loop_new(NULL/*will be used g_main_context_default()*/, FALSE);
+  _info("glib main loop is running");
+  g_main_loop_run(_ourMainLoop);
+}
+
+void stopDbusServer() {
+  g_main_loop_quit(_ourMainLoop);
+  // _info("glib main loop is stopped");
+}
+
+static void _onDbusOwnerChange(GObject *gobject, GParamSpec *pspec, gpointer user_data) {
+  GDBusProxy *proxy = G_DBUS_PROXY(gobject);
+
+  gchar *owner = g_dbus_proxy_get_name_owner(proxy);
+
+  if (owner == NULL || owner[0] == '\0') {
+    /* We only care about folks coming on the bus.  Exit quickly otherwise. */
+    _info("new dbus owner is empty, nothing to do");
+    g_free(owner);
+    return;
+  }
+
+  if (g_strcmp0(owner, DBUS_NAME)) {
+    /* We only care about this address, reject all others. */
+    _info("new dbus owner is AppMenu.Registrar, nothing to do");
+    g_free(owner);
+    return;
+  }
+
+  if (user_data == NULL) {
+    _error("_onDbusOwnerChange invoked with null user_data");
+    g_free(owner);
+    return;
+  }
+
+  // _logmsg(LOG_LEVEL_INFO, "new owner '%s'", owner);
+
+  WndInfo *wi = (WndInfo *) user_data;
+
+  if (wi->menuPath == NULL) {
+    _error("_onDbusOwnerChange invoked with empty WndInfo");
+    g_free(owner);
+    return;
+  }
+
+  char buf[1024];
+  _printWndInfo(wi, buf, 1024);
+  _logmsg(LOG_LEVEL_INFO, "window: '%s'", buf);
+
+  GError *error = NULL;
+  g_dbus_proxy_call_sync(wi->registrar, "RegisterWindow",
+                         g_variant_new("(uo)",
+                                       wi->xid,
+                                       wi->menuPath),
+                         G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error);
+  if (error != NULL) {
+    _logmsg(LOG_LEVEL_ERROR, "Unable to re-register window, error: %s", error->message);
+    g_error_free(error);
+    g_free(owner);
+    return;
+  }
+
+  // _info("Window has been successfully re-registered");
+  g_free(owner);
+}
+
+static void _releaseMenuItem(gpointer data) {
+  if (data != NULL) {
+    g_list_free_full(dbusmenu_menuitem_take_children((DbusmenuMenuitem *) data), _releaseMenuItem);
+    g_object_unref(G_OBJECT(data));
+  }
+}
+
+static void _releaseWindow(WndInfo *wi) {
+  if (wi == NULL) return;
+  if (wi->menuPath == NULL) {
+    _error("try to release empty WndInfo");
+    return;
+  }
+
+  free(wi->menuPath);
+  wi->menuPath = NULL;
+
+  if (wi->menuroot != NULL) {
+    _releaseMenuItem(wi->menuroot);
+    wi->menuroot = NULL;
+  }
+
+  if (wi->server != NULL) {
+    g_object_unref(wi->server);
+    wi->server = NULL;
+  }
+
+  if (wi->registrar != NULL) {
+    g_object_unref(wi->registrar);
+    wi->registrar = NULL;
+  }
+
+  free(wi);
+}
+
+WndInfo *registerWindow(long windowXid, jeventcallback handler) {
+  // _info("register new window");
+
+  WndInfo *wi = (WndInfo *) malloc(sizeof(WndInfo));
+  memset(wi, 0, sizeof(WndInfo));
+
+  wi->xid = (guint32) windowXid;
+  wi->menuPath = malloc(64);
+  sprintf(wi->menuPath, "/com/canonical/menu/0x%lx", windowXid);
+
+  wi->menuroot = dbusmenu_menuitem_new();
+  if (wi->menuroot == NULL) {
+    _error("can't create menuitem for new root");
+    _releaseWindow(wi);
+    return NULL;
+  }
+
+  g_object_set_data(G_OBJECT(wi->menuroot), MENUITEM_JHANDLER_PROPERTY, handler);
+  dbusmenu_menuitem_property_set(wi->menuroot, DBUSMENU_MENUITEM_PROP_LABEL, "DBusMenuRoot");
+
+  wi->server = dbusmenu_server_new(wi->menuPath);
+  dbusmenu_server_set_root(wi->server, wi->menuroot);
+
+  wi->registrar = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION,
+                                                G_DBUS_PROXY_FLAGS_NONE,
+                                                NULL,
+                                                DBUS_NAME,
+                                                REG_OBJECT,
+                                                REG_IFACE,
+                                                NULL, NULL);
+  if (wi->registrar == NULL) {
+    // probably need to watch for registrar on dbus
+    // guint watcher = g_bus_watch_name(G_BUS_TYPE_SESSION, DBUS_NAME, G_BUS_NAME_WATCHER_FLAGS_NONE, on_registrar_available, on_registrar_unavailable);
+    _error("can't obtain registrar");
+    _releaseWindow(wi);
+    return NULL;
+  }
+
+  char buf[1024];
+  _printWndInfo(wi, buf, 1024);
+  _logmsg(LOG_LEVEL_INFO, "new window info: %s", buf);
+
+  GError *error = NULL;
+  g_dbus_proxy_call_sync(
+    wi->registrar,
+    "RegisterWindow",
+    g_variant_new("(uo)", windowXid, wi->menuPath),
+    G_DBUS_CALL_FLAGS_NONE,
+    -1,
+    NULL,
+    &error);
+
+  if (error != NULL) {
+    _logmsg(LOG_LEVEL_ERROR, "Unable to register window, error: %s", error->message);
+    g_error_free(error);
+    _releaseWindow(wi);
+    return NULL;
+  }
+
+  wi->jhandler = handler;
+  g_signal_connect(wi->registrar, "notify::g-name-owner", G_CALLBACK(_onDbusOwnerChange), wi);
+
+  return wi;
+}
+
+static gboolean _execReleaseWindow(gpointer user_data) {
+  _releaseWindow(user_data);
+  return FALSE;
+}
+
+void releaseWindowOnMainLoop(WndInfo *wi) {
+  // _info("scheduled releaseWindowOnMainLoop");
+  g_idle_add(_execReleaseWindow, wi);
+}
+
+static const char * _getItemLabel(DbusmenuMenuitem *item) {
+  const gchar * label = dbusmenu_menuitem_property_get(item, DBUSMENU_MENUITEM_PROP_LABEL);
+  return label == NULL ? "null" : label;
+}
+
+void clearRootMenu(WndInfo *wi) {
+  if (wi == NULL || wi->menuroot == NULL) return;
+  // _info("clear root");
+  g_list_free_full(dbusmenu_menuitem_take_children(wi->menuroot), _releaseMenuItem);
+}
+
+void clearMenu(DbusmenuMenuitem *menu) {
+  if (menu == NULL) return;
+  // _logmsg(LOG_LEVEL_INFO, "clear menu %s", _getItemLabel(menu));
+  g_list_free_full(dbusmenu_menuitem_take_children(menu), _releaseMenuItem);
+}
+
+//
+// menu <==> internal_node
+// menuItem <==> leaf
+//
+
+static const char *_type2str(int type) {
+  switch (type) {
+    case EVENT_OPENED:
+      return "event-opened";
+    case EVENT_CLOSED:
+      return "event-closed";
+    case EVENT_CLICKED:
+      return "event-clicked";
+    case SIGNAL_ACTIVATED:
+      return "sig-activated";
+    case SIGNAL_ABOUT_TO_SHOW:
+      return "sig-about-to-show";
+    case SIGNAL_SHOWN:
+      return "sig-shown";
+    case SIGNAL_CHILD_ADDED:
+      return "sig-child-added";
+  }
+  return "unknown event type";
+}
+
+static void _handleItemSignal(DbusmenuMenuitem *item, int type) {
+//    const gchar * label = dbusmenu_menuitem_property_get(item, DBUSMENU_MENUITEM_PROP_LABEL);
+//    _logmsg(LOG_LEVEL_INFO, "_onItemSignal %s, item '%s'", _type2str(type), label == NULL ? "null" : label);
+
+  gpointer jhandler = g_object_get_data(G_OBJECT(item), MENUITEM_JHANDLER_PROPERTY);
+  if (jhandler == NULL) {
+    _error("_onItemSignal: null jhandler");
+    return;
+  }
+
+  const int uid = dbusmenu_menuitem_property_get_int(item, MENUITEM_UID_PROPERTY);
+  (*((jeventcallback) jhandler))(uid, type);
+}
+
+
+static void _onItemEvent(DbusmenuMenuitem *item, const char *event) {
+//    _logmsg(LOG_LEVEL_INFO, "_onItemEvent %s", event);
+
+  int eventType = -1;
+  if (strcmp(DBUSMENU_MENUITEM_EVENT_OPENED, event) == 0)
+    eventType = EVENT_OPENED;
+  else if (strcmp(DBUSMENU_MENUITEM_EVENT_CLOSED, event) == 0)
+    eventType = EVENT_CLOSED;
+  else if (strcmp(DBUSMENU_MENUITEM_EVENT_ACTIVATED, event) == 0)
+    eventType = EVENT_CLICKED;
+  else
+    _error("unknown event type");
+
+  _handleItemSignal(item, eventType);
+}
+
+static void _onItemActivated(DbusmenuMenuitem *item) {
+  _handleItemSignal(item, SIGNAL_ACTIVATED);
+}
+
+static void _onItemAboutToShow(DbusmenuMenuitem *item) {
+  _handleItemSignal(item, SIGNAL_ABOUT_TO_SHOW);
+}
+
+static void _onItemShowToUser(DbusmenuMenuitem *item) {
+  _handleItemSignal(item, SIGNAL_SHOWN);
+}
+
+static void _onItemChildAdded(DbusmenuMenuitem *parent, DbusmenuMenuitem *child, guint32 pos) {
+  _handleItemSignal(parent, SIGNAL_CHILD_ADDED);
+}
+
+DbusmenuMenuitem *addRootMenu(WndInfo *wi, int uid, const char * label) {
+  if (wi == NULL || wi->menuroot == NULL)
+    return NULL;
+  // _logmsg(LOG_LEVEL_INFO, "add root %d", uid);
+  return addMenuItem(wi->menuroot, uid, label, true);
+}
+
+DbusmenuMenuitem *addMenuItem(DbusmenuMenuitem *parent, int uid, const char * label, int type) {
+  // _logmsg(LOG_LEVEL_INFO, "add menu item %s (%d) [p %s]", label, uid, _getItemLabel(parent));
+
+  DbusmenuMenuitem *item = dbusmenu_menuitem_new();
+
+  dbusmenu_menuitem_property_set_int(item, MENUITEM_UID_PROPERTY, uid);
+  dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_LABEL, label);
+  if (type == ITEM_SUBMENU)
+    dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY, DBUSMENU_MENUITEM_CHILD_DISPLAY_SUBMENU);
+  else if (type == ITEM_CHECK)
+    dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, DBUSMENU_MENUITEM_TOGGLE_CHECK);
+  else if (type == ITEM_RADIO)
+    dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_TOGGLE_TYPE, DBUSMENU_MENUITEM_TOGGLE_RADIO);
+
+  dbusmenu_menuitem_property_set_bool(item, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
+
+  g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_EVENT, G_CALLBACK(_onItemEvent), NULL);
+  g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_ABOUT_TO_SHOW, G_CALLBACK(_onItemAboutToShow), NULL);
+  // g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_SHOW_TO_USER, G_CALLBACK(_onItemShowToUser), NULL);
+  g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_ITEM_ACTIVATED, G_CALLBACK(_onItemActivated), NULL);
+  // g_signal_connect(G_OBJECT(item), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED, G_CALLBACK(_onItemChildAdded), NULL);
+
+  if (parent != NULL) {
+    gpointer data = g_object_get_data(G_OBJECT(parent), MENUITEM_JHANDLER_PROPERTY);
+    if (data == NULL)
+      _logmsg(LOG_LEVEL_ERROR, "parent of item %d hasn't jhandler", uid);
+    g_object_set_data(G_OBJECT(item), MENUITEM_JHANDLER_PROPERTY, data);
+    dbusmenu_menuitem_child_append(parent, item);
+  }
+
+  return item;
+}
+
+DbusmenuMenuitem* addSeparator(DbusmenuMenuitem * parent, int uid) {
+  DbusmenuMenuitem* item = dbusmenu_menuitem_new();
+  dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_TYPE, "separator");
+  dbusmenu_menuitem_property_set_int(item, MENUITEM_UID_PROPERTY, uid);
+  dbusmenu_menuitem_property_set_bool(item, DBUSMENU_MENUITEM_PROP_VISIBLE, TRUE);
+  if (parent != NULL)
+    dbusmenu_menuitem_child_append(parent, item);
+
+  return item;
+}
+
+void setItemLabel(DbusmenuMenuitem *item, const char *label) {
+  dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_LABEL, label);
+}
+
+void setItemEnabled(DbusmenuMenuitem *item, bool isEnabled) {
+  dbusmenu_menuitem_property_set_bool(item, DBUSMENU_MENUITEM_PROP_ENABLED, (gboolean) isEnabled);
+}
+
+void setItemIcon(DbusmenuMenuitem *item, const char *iconBytesPng, int iconBytesCount) {
+  const gboolean propreturn = dbusmenu_menuitem_property_set_byte_array(item, DBUSMENU_MENUITEM_PROP_ICON_DATA,
+                                                                        (guchar *) iconBytesPng, iconBytesCount);
+  // NOTE: memory copied (try to call memset(iconBytesPng, 0, iconBytesCount) after)
+
+//  if (propreturn)
+//    _logmsg(LOG_LEVEL_INFO, "\tset %d icon bytes for item %s", iconBytesCount, _getItemLabel(item));
+//  else
+//    _logmsg(LOG_LEVEL_ERROR, "\tcan't set %d icon bytes for item %s", iconBytesCount, _getItemLabel(item));
+}
+
+static gboolean _execJRunnable(gpointer user_data) {
+  (*((jrunnable) user_data))();
+  return FALSE;
+}
+
+void execOnMainLoop(jrunnable run) {
+  // _info("scheduled execOnMain");
+  g_idle_add(_execJRunnable, run);
+}
\ No newline at end of file
diff --git a/native/LinuxGlobalMenu/DbusMenuWrapper.h b/native/LinuxGlobalMenu/DbusMenuWrapper.h
new file mode 100644 (file)
index 0000000..ef0c8ea
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef DBM_DBUSMENUWRAPPER_H
+#define DBM_DBUSMENUWRAPPER_H
+
+#include <stdbool.h>
+
+#define LOG_LEVEL_ERROR 10
+#define LOG_LEVEL_INFO 5
+
+#define EVENT_OPENED 0
+#define EVENT_CLOSED 1
+#define EVENT_CLICKED 2
+#define SIGNAL_ACTIVATED 3
+#define SIGNAL_ABOUT_TO_SHOW 4
+#define SIGNAL_SHOWN 5
+#define SIGNAL_CHILD_ADDED 6
+
+#define ITEM_SIMPLE 0
+#define ITEM_SUBMENU 1
+#define ITEM_CHECK 2
+#define ITEM_RADIO 3
+
+typedef void (*jeventcallback)(int/*uid*/, int/*ev-type*/);
+typedef void (*jlogger)(int/*level*/, const char*);
+typedef void (*jrunnable)(void);
+
+typedef struct _WndInfo WndInfo;
+typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+// runs main loop of glib (which is needed to communicate with dbus)
+// must be called from java thread (to avoid detach, so jna-callbacks will be invoked from same thread)
+void runDbusServer(jlogger jlogger);
+void stopDbusServer();
+void execOnMainLoop(jrunnable run);
+
+WndInfo* registerWindow(long windowXid, jeventcallback handler); // creates menu-server and binds to xid
+void releaseWindowOnMainLoop(WndInfo* wi);
+
+void clearRootMenu(WndInfo* wi);
+void clearMenu(DbusmenuMenuitem* menu);
+
+DbusmenuMenuitem* addRootMenu(WndInfo* wi, int uid, const char * label);
+DbusmenuMenuitem* addMenuItem(DbusmenuMenuitem * parent, int uid, const char * label, int type);
+DbusmenuMenuitem* addSeparator(DbusmenuMenuitem * parent, int uid);
+
+void setItemLabel(DbusmenuMenuitem* item, const char * label);
+void setItemEnabled(DbusmenuMenuitem* item, bool isEnabled);
+void setItemIcon(DbusmenuMenuitem* item, const char * iconBytesPng, int iconBytesCount);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //DBM_DBUSMENUWRAPPER_H
diff --git a/native/LinuxGlobalMenu/test.cc b/native/LinuxGlobalMenu/test.cc
new file mode 100644 (file)
index 0000000..ec91f80
--- /dev/null
@@ -0,0 +1,55 @@
+//
+// Created by parallels on 8/1/18.
+//
+
+#include "DbusMenuWrapper.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+
+
+static void _onDestroyWindow(void) {
+    gtk_main_quit();
+    return;
+}
+
+static void _testHandler(int uid, int evtype) {
+    int n = 0;
+}
+
+int main (int argv, char ** argc) {
+    gtk_init(&argv, &argc);
+
+    GtkWidget * window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(_onDestroyWindow), NULL);
+
+    gtk_widget_show(window);
+
+    // create register menu object for window
+    long wndxid = GDK_WINDOW_XID (gtk_widget_get_window (window));
+    WndInfo* wi = registerWindow(wndxid, &_testHandler);
+
+    // populate menu
+    DbusmenuMenuitem* root1 = addRootMenu(wi, 1);
+    setItemLabel(root1, "root1");
+
+    FILE *f;
+    char buffer[1024*1024];
+    gsize length;
+
+    f = fopen ("../test.png", "r");
+    length = fread (buffer, 1, sizeof(buffer), f);
+    fclose (f);
+
+    DbusmenuMenuitem* item1 = addMenuItem(root1, 2, true);
+    setItemLabel(item1, "item1");
+    setItemIcon(item1, buffer, length);
+    DbusmenuMenuitem* sub1 = addMenuItem(sub1, 3, false);
+    setItemLabel(sub1, "sub1");
+    setItemIcon(sub1, buffer, length);
+
+    // run main loop
+    gtk_main();
+
+    return 0;
+}
diff --git a/native/LinuxGlobalMenu/test.png b/native/LinuxGlobalMenu/test.png
new file mode 100644 (file)
index 0000000..055f81c
Binary files /dev/null and b/native/LinuxGlobalMenu/test.png differ
index 9546ca0722122738aa6c8ce1950dc02393050dad..9f0cea79951e242f0a879a5397e6fab8fb026079 100644 (file)
@@ -224,8 +224,6 @@ class CommunityLibraryLicenses {
                        licenseUrl: "http://glassfish.java.net/public/CDDL+GPL_1_1.html"),
     new LibraryLicense(name: "Jaxen", version: "", license: "modified Apache", url: "https://github.com/jaxen-xpath/jaxen",
                        licenseUrl: "https://github.com/jaxen-xpath/jaxen/blob/master/LICENSE.txt"),
-    new LibraryLicense(name: "jayatana", libraryName: "jayatana", version: "1.2.4", license: "MIT License",
-                       url: "https://code.google.com/p/java-swing-ayatana/", licenseUrl: "http://opensource.org/licenses/mit-license.php"),
     new LibraryLicense(name: "JBcrypt", libraryName: "trilead-ssh2", version: "1.0.0", license: "ISC License",
                        licenseUrl: "https://github.com/jeremyh/jBCrypt/blob/master/LICENSE", url: "https://github.com/jeremyh/jBCrypt"),
     new LibraryLicense(name: "JCIP Annotations", libraryName: "jcip", license: "Creative Commons Attribution License",
index b78624d37fd83010613f54913cffa8d770ced013..13b79b28205d15d7eb9b29b894f1802d3858546e 100644 (file)
@@ -236,7 +236,7 @@ public final class ActionMenu extends JBMenu {
     }
   }
 
-  private void clearItems() {
+  public void clearItems() {
     if (SystemInfo.isMacSystemMenu && myPlace.equals(ActionPlaces.MAIN_MENU)) {
       for (Component menuComponent : getMenuComponents()) {
         if (menuComponent instanceof ActionMenu) {
@@ -258,7 +258,7 @@ public final class ActionMenu extends JBMenu {
     validate();
   }
 
-  private void fillMenu() {
+  public void fillMenu() {
     DataContext context;
     boolean mayContextBeInvalid;
 
index 6b1f670715cb2d2bbad57159589d46088c46bbf6..5b0046a86d00b1cf3b0ba806e20abb218b22ebd1 100644 (file)
@@ -351,20 +351,7 @@ public class FrameWrapper implements Disposable, DataProvider {
       FrameState.setFrameStateListener(this);
       setGlassPane(new IdeGlassPaneImpl(getRootPane(), true));
 
-      boolean setMenuOnFrame = SystemInfo.isMac;
-
-      if (SystemInfo.isLinux) {
-        final String desktop = System.getenv("XDG_CURRENT_DESKTOP");
-        if ("Unity".equals(desktop) || "Unity:Unity7".equals(desktop)) {
-         try {
-           Class.forName("com.jarego.jayatana.Agent");
-           setMenuOnFrame = true;
-         }
-         catch (ClassNotFoundException e) {
-           // ignore
-         }
-       }
-      }
+      final boolean setMenuOnFrame = SystemInfo.isMac || IdeMenuBar.isLinuxGlobalMenuAvailable();
 
       if (setMenuOnFrame) {
         setJMenuBar(new IdeMenuBar(ActionManagerEx.getInstanceEx(), DataManager.getInstance()));
diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java
new file mode 100644 (file)
index 0000000..5be48bd
--- /dev/null
@@ -0,0 +1,436 @@
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.openapi.wm.impl;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.impl.ActionMenu;
+import com.intellij.openapi.actionSystem.impl.ActionMenuItem;
+import com.intellij.openapi.actionSystem.impl.StubItem;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.util.lang.UrlClassLoader;
+import com.intellij.util.ui.UIUtil;
+import com.sun.jna.Callback;
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Pointer;
+import org.jetbrains.annotations.NotNull;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.peer.ComponentPeer;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+interface GlobalMenuLib extends Library {
+  void runDbusServer(JLogger jlogger);
+  void stopDbusServer();
+  void execOnMainLoop(JRunnable run);
+
+  Pointer registerWindow(long windowXid, EventHandler handler);
+  void releaseWindowOnMainLoop(Pointer wi);
+
+  void clearRootMenu(Pointer wi);
+  void clearMenu(Pointer dbmi);
+
+  Pointer addRootMenu(Pointer wi, int uid, String label);
+  Pointer addMenuItem(Pointer parent, int uid, String label, int type);
+  Pointer addSeparator(Pointer wi, int uid);
+
+  void setItemLabel(Pointer item, String label);
+  void setItemEnabled(Pointer item, boolean isEnabled);
+  void setItemIcon(Pointer item, byte[] iconBytesPng, int iconBytesCount);
+
+  interface EventHandler extends Callback {
+    void handleEvent(int uid, int eventType);
+  }
+  interface JLogger extends Callback {
+    void log(int level, String msg);
+  }
+  interface JRunnable extends Callback {
+    void run();
+  }
+
+  int LOG_LEVEL_ERROR = 10;
+  int LOG_LEVEL_INFO = 5;
+
+  int EVENT_OPENED = 0;
+  int EVENT_CLOSED = 1;
+  int EVENT_CLICKED = 2;
+
+  int SIGNAL_ACTIVATED = 3;
+  int SIGNAL_ABOUT_TO_SHOW = 4;
+  int SIGNAL_SHOWN = 5;
+  int SIGNAL_CHILD_ADDED = 6;
+
+  int ITEM_SIMPLE = 0;
+  int ITEM_SUBMENU = 1;
+  int ITEM_CHECK = 2;
+  int ITEM_RADIO = 3;
+}
+
+public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
+  private static final Logger LOG = Logger.getInstance(GlobalMenuLinux.class);
+  private static final GlobalMenuLib ourLib;
+  private static final GlobalMenuLib.JLogger ourGLogger;
+  private static final Thread ourGlibMainLoopThread;
+
+  private final long myXid;
+  private List<MenuItemInternal> myRoots;
+  private Pointer myWindowHandle;
+  private GlobalMenuLib.JRunnable myGlibLoopRunnable; // only to hold runnable object until it executed
+
+  static {
+    ourLib = _loadLibrary();
+    if (ourLib != null) {
+      ourGLogger = (level, msg) -> {
+        if (level == GlobalMenuLib.LOG_LEVEL_INFO) {
+          // System.out.println("INFO: " + msg);
+          LOG.info(msg);
+        } else {
+          // System.out.println("ERROR: " + msg);
+          LOG.error(msg);
+        }
+      };
+      ourGlibMainLoopThread = new Thread(()->ourLib.runDbusServer(ourGLogger), "Glib-main-loop");
+      ourGlibMainLoopThread.start();
+    } else {
+      ourGLogger = null;
+      ourGlibMainLoopThread = null;
+    }
+  }
+
+  public static GlobalMenuLinux create(@NotNull JFrame frame) {
+    final long xid = _getX11WindowXid(frame);
+    return xid == 0 ? null : new GlobalMenuLinux(xid);
+  }
+
+  private GlobalMenuLinux(long xid) {
+    LOG.info("created instance of GlobalMenuLinux for xid=0x" + Long.toHexString(xid));
+    myXid = xid;
+  }
+
+  @Override
+  public void dispose() {
+    if (ourLib == null)
+      return;
+
+    if (myWindowHandle != null)
+      ourLib.releaseWindowOnMainLoop(myWindowHandle);
+  }
+
+  public void setRoots(List<ActionMenu> roots) {
+    if (ourLib == null)
+      return;
+
+    ApplicationManager.getApplication().assertIsDispatchThread();
+
+    final List<MenuItemInternal> newRoots = roots != null && !roots.isEmpty() ? new ArrayList<>(roots.size()) : null;
+    if (newRoots != null) {
+      for (ActionMenu am: roots) {
+        // final int uid = myUid2MI.size();
+        final int uid = System.identityHashCode(am);
+        final MenuItemInternal mi = new MenuItemInternal(uid, GlobalMenuLib.ITEM_SUBMENU, am.getText(), null, true, am);
+        //myUid2MI.put(uid, mi);
+        newRoots.add(mi);
+      }
+    }
+
+    myRoots = newRoots;
+
+    myGlibLoopRunnable = () -> {
+      // Glib-loop
+      if (myWindowHandle == null) {
+        myWindowHandle = ourLib.registerWindow(myXid, this);
+        if (myWindowHandle == null) {
+          LOG.error("AppMenu-service can't register xid " + myXid);
+          return;
+        }
+      }
+
+      ourLib.clearRootMenu(myWindowHandle);
+
+      final List<MenuItemInternal> croots = myRoots;
+      if (croots == null || croots.isEmpty())
+        return;
+
+      for (MenuItemInternal mi: croots)
+        mi.nativePeer = ourLib.addRootMenu(myWindowHandle, mi.uid, mi.txt);
+    };
+
+    ourLib.execOnMainLoop(myGlibLoopRunnable); // TODO: clean ref myGlibLoopRunnable
+  }
+
+  private MenuItemInternal _findMenuItem(int uid) {
+    return _findMenuItem(myRoots, uid);
+  }
+
+  private MenuItemInternal _findMenuItem(List<MenuItemInternal> kids, int uid) {
+    if (kids == null || kids.isEmpty())
+      return null;
+
+    for (MenuItemInternal mi: kids) {
+      if (mi.uid == uid)
+        return mi;
+      final MenuItemInternal child2 = _findMenuItem(mi.children, uid);
+      if (child2 != null)
+        return child2;
+    }
+    return null;
+  }
+
+  private static String _evtype2str(int eventType) {
+    switch (eventType) {
+      case GlobalMenuLib.EVENT_OPENED:
+        return "event-opened";
+      case GlobalMenuLib.EVENT_CLOSED:
+        return "event-closed";
+      case GlobalMenuLib.EVENT_CLICKED:
+        return "event-clicked";
+      case GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW:
+        return "signal-about-to-show";
+      case GlobalMenuLib.SIGNAL_ACTIVATED:
+        return "signal-activated";
+      case GlobalMenuLib.SIGNAL_CHILD_ADDED:
+        return "signal-child-added";
+      case GlobalMenuLib.SIGNAL_SHOWN:
+        return "signal-shown";
+    }
+    return "unknown-event-type-"+eventType;
+  }
+
+  private static byte[] _icon2png(Icon icon) {
+    if (icon == null || icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0)
+      return null;
+
+    final BufferedImage img = UIUtil.createImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
+    final Graphics2D g2d = img.createGraphics();
+    icon.paintIcon(null, g2d, 0, 0);
+    g2d.dispose();
+
+    final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    try {
+      ImageIO.write(img, "png", bos);
+      return bos.toByteArray();
+    }
+    catch (IOException e) {
+      LOG.error(e);
+      return null;
+    }
+  }
+
+  private static MenuItemInternal _component2mi(Component each) {
+    if (each == null)
+      return null;
+
+    if (each instanceof ActionMenuItem) {
+      final ActionMenuItem ami = (ActionMenuItem)each;
+      return new MenuItemInternal(System.identityHashCode(ami), ami.isToggleable() ? GlobalMenuLib.ITEM_CHECK : GlobalMenuLib.ITEM_SIMPLE, ami.getText(), _icon2png(ami.getIcon()), ami.isEnabled(), ami);
+    }
+    if (each instanceof ActionMenu) {
+      final ActionMenu am2 = (ActionMenu)each;
+      return new MenuItemInternal(System.identityHashCode(am2), GlobalMenuLib.ITEM_SUBMENU, am2.getText(), null, am2.isEnabled(), am2);
+    }
+    if (each instanceof JSeparator) {
+      return new MenuItemInternal(System.identityHashCode(each), GlobalMenuLib.ITEM_SIMPLE, null, null, true, null);
+    }
+    if (each instanceof StubItem) {
+      // System.out.println("skip separator");
+    } else {
+      LOG.error("unknown type of menu-item, class: " + each.getClass());
+    }
+    return null;
+  }
+
+  @Override
+  public void handleEvent(int uid, int eventType) {
+    // glib main-loop thread
+    final MenuItemInternal mi = _findMenuItem(uid);
+    if (mi == null) {
+      LOG.error("can't find menu-item by uid " + uid + ", eventType=" + eventType);
+      return;
+    }
+    if (mi.nativePeer == null) {
+      LOG.error("menu-item hasn't native peer, uid = " + uid + ", eventType=" + eventType);
+      return;
+    }
+    if (mi.jmenuitem == null) {
+      LOG.error("menu-item hasn't associated swing peer, uid = " + uid + ", eventType=" + eventType);
+      return;
+    }
+
+    if (eventType == GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW || eventType == GlobalMenuLib.EVENT_CLOSED) {
+      if (!(mi.jmenuitem instanceof ActionMenu)) {
+        LOG.error("about-to-show is emitted for non-ActionMenu item: " + mi.jmenuitem.getClass().getName());
+        return;
+      }
+
+      final ActionMenu am = (ActionMenu)mi.jmenuitem;
+      // System.out.printf("handle event %s from %s\n", _evtype2str(eventType), mi.toString());
+
+      if (eventType == GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW) {
+        // glib main-loop thread
+        final List<MenuItemInternal> children = new ArrayList<>();
+
+        final long startMs = System.currentTimeMillis();
+        ApplicationManager.getApplication().invokeAndWait(()-> {
+          // ETD-start
+          am.removeAll();
+          am.fillMenu();
+          // System.out.println("\t size of components : " + am.getPopupMenu().getComponents().length);
+
+          // collect children
+          for (Component each : ((ActionMenu)mi.jmenuitem).getPopupMenu().getComponents()) {
+            final MenuItemInternal cmi = _component2mi(each);
+            if (cmi != null)
+              children.add(cmi);
+          }
+        });
+        final long elapsedMs = System.currentTimeMillis() - startMs;
+        if (elapsedMs > 1000)
+          LOG.info("global menu filled with " + children.size() + " components, spent " + elapsedMs + " ms");
+
+        // return to glib main-loop thread
+        ourLib.clearMenu(mi.nativePeer); // just for extra insurance
+        for (MenuItemInternal child: children) {
+          if (child.jmenuitem == null) {
+            child.nativePeer = ourLib.addSeparator(mi.nativePeer, child.uid);
+            continue;
+          }
+          child.nativePeer = ourLib.addMenuItem(mi.nativePeer, child.uid, child.txt, child.type);
+          if (!child.isEnabled)
+            ourLib.setItemEnabled(child.nativePeer, false);
+          if (child.iconPngBytes != null && child.iconPngBytes.length > 0)
+            ourLib.setItemIcon(child.nativePeer, child.iconPngBytes, child.iconPngBytes.length);
+        }
+        mi.children = children;
+      } else if (eventType == GlobalMenuLib.EVENT_CLOSED) {
+        // final long startMs = System.currentTimeMillis();
+        ApplicationManager.getApplication().invokeLater(()-> {
+          // ETD-start
+          am.clearItems();
+        });
+        // final long elapsedMs = System.currentTimeMillis() - startMs;
+        // System.out.printf("\t cleared menu '%s', spent %d ms\n", mi.txt, elapsedMs);
+
+        // return to glib main-loop thread
+        ourLib.clearMenu(mi.nativePeer);
+        ourLib.addSeparator(mi.nativePeer, Integer.MAX_VALUE); // to prevent glib-warnings (about empty submenus)
+        mi.children = null;
+      }
+
+      return;
+    }
+
+    if (eventType == GlobalMenuLib.EVENT_CLICKED) {
+      if (!(mi.jmenuitem instanceof ActionMenuItem)) {
+        LOG.error("clicked event for non-ActionMenuItem item: " + mi.jmenuitem.getClass().getName());
+        return;
+      }
+
+      final ActionMenuItem ami = (ActionMenuItem)mi.jmenuitem;
+      // System.out.printf("handle click event %s from %s\n", _evtype2str(eventType), mi.toString());
+
+      ApplicationManager.getApplication().invokeLater(()-> ami.doClick());
+    }
+  }
+
+  public static boolean isAvailable() { return ourLib != null; }
+
+  private static GlobalMenuLib _loadLibrary() {
+    if (!SystemInfo.isLinux)
+      return null;
+
+    if (!"Unity".equals(System.getenv("XDG_CURRENT_DESKTOP"))) {
+      LOG.info("skip loading of dbusmenu wrapper because not-unity desktop used");
+      return null;
+    }
+
+    UrlClassLoader.loadPlatformLibrary("dbm");
+
+    // Set JNA to convert java.lang.String to char* using UTF-8, and match that with
+    // the way we tell CF to interpret our char*
+    // May be removed if we use toStringViaUTF16
+    System.setProperty("jna.encoding", "UTF8");
+
+    final Map<String, Object> options = new HashMap<>();
+    try {
+      return Native.loadLibrary("dbm", GlobalMenuLib.class, options);
+    } catch (UnsatisfiedLinkError ule) {
+      LOG.error(ule);
+    } catch (RuntimeException e) {
+      LOG.error(e);
+    }
+    return null;
+  }
+
+  private static class MenuItemInternal {
+    final int uid;
+    final int type;
+    final String txt;
+    final byte[] iconPngBytes;
+    final JMenuItem jmenuitem;
+    final boolean isEnabled;
+    Pointer nativePeer;
+    List<MenuItemInternal> children;
+
+    MenuItemInternal(int uid, int type, String txt, byte[] iconPngBytes, boolean isEnabled, JMenuItem jmenuitem) {
+      this.uid = uid;
+      this.type = type;
+      this.txt = txt;
+      this.iconPngBytes = iconPngBytes;
+      this.isEnabled = isEnabled;
+      this.jmenuitem = jmenuitem;
+    }
+
+    @Override
+    public String toString() { return String.format("'%s' (%d)",txt, uid); }
+  }
+
+  private static long _getX11WindowXid(@NotNull JFrame frame) {
+    final ComponentPeer wndPeer = frame.getPeer();
+    if (wndPeer == null) {
+      // wait a little for X11-peer to be connected
+      LOG.info("frame peer is null, wait for connection");
+      return 0;
+    }
+
+    // sun.awt.X11.XBaseWindow isn't available at all jdks => use reflection
+    if (!wndPeer.getClass().getName().equals("sun.awt.X11.XFramePeer")) {
+      LOG.info("frame peer isn't instance of XBaseWindow, class of peer: " + wndPeer.getClass());
+      return 0;
+    }
+
+    // System.out.println("Window id (from XBaseWindow): 0x" + Long.toHexString(((XBaseWindow)frame.getPeer()).getWindow()));
+
+    Method method = null;
+    try {
+      method = wndPeer.getClass().getMethod("getWindow");
+    } catch (SecurityException e) {
+      LOG.error(e);
+    } catch (NoSuchMethodException e) {
+      LOG.error(e);
+    }
+    if (method == null)
+      return 0;
+
+    try {
+      return (long)method.invoke(wndPeer);
+    } catch (IllegalArgumentException e) {
+      LOG.error(e);
+    } catch (IllegalAccessException e) {
+      LOG.error(e);
+    } catch (InvocationTargetException e) {
+      LOG.error(e);
+    }
+    return 0;
+  }
+}
\ No newline at end of file
index d14804013e5fca617ed6c6915a4430ccc787290e..f740ea97183465d4ebdfbe9c665aefbae33e5f71 100644 (file)
@@ -143,8 +143,6 @@ public class IdeFrameImpl extends JFrame implements IdeFrameEx, AccessibleContex
 
     myFrameDecorator = IdeFrameDecorator.decorate(this);
 
-    IdeMenuBar.installAppMenuIfNeeded(this);
-
     setFocusTraversalPolicy(new LayoutFocusTraversalPolicyExt()    {
       @Override
       protected Component getDefaultComponentImpl(Container focusCycleRoot) {
index a8a0df29c590c71415e7a5de62e5b7b27b08e7a4..6d6b33c513c8d5015b278619173c3021b719d572 100644 (file)
@@ -15,6 +15,7 @@ import com.intellij.openapi.actionSystem.impl.MenuItemPresentationFactory;
 import com.intellij.openapi.actionSystem.impl.WeakTimerListener;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.registry.Registry;
@@ -28,7 +29,6 @@ import com.intellij.util.ui.Animator;
 import com.intellij.util.ui.JBUI;
 import com.intellij.util.ui.MouseEventAdapter;
 import com.intellij.util.ui.UIUtil;
-import org.java.ayatana.ApplicationMenu;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -50,6 +50,7 @@ import java.util.List;
  * @author Vladimir Kondratyev
  */
 public class IdeMenuBar extends JMenuBar implements IdeEventQueue.EventDispatcher, UISettingsListener {
+  private static final Logger LOG = Logger.getInstance(IdeMenuBar.class);
   private static final int COLLAPSED_HEIGHT = 2;
 
   private enum State {
@@ -77,6 +78,8 @@ public class IdeMenuBar extends JMenuBar implements IdeEventQueue.EventDispatche
   private double myProgress;
   private boolean myActivated;
 
+  private GlobalMenuLinux myGlobalMenuLinux;
+
   public IdeMenuBar(ActionManagerEx actionManager, DataManager dataManager) {
     myActionManager = actionManager;
     myTimerListener = new MyTimerListener();
@@ -311,7 +314,9 @@ public class IdeMenuBar extends JMenuBar implements IdeEventQueue.EventDispatche
     return component;
   }
 
-  void updateMenuActions() {
+  void updateMenuActions() { updateMenuActions(false); }
+
+  void updateMenuActions(boolean forceRebuild) {
     myNewVisibleActions.clear();
 
     if (!myDisabled) {
@@ -319,7 +324,7 @@ public class IdeMenuBar extends JMenuBar implements IdeEventQueue.EventDispatche
       expandActionGroup(dataContext, myNewVisibleActions, myActionManager);
     }
 
-    if (!myNewVisibleActions.equals(myVisibleActions)) {
+    if (forceRebuild || !myNewVisibleActions.equals(myVisibleActions)) {
       // should rebuild UI
       final boolean changeBarVisibility = myNewVisibleActions.isEmpty() || myVisibleActions.isEmpty();
 
@@ -334,6 +339,7 @@ public class IdeMenuBar extends JMenuBar implements IdeEventQueue.EventDispatche
         add(new ActionMenu(null, ActionPlaces.MAIN_MENU, (ActionGroup)action, myPresentationFactory, enableMnemonics, isDarkMenu));
       }
 
+      updateGlobalMenuRoots();
       updateMnemonicsVisibility();
       if (myClockPanel != null) {
         add(myClockPanel);
@@ -417,6 +423,17 @@ public class IdeMenuBar extends JMenuBar implements IdeEventQueue.EventDispatche
     updateMenuActions();
   }
 
+  private void updateGlobalMenuRoots() {
+    if (myGlobalMenuLinux != null) {
+      final List<ActionMenu> roots = new ArrayList<>();
+      for (Component each: getComponents()) {
+        if (each instanceof ActionMenu)
+          roots.add((ActionMenu)each);
+      }
+      myGlobalMenuLinux.setRoots(roots);
+    }
+  }
+
   private final class MyTimerListener implements TimerListener {
     @Override
     public ModalityState getModalityState() {
@@ -526,10 +543,28 @@ public class IdeMenuBar extends JMenuBar implements IdeEventQueue.EventDispatche
     }
   }
 
+  public static boolean isLinuxGlobalMenuAvailable() {
+    if (!SystemInfo.isLinux || !Registry.is("linux.native.menu"))
+      return false;
+
+    final String desktop = System.getenv("XDG_CURRENT_DESKTOP");
+    return desktop == null ? false : desktop.startsWith("Unity") || desktop.startsWith("ubuntu");
+  }
+
   public static void installAppMenuIfNeeded(@NotNull final JFrame frame) {
-    if (SystemInfo.isLinux && Registry.is("linux.native.menu") && "Unity".equals(System.getenv("XDG_CURRENT_DESKTOP"))) {
-      //noinspection SSBasedInspection
-      SwingUtilities.invokeLater(() -> ApplicationMenu.tryInstall(frame));
+    if (!isLinuxGlobalMenuAvailable())
+      return;
+
+    if (frame.getJMenuBar() instanceof IdeMenuBar) {
+      final GlobalMenuLinux gml = GlobalMenuLinux.create(frame);
+      if (gml == null)
+        return;
+
+      final IdeMenuBar frameMenuBar = (IdeMenuBar)frame.getJMenuBar();
+      frameMenuBar.myGlobalMenuLinux = gml;
+      Disposer.register(frameMenuBar.myDisposable, gml);
+      frameMenuBar.updateMenuActions(true);
+      frameMenuBar.setVisible(false);
     }
   }
 
index 7ff4624ceb25a1856139f7385f2c3aec9c5d8d34..d8902f1278d73fb11034ffcc1615bf14f8bc8ceb 100644 (file)
@@ -455,6 +455,7 @@ public final class WindowManagerImpl extends WindowManagerEx implements Persiste
     frame.setExtendedState(myDefaultFrameInfo.getExtendedState());
     frame.setVisible(true);
     addFrameStateListener(frame);
+    IdeMenuBar.installAppMenuIfNeeded(frame);
   }
 
   @Override