Subject: Re: bug#68914: Windows makes Emacs choke on and swallow the WIN keys

From: Raffael Stocker <[email-masked]>
Date: Mon, 12 Feb 2024 21:13:27 +0100

I have added debug output to the keyboard hook (see the attached patch)
and was able to observe the bug while Emacs was unresponsive (either
because the current master is iffy on Windows or because of my
output...).  The locked windows key problem seems to appear when an
s-<something> combination is pressed.  Normal debug output looks like
this:

--8<---------------cut here---------------start------------->8---
KEYDOWN 0x5b, 0x5b: 0.0018 ms

Simulated S-x combination: 0.647 ms

KEYUP received, winsdown: 1, w: 0x101
no key pressed anymore, clear flags
KEYUP processed normally: 4.57 ms
--8<---------------cut here---------------end--------------->8---

Emacs first registers the windows key to be pressed in a WM_KEYDOWN
event, then upon the second call of ‘funhook’ sees the other key in the
combination and sends a WIN+<x> input to the system and then in the
third call receives the WM_KEYUP event, cleans up its state and calls
‘CallNextHookEx’ to let other applications in the hook chain process the
combination normally.  The times are the execution times of the hook.

With the bug present, I get the following output:

--8<---------------cut here---------------start------------->8---
KEYDOWN 0x5b, 0x5b: 0.0005 ms

Simulated S-x combination: 1.08 ms

0 < winsdown = 1: 0.0015 ms

0 < winsdown = 1: 0.0015 ms

0 < winsdown = 1: 0.0016 ms
--8<---------------cut here---------------end--------------->8---

The WM_KEYUP event is missing here; instead, if I press any key, Emacs
ignores it and calls ‘CallNextHookEx’ normally; the above output shows
three such key presses.  If I press ‘e’ now, Windows Explorer will
open.  That is, Emacs doesn't seem to receive the WM_KEYUP event, but
the system doesn't seem to see it either (unless my understanding of the
situation is completely wrong).

Note that the times shown above are very short; I have seen up to 15 ms,
but nothing longer.  Emacs was unresponsive for a few seconds while the
behaviour occurred; but if the hook was removed by Windows, this was not
permanent, as the remaining output shows.  Also, pressing a windows key
seems to cure the problem in this case.

I will continue to observe this and try to find out more, but any
insights are welcome.

Regards,
Raffael

diff --git a/src/w32fns.c b/src/w32fns.c
index 1d5a591466c..b2572463e04 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -20,6 +20,7 @@ Copyright (C) 1989, 1992-2024 Free Software Foundation, Inc.
 /* Added by Kevin Gallo */
 
 #include <config.h>
+#include <time.h>
 /* Override API version to get the latest functionality.  */
 #undef _WIN32_WINNT
 #define _WIN32_WINNT 0x0600
@@ -2556,6 +2557,8 @@ funhook (int code, WPARAM w, LPARAM l)
   int console = 0;
   KBDLLHOOKSTRUCT const *hs = (KBDLLHOOKSTRUCT*)l;
 
+  struct timespec begin;
+  clock_gettime (CLOCK_MONOTONIC, &begin);
   if (code < 0 || (hs->flags & LLKHF_INJECTED))
     return CallNextHookEx (0, code, w, l);
 
@@ -2595,6 +2598,11 @@ funhook (int code, WPARAM w, LPARAM l)
 	      kbdhook.winseen = 1;
 	      kbdhook.winsdown++;
 	    }
+	  struct timespec end;
+	  clock_gettime (CLOCK_MONOTONIC, &end);
+	  fprintf (stderr, "KEYDOWN 0x%lx, 0x%lx: %.3g ms\n\n", hs->vkCode, hs->scanCode,
+		   (double)(end.tv_nsec - begin.tv_nsec)/1000000);
+
 	  /* Returning 1 here drops the keypress without further processing.
 	     If the keypress was allowed to go through, the normal Windows
 	     hotkeys would take over.  */
