mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-21 05:51:25 +07:00
linux-menubar: implemented global menu for unity
This commit is contained in:
BIN
bin/linux/libdbm64.so
Executable file
BIN
bin/linux/libdbm64.so
Executable file
Binary file not shown.
@@ -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)
|
||||
|
||||
Binary file not shown.
59
native/LinuxGlobalMenu/CMakeLists.txt
Normal file
59
native/LinuxGlobalMenu/CMakeLists.txt
Normal file
@@ -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)
|
||||
411
native/LinuxGlobalMenu/DbusMenuWrapper.c
Normal file
411
native/LinuxGlobalMenu/DbusMenuWrapper.c
Normal file
@@ -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);
|
||||
}
|
||||
57
native/LinuxGlobalMenu/DbusMenuWrapper.h
Normal file
57
native/LinuxGlobalMenu/DbusMenuWrapper.h
Normal file
@@ -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
|
||||
55
native/LinuxGlobalMenu/test.cc
Normal file
55
native/LinuxGlobalMenu/test.cc
Normal file
@@ -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;
|
||||
}
|
||||
BIN
native/LinuxGlobalMenu/test.png
Normal file
BIN
native/LinuxGlobalMenu/test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user