linux-menubar: implemented global menu for unity

This commit is contained in:
Artem Bochkarev
2018-09-17 14:46:08 +07:00
parent 2c4510ec55
commit 4f839ae920
15 changed files with 1064 additions and 27 deletions

BIN
bin/linux/libdbm64.so Executable file

Binary file not shown.

View 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)

Binary file not shown.

View 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)

View 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);
}

View 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

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View 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",

View 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;

View 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()));

View File

@@ -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;
}
}

View 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) {

View 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);
}
}

View 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