@@ -2602,6 +2610,8 @@ funhook (int code, WPARAM w, LPARAM l)
 	}
       else if (kbdhook.winsdown > 0 && (w == WM_KEYUP || w == WM_SYSKEYUP))
 	{
+	  fprintf (stderr, "KEYUP received, winsdown: %d, w: 0x%llx\n", kbdhook.winsdown, w);
+
 	  /* A key that has been captured earlier is being released now.  */
 	  if (hs->vkCode == VK_LWIN && kbdhook.lwindown)
 	    {
@@ -2640,29 +2650,43 @@ funhook (int code, WPARAM w, LPARAM l)
 			= KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
 		      inputs[1].ki.time = 0;
 		      SendInput (2, inputs, sizeof (INPUT));
+		      fprintf (stderr, "Simulated KEYUP to system\n");
 		    }
 		  else if (focus != NULL)
 		    {
 		      /* When not passed to system, must simulate privately to Emacs.	 */
 		      PostMessage (focus, WM_SYSKEYDOWN, hs->vkCode, 0);
 		      PostMessage (focus, WM_SYSKEYUP, hs->vkCode, 0);
+		      fprintf (stderr, "passing to system, simulating privately\n");
 		    }
+		  else
+		    fprintf (stderr, "winsdown == 0, but nothing to do\n");
 		}
 	    }
 	  if (kbdhook.winsdown == 0)
 	    {
+	      fprintf (stderr, "no key pressed anymore, clear flags\n");
 	      /* No Windows keys pressed anymore - clear the state flags.  */
 	      kbdhook.suppress_lone = 0;
 	      kbdhook.winseen = 0;
 	    }
 	  if (!kbdhook.send_win_up)
 	    {
+	      struct timespec end;
+	      clock_gettime (CLOCK_MONOTONIC, &end);
+	      fprintf (stderr, "swallowing KEYUP: %.3g ms\n\n",
+		       (double)(end.tv_nsec - begin.tv_nsec)/1000000);
+
 	      /* Swallow this release message, as not to confuse
 		 applications who did not get to see the original
 		 WM_KEYDOWN message either.  */
 	      return 1;
 	    }
 	  kbdhook.send_win_up = 0;
+	  struct timespec end;
+	  clock_gettime (CLOCK_MONOTONIC, &end);
+	  fprintf (stderr, "KEYUP processed normally: %.3g ms\n\n",
+		   (double)(end.tv_nsec - begin.tv_nsec)/1000000);
 	}
     }
   else if (kbdhook.winsdown > 0)
@@ -2698,10 +2722,19 @@ funhook (int code, WPARAM w, LPARAM l)
 	     channel when the keys are released.  */
 	  kbdhook.suppress_lone = 1;
 	  kbdhook.send_win_up = 1;
+	  struct timespec end;
+	  clock_gettime (CLOCK_MONOTONIC, &end);
+	  fprintf (stderr, "Simulated S-x combination: %.3g ms\n\n",
+		   (double)(end.tv_nsec - begin.tv_nsec)/1000000);
 	  /* Swallow the original keypress (as we want the Win key
 	     down message simulated above to precede this real message).  */
 	  return 1;
 	}
+      struct timespec end;
+      clock_gettime (CLOCK_MONOTONIC, &end);
+      fprintf (stderr, "0 < winsdown = %d: %.3g ms\n\n",
+	       kbdhook.winsdown,
+	       (double)(end.tv_nsec - begin.tv_nsec)/1000000);
     }
 
   /* Next, handle the registered Alt-* combinations.  */
@@ -2905,6 +2938,7 @@ reset_w32_kbdhook_state (void)
   kbdhook.send_win_up = 0;
   kbdhook.suppress_lone = 0;
   kbdhook.winseen = 0;
+  fprintf (stderr, "Resetting state\n");
 }
 
 /* GetKeyState and MapVirtualKey on Windows 95 do not actually distinguish
@@ -3526,6 +3560,7 @@ send_deferred_msg (deferred_msg * msg_buf,
 		   WPARAM wParam,
 		   LPARAM lParam)
 {
+  fprintf(stderr, "Sending deferred message\n");
   /* Only input thread can send deferred messages.  */
   if (GetCurrentThreadId () != dwWindowsThreadId)
     emacs_abort ();
@@ -4343,6 +4378,7 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 	case VK_LWIN:
 	  if (!w32_kbdhook_active && NILP (Vw32_pass_lwindow_to_system))
 	    {
+	      fprintf (stderr, "kbdhook not active, processing VK_LWIN\n");
 	      /* Prevent system from acting on keyup (which opens the
 		 Start menu if no other key was pressed) by simulating a
 		 press of Space which we will ignore.  */
@@ -4362,6 +4398,7 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 	case VK_RWIN:
 	  if (!w32_kbdhook_active && NILP (Vw32_pass_rwindow_to_system))
 	    {
+	      fprintf (stderr, "kbdhook not active, processing VK_RWIN\n");
 	      if (GetAsyncKeyState (wParam) & 1)
 		{
 		  if (FIXNUMP (Vw32_phantom_key_code))