linux-menu: fixed mnemonics processing under KDE
authorArtem Bochkarev <artem.bochkarev@jetbrains.com>
Wed, 30 Jan 2019 11:23:04 +0000 (14:23 +0300)
committerArtem Bochkarev <artem.bochkarev@jetbrains.com>
Fri, 1 Feb 2019 10:32:58 +0000 (13:32 +0300)
process key-events (for mnemonic shortcuts) within ide

bin/linux/libdbm64.so
native/LinuxGlobalMenu/DbusMenuWrapper.c
native/LinuxGlobalMenu/DbusMenuWrapper.h
platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java

index 8d086450278368ad09fdb5ca370fcc074756dfb0..d8fa66c6b661c2492cac81af1bb5b1a3e2ee20ab 100755 (executable)
Binary files a/bin/linux/libdbm64.so and b/bin/linux/libdbm64.so differ
index 42e0b70773fc8505f0bf57d5f3883af05f284abf..2d35071a0ae0dca66b251f34fd930a54556356d8 100644 (file)
@@ -65,7 +65,7 @@ static void _printWndInfo(const WndInfo *wi, char *out, int outLen) {
     return;
   }
 
-  snprintf(out, outLen, "xid=0x%X menuPath='%s' registrar=0x%p server=0x%p menuroot=0x%p", wi->xid, wi->menuPath,
+  snprintf(out, (size_t)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);
 }
 
@@ -163,7 +163,7 @@ static void _releaseMenuItem(gpointer data) {
   }
 }
 
