Subject: Re: bug#14793: 23.4; Cannot bind a function to a sequence of two mouse keys

From: Alex <[email-masked]>
Date: Wed, 04 Oct 2017 21:58:41 -0600

Eli Zaretskii <[email-masked]> writes:

>> From: Alex <[email-masked]>
>> Cc: [email-masked],  [email-masked],  [email-masked]
>> Date: Mon, 02 Oct 2017 21:59:37 -0600
>> 
>> > I just asked that question because my reading of the code is that this
>> > is not supported.  Maybe I'm misreading: keyboard.c:read_char is not
>> > for the faint at heart.
>> 
>> Yeah, I'm having trouble going through it. Is there a way to make it
>> supported, though? For example, what about catching the error around
>> read_char_x_menu_prompt (keyboard.c:L2683) and not exiting if an error
>> was thrown?
>
> That's hardly a clean solution.  A clean solution would avoid calling
> Fx_popup_menu when it detects the situation we are talking about.

Right, I just figured it was the easiest way to check if the rest of the
code supported prefix mouse clicks, but maybe it isn't for C. In any
case, I have a possible solution below.

>> >> Also, "(emacs) Mouse Buttons" states that "You can put more than one
>> >> mouse button in a key sequence, but it isn’t usual to do so.".
>> >> 
>> >> How would one do this if a mouse click can't be a prefix key?
>> >
>> > By making a menu, I presume.
>> 
>> Do you mean those mentioned in "(emacs) Menu Mouse Clicks"? Does
>> clicking in those menus count as key sequences?
>
> Yes, of course.  You can see that with "C-h l", for example.  Also,
> every non-leaf menu item is a keymap.

Ah, I see it now. It seems that it depends on the menu (the binding for
C-down-mouse-1 isn't a keymap, but C-down-mouse-2's is, which is
important for below).

Anyway, I tried out the following diff, which is based on the condition
at xmenu.c:L1460. The problem is that it uses the global variable
`menu_items_used', which is incremented during the execution of
Fx_popup_menu (keymap_panes). So the diff uses the value of
`menu_items_used' of the previously called menu. With the diff, you can
see that pressing C-down-mouse-2 right after startup leads to an
undefined key message instead of opening a menu; after pressing
C-down-mouse-1, though, pressing C-down-mouse-2 opens its menu properly.