-static void _unregisterWindow(long xid, GDBusProxy * registrar) {
+static void _unregisterWindow(guint32 xid, GDBusProxy * registrar) {
   // NOTE: sync call g_dbus_proxy_call_sync(wi->registrar, "UnregisterWindow", g_variant_new("(u)", wi->xid), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error)
   // under ubuntu18 (with GlobalMenu plugin) executes several minutes.
   // We make async call and don't care about results and errors (i.e. NULL callbacks)
@@ -200,7 +200,7 @@ static void _releaseWindow(WndInfo *wi) {
     _unregisterWindow(wi->xid, wi->registrar);
     if (wi->linkedXids != NULL) {
       for (GList* l = wi->linkedXids; l != NULL; l = l->next)
-        _unregisterWindow(l->data, wi->registrar);
+        _unregisterWindow((guint32)l->data, wi->registrar);
     }
 
     g_object_unref(wi->registrar);
@@ -240,7 +240,7 @@ void createMenuRootForWnd(WndInfo *wi) {
         dbusmenu_server_set_root(wi->server, wi->menuroot);
 }
 
-WndInfo *registerWindow(long windowXid, jeventcallback handler) {
+WndInfo *registerWindow(guint32 windowXid, jeventcallback handler) {
   // _info("register new window");
 
   WndInfo *wi = (WndInfo *) malloc(sizeof(WndInfo));
@@ -248,7 +248,7 @@ WndInfo *registerWindow(long windowXid, jeventcallback handler) {
 
   wi->xid = (guint32) windowXid;
   wi->menuPath = malloc(64);
-  sprintf(wi->menuPath, "/com/canonical/menu/0x%lx", windowXid);
+  sprintf(wi->menuPath, "/com/canonical/menu/0x%x", windowXid);
 
   wi->menuroot = dbusmenu_menuitem_new();
   if (wi->menuroot == NULL) {
@@ -305,7 +305,7 @@ WndInfo *registerWindow(long windowXid, jeventcallback handler) {
   return wi;
 }
 
-void bindNewWindow(WndInfo * wi, long windowXid) {
+void bindNewWindow(WndInfo * wi, guint32 windowXid) {
   if (wi == NULL || wi->server == NULL || wi->menuPath == NULL)
     return;
 
@@ -326,10 +326,10 @@ void bindNewWindow(WndInfo * wi, long windowXid) {
   }
 
   // _logmsg(LOG_LEVEL_INFO, "bind new window 0x%lx", windowXid);
-  wi->linkedXids = g_list_append(wi->linkedXids, windowXid);
+  wi->linkedXids = g_list_append(wi->linkedXids, (gpointer)windowXid);
 }
 
-void unbindWindow(WndInfo * wi, long windowXid) {
+void unbindWindow(WndInfo * wi, guint32 windowXid) {
   if (wi == NULL || wi->server == NULL || wi->menuPath == NULL)
     return;
 
@@ -337,7 +337,7 @@ void unbindWindow(WndInfo * wi, long windowXid) {
   _unregisterWindow(windowXid, wi->registrar);
 
   if (wi->linkedXids != NULL)
-    wi->linkedXids = g_list_remove(wi->linkedXids, windowXid);
+    wi->linkedXids = g_list_remove(wi->linkedXids, (gpointer)windowXid);
 }
 
 static gboolean _execReleaseWindow(gpointer user_data) {
@@ -390,8 +390,9 @@ static const char *_type2str(int type) {
       return "sig-shown";
     case SIGNAL_CHILD_ADDED:
       return "sig-child-added";
+    default:
+        return "unknown event type";
   }
-  return "unknown event type";
 }
 
 static void _handleItemSignal(DbusmenuMenuitem *item, int type) {
@@ -479,7 +480,7 @@ DbusmenuMenuitem *addMenuItem(DbusmenuMenuitem *parent, int uid, const char * la
     if (position < 0)
       dbusmenu_menuitem_child_append(parent, item);
     else
-      dbusmenu_menuitem_child_add_position(parent, item, position);
+      dbusmenu_menuitem_child_add_position(parent, item, (guint)position);
   }
 
   return item;
@@ -494,16 +495,23 @@ DbusmenuMenuitem* addSeparator(DbusmenuMenuitem * parent, int uid, int position)
     if (position < 0)
       dbusmenu_menuitem_child_append(parent, item);
     else
-      dbusmenu_menuitem_child_add_position(parent, item, position);
+      dbusmenu_menuitem_child_add_position(parent, item, (guint)position);
   }
 
   return item;
 }
 
-void reorderMenuItem(DbusmenuMenuitem * parent, DbusmenuMenuitem* item, int position) { dbusmenu_menuitem_child_reorder(parent, item, position); }
+void reorderMenuItem(DbusmenuMenuitem * parent, DbusmenuMenuitem* item, int position) { dbusmenu_menuitem_child_reorder(parent, item, (guint)position); }
 
 void removeMenuItem(DbusmenuMenuitem * parent, DbusmenuMenuitem* item) { dbusmenu_menuitem_child_delete(parent, item); }
 
+static gboolean _showMenuItem(gpointer item) {
+    dbusmenu_menuitem_show_to_user(item, 0);
+    return FALSE;
+}
+
+void showMenuItem(DbusmenuMenuitem* item) { g_idle_add(_showMenuItem, item); }
+
 void setItemLabel(DbusmenuMenuitem *item, const char *label) {
   dbusmenu_menuitem_property_set(item, DBUSMENU_MENUITEM_PROP_LABEL, label);
 }
@@ -513,14 +521,8 @@ void setItemEnabled(DbusmenuMenuitem *item, bool 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);
+  dbusmenu_menuitem_property_set_byte_array(item, DBUSMENU_MENUITEM_PROP_ICON_DATA, (guchar*)iconBytesPng, (gsize)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));
 }
 
 // java modifiers
@@ -530,7 +532,7 @@ static const int META_MASK           = 1 << 2;
 static const int ALT_MASK            = 1 << 3;
 
 void setItemShortcut(DbusmenuMenuitem *item, int jmodifiers, int x11keycode) {
-  char* xname = XKeysymToString(x11keycode);
+  char* xname = XKeysymToString((KeySym)x11keycode);
   if (xname == NULL) {
     // _logmsg(LOG_LEVEL_ERROR, "XKeysymToString returns null for x11keycode=%d", x11keycode);
     return;
index 42264b053749724e5f70d6dbc42e82ae32effe58..e10dc7add3cb38c58ed6737a7abdfaf8c61c9081 100644 (file)
@@ -39,11 +39,11 @@ void runMainLoop(jlogger jlogger, jrunnable onAppmenuServiceAppeared, jrunnable
 
 void execOnMainLoop(jrunnable run);
 
-WndInfo* registerWindow(long windowXid, jeventcallback handler); // creates menu-server and binds to xid
+WndInfo* registerWindow(guint32 windowXid, jeventcallback handler); // creates menu-server and binds to xid
 void releaseWindowOnMainLoop(WndInfo* wi, jrunnable onReleased);
 
-void bindNewWindow(WndInfo * wi, long windowXid);
-void unbindWindow(WndInfo * wi, long windowXid);
+void bindNewWindow(WndInfo * wi, guint32 windowXid);
+void unbindWindow(WndInfo * wi, guint32 windowXid);
 
 void createMenuRootForWnd(WndInfo *wi);
 void clearRootMenu(WndInfo* wi);
@@ -55,6 +55,7 @@ DbusmenuMenuitem* addSeparator(DbusmenuMenuitem * parent, int uid, int position)
 
 void reorderMenuItem(DbusmenuMenuitem * parent, DbusmenuMenuitem* item, int position);
 void removeMenuItem(DbusmenuMenuitem * parent, DbusmenuMenuitem* item);
+void showMenuItem(DbusmenuMenuitem* item);
 
 void setItemLabel(DbusmenuMenuitem* item, const char * label);
 void setItemEnabled(DbusmenuMenuitem* item, bool isEnabled);
index 0b85dd9d5ce0b58e292b735c1f7be80c928c3fb7..f5ebb5b28d6d696d85c842d4eee0dce4e9ca5637 100644 (file)
@@ -5,6 +5,7 @@ import com.intellij.execution.ExecutionException;
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.execution.process.ProcessOutput;
 import com.intellij.execution.util.ExecUtil;
+import com.intellij.ide.IdeEventQueue;
 import com.intellij.ide.plugins.PluginManager;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.actionSystem.AnAction;
@@ -30,6 +31,7 @@ import javax.imageio.ImageIO;
 import javax.swing.Timer;
 import javax.swing.*;
 import java.awt.*;
+import java.awt.event.KeyEvent;
 import java.awt.image.BufferedImage;
 import java.awt.peer.ComponentPeer;
 import java.io.ByteArrayOutputStream;
@@ -63,6 +65,7 @@ interface GlobalMenuLib extends Library {
 
   void reorderMenuItem(Pointer parent, Pointer item, int position);
   void removeMenuItem(Pointer parent, Pointer item);
+  void showMenuItem(Pointer item);
 
   void setItemLabel(Pointer item, String label);
   void setItemEnabled(Pointer item, boolean isEnabled);
@@ -208,6 +211,37 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
       if (myIsDisposed)
         ourInstances.remove(myXid);
     };
+
+    if (SystemInfo.isKDE) {
+      // root menu items doesn't catch mnemonic shortcuts (in KDE), so process them inside IDE
+      IdeEventQueue.getInstance().addDispatcher(e -> {
+        if (!(e instanceof KeyEvent))
+          return false;
+
+        final KeyEvent event = (KeyEvent)e;
+        if (!event.isAltDown())
+          return false;
+
+        final Component src = event.getComponent();
+        final Window wndParent = src instanceof Window ? (Window)src : SwingUtilities.windowForComponent(src);
+        final char eventChar = Character.toUpperCase(event.getKeyChar());
+
+        for (GlobalMenuLinux gml: ourInstances.values()) {
+          if (gml.myFrame == wndParent) {
+            for (MenuItemInternal root : gml.myRoots) {
+              if (eventChar == root.mnemonic) {
+                ourLib.showMenuItem(root.nativePeer);
+                return false;
+              }
+            }
+            return false;
+          }
+        }
+
+        return false;
+      }, this);
+    }
+
     ourInstances.put(myXid, this);
   }
 
@@ -652,6 +686,7 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
 
     String txt;
     String originTxt;
+    char mnemonic;
     boolean isEnabled = true;
     boolean isChecked = false;
     byte[] iconPngBytes;
@@ -714,7 +749,19 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
     void setLabelFromSwingPeer(@NotNull JMenuItem peer) {
       // exec at EDT
       originTxt = peer.getText();
-      txt = _buildMnemonicLabel(peer);
+      txt = originTxt != null ? originTxt : "";
+      mnemonic = 0;
+
+      if (originTxt != null && !originTxt.isEmpty()) {
+        final int mnemonicCode = peer.getMnemonic();
+        final int mnemonicIndex = peer.getDisplayedMnemonicIndex();
+        if (mnemonicIndex >= 0 && mnemonicIndex < originTxt.length() && Character.toUpperCase(originTxt.charAt(mnemonicIndex)) == mnemonicCode) {
+          final StringBuilder res = new StringBuilder(originTxt);
+          res.insert(mnemonicIndex, '_');
+          txt = res.toString();
+          mnemonic = (char)mnemonicCode;
+        }
+      }
     }
 
     void updateNative() {
@@ -900,27 +947,6 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
     }
   }
 
-  private static String _buildMnemonicLabel(JMenuItem jmenuitem) {
-    String text = jmenuitem.getText();
-    final int mnemonicCode = jmenuitem.getMnemonic();
-    final int mnemonicIndex = jmenuitem.getDisplayedMnemonicIndex();
-    if (text == null)
-      text = "";
-    final int index;
-    if (mnemonicIndex >= 0 && mnemonicIndex < text.length() && Character.toUpperCase(text.charAt(mnemonicIndex)) == mnemonicCode) {
-      index = mnemonicIndex;
-    } else {
-      // Mnemonic mismatch index
-      index = -1;
-      // LOG.error("Mnemonic code " + mnemonicCode + " mismatch index " + mnemonicIndex + " with txt: " + text);
-    }
-
-    final StringBuilder res = new StringBuilder(text);
-    if(index != -1)
-      res.insert(index, '_');
-    return res.toString();
-  }
-
   private static Object _getPeerField(@NotNull Component object) {
     try {
       Field field = Component.class.getDeclaredField("peer");