diff --git a/src/keyboard.c b/src/keyboard.c
index e8701b8870..5f92807087 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -2678,7 +2678,8 @@ read_char (int commandflag, Lisp_Object map,
       && !EQ (XCAR (prev_event), Qmenu_bar)
       && !EQ (XCAR (prev_event), Qtool_bar)
       /* Don't bring up a menu if we already have another event.  */
-      && !CONSP (Vunread_command_events))
+      && !CONSP (Vunread_command_events)
+      && menu_items_used > MENU_ITEMS_PANE_LENGTH)
     {
       c = read_char_x_menu_prompt (map, prev_event, used_mouse_menu);
 
-- 
2.14.2

I've attached a patch that seems to work for single clicks. I had to
move Fx_popup_menu's implementation outside of DEFUN so I could use
used_mouse_menu in it. What do you think?

>From d001740b0f59cdc2165d62e05c7f2cb5ff8ab6b4 Mon Sep 17 00:00:00 2001
From: Alexander Gramiak <[email-masked]>
Date: Tue, 3 Oct 2017 17:11:08 -0600
Subject: [PATCH] Allow mouse clicks to be prefix keys (Bug#14793)

* src/keyboard.c (read_char): Check used_mouse_menu after calling
read_char_x_menu_prompt.
(read_char_x_menu_prompt): Pass used_mouse_menu to x_popup_menu and
return nil if it wasn't set to true.
* src/menu.c (Fx_popup_menu): Extract into a new function.
(x_popup_menu): New function. Don't error if used_mouse_menu is null,
and set used_mouse_menu if there was no error.
* src/keyboard.h: Declare x_popup_menu.
---
 src/keyboard.c | 21 +++++++++++++--------
 src/keyboard.h |  3 +++
 src/menu.c     | 11 ++++++++++-
 3 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/src/keyboard.c b/src/keyboard.c
index e8701b8870..5912f89115 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -2682,11 +2682,15 @@ read_char (int commandflag, Lisp_Object map,
     {
       c = read_char_x_menu_prompt (map, prev_event, used_mouse_menu);
 
-      /* Now that we have read an event, Emacs is not idle.  */
-      if (!end_time)
-	timer_stop_idle ();
+      /* Only exit if a mouse menu was actually used successfully.  */
+      if (*used_mouse_menu)
+        {
+          /* Now that we have read an event, Emacs is not idle.  */
+          if (!end_time)
+            timer_stop_idle ();
 
-      goto exit;
+          goto exit;
+        }
     }
 
   /* Maybe autosave and/or garbage collect due to idleness.  */
@@ -8457,8 +8461,11 @@ read_char_x_menu_prompt (Lisp_Object map,
       /* Display the menu and get the selection.  */
       Lisp_Object value;
 
-      value = Fx_popup_menu (prev_event, get_keymap (map, 0, 1));
-      if (CONSP (value))
+      value = x_popup_menu (prev_event, get_keymap (map, 0, 1), used_mouse_menu);
+
+      if (!*used_mouse_menu)
+        value = Qnil;
+      else if (CONSP (value))
 	{
 	  Lisp_Object tem;
 
@@ -8489,8 +8496,6 @@ read_char_x_menu_prompt (Lisp_Object map,
 	}
       else if (NILP (value))
 	value = Qt;
-      if (used_mouse_menu)
-	*used_mouse_menu = true;
       return value;
     }
   return Qnil ;
diff --git a/src/keyboard.h b/src/keyboard.h
index a2a5f8f21d..954485c78b 100644
--- a/src/keyboard.h
+++ b/src/keyboard.h
@@ -360,6 +360,9 @@ enum menu_item_idx
 
 extern void unuse_menu_items (void);
 
+extern Lisp_Object
+x_popup_menu (Lisp_Object position, Lisp_Object menu, bool *used_mouse_menu);
+
 /* This is how to deal with multibyte text if HAVE_MULTILINGUAL_MENU
    isn't defined.  The use of HAVE_MULTILINGUAL_MENU could probably be
    confined to an extended version of this with sections of code below
diff --git a/src/menu.c b/src/menu.c
index d569b4b29b..257c67f6a4 100644
--- a/src/menu.c
+++ b/src/menu.c
@@ -1157,6 +1157,12 @@ keyboard input, then this normally results in a quit and
 event (indicating that the user invoked the menu with the mouse) then
 no quit occurs and `x-popup-menu' returns nil.  */)
   (Lisp_Object position, Lisp_Object menu)
+{
+  return x_popup_menu (position, menu, NULL);
+}
+
+Lisp_Object
+x_popup_menu (Lisp_Object position, Lisp_Object menu, bool *used_mouse_menu)
 {
   Lisp_Object keymap, tem, tem2;
   int xpos = 0, ypos = 0;
@@ -1439,7 +1445,10 @@ no quit occurs and `x-popup-menu' returns nil.  */)
     FRAME_DISPLAY_INFO (f)->grabbed = 0;
 #endif
 
-  if (error_name) error ("%s", error_name);
+  if (error_name && !used_mouse_menu)
+    error ("%s", error_name);
+  else if (!error_name && used_mouse_menu)
+    *used_mouse_menu = true;
   return selection;
 }
 
-- 
2.14.2

There are a couple issues that I can see though, regarding double/triple
clicks. First, evaluate the following:

  (define-prefix-command 'test)
  (global-set-key [mouse-3] #'test)
  (global-set-key [mouse-3 mouse-1] #'forward-word)

1. Triple clicking mouse-3 results in a popup menu that seems like it
   shouldn't appear, but this is also case when triple clicking
   C-mouse-3 even without my patch. I'm not sure what it's doing there.
   
2. Double clicking mouse-3 usually results in a mouse-3 double-mouse-3
   sequence, instead of double-mouse-3. You can tell by also evaluating:

     (global-set-key [mouse-3 double-mouse-3] #'forward-line)
     (global-set-key [double-mouse-3] #'goto-line)

   With the above key set, double clicking mouse-3 calls `forward-line',
   and oddly enough _triple_ clicking mouse-3 calls `goto-line'. Here's what
   `describe-key' has to say about triple clicking mouse-3:

     <double-mouse-3> (translated from <triple-down-mouse-3>
     <triple-mouse-3>) runs the command goto-line

  I have no idea why that translation exists.

Do you have any ideas on how to solve these issues (especially getting
double click to issue double-mouse-#)? If not, maybe I'll fiddle around
with read_key_sequence later.