all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Raffael Stocker <r.stocker@mnet-mail.de>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 69083@debbugs.gnu.org
Subject: bug#69083: Emacs's keyboard hook state is not reset on session lock (Windows)
Date: Sun, 03 Mar 2024 17:43:56 +0100	[thread overview]
Message-ID: <yplmcysbz2nn.fsf@mnet-mail.de> (raw)
In-Reply-To: <86bk7ysbbz.fsf@gnu.org> (Eli Zaretskii's message of "Fri, 01 Mar 2024 08:41:36 +0200")

[-- Attachment #1: Type: text/plain, Size: 1111 bytes --]

Eli Zaretskii <eliz@gnu.org> writes:

>> I'll think about this a bit more, maybe I can come up with a nice
>> solution that doesn't require keeping too much state just for the reset
>> function.  I am thinking about registering the notification for the
>> first created frame, and when that is deleted, i.e. receives the
>> ‘WM_DESTROY’ message, "handing" it over to some other frame if there is
>> one.
>
> This sounds like the simplest approach to me.

I have implemented this and attached the patch.  I have tested it with
both console and GUI Emacs (with and without server) and have found no
further problems with it.

>> I don't know much about the internals of Emacs frame handling.
>> Could ‘w32_frame_list_z_order’ be used (from the input thread) for
>> something like that?  Or is there a better approach?
>
> Why is the order important?  Can't you "hand" the registration to some
> other frame, no matter which one?

The order is not important, I just didn't know where to look to get
a frame; sorry for the noise.  I now use ‘Fnext_frame’.

Regards,
Raffael


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Fix resetting keyboard hook state --]
[-- Type: text/x-patch, Size: 10101 bytes --]

From fb9b8daf18a3dc96879d58d9c4e1e3dcfcf8e2e7 Mon Sep 17 00:00:00 2001
From: Raffael Stocker <r.stocker@mnet-mail.de>
Date: Sun, 3 Mar 2024 17:31:10 +0100
Subject: [PATCH] Fix resetting keyboard hook state on Windows

Register session notifications so Emacs is notified when the
computer is being locked, as required to reset the low level
keyboard hook state (Bug#69083).

* src/w32term.h:
* src/w32fns.c (setup_w32_kbdhook, remove_w32_kbdhook)
(w32_wnd_proc, globals_of_w32fns, maybe_pass_notification):
Register and manage session notifications in GUI Emacs.
* src/w32console.c (initialize_w32_display, find_ime_window):
* src/w32xfns.c (drain_message_queue):
Register notifications and reset keyboard hook state in console
Emacs.
* src/w32.c (term_ntproc): Un-register session notifications
when terminating.
---
 src/w32.c        |  5 ++++
 src/w32console.c | 25 +++++++++++++++--
 src/w32fns.c     | 73 +++++++++++++++++++++++++++++++++++++++++++++---
 src/w32term.h    |  3 +-
 src/w32xfns.c    | 12 ++++++--
 5 files changed, 109 insertions(+), 9 deletions(-)

diff --git a/src/w32.c b/src/w32.c
index df5465c2135..d34ab70f82d 100644
--- a/src/w32.c
+++ b/src/w32.c
@@ -10392,11 +10392,16 @@ check_windows_init_file (void)
     }
 }
 
+/* from w32fns.c */
+extern void remove_w32_kbdhook (void);
+
 void
 term_ntproc (int ignored)
 {
   (void)ignored;
 
+  remove_w32_kbdhook ();
+
   term_timers ();
 
   /* shutdown the socket interface if necessary */
diff --git a/src/w32console.c b/src/w32console.c
index 0936b5f37e6..7dcbc795cac 100644
--- a/src/w32console.c
+++ b/src/w32console.c
@@ -659,6 +659,24 @@ w32_face_attributes (struct frame *f, int face_id)
   return char_attr;
 }
 
+/* The IME window is needed to receive the session notifications
+   required to reset the low level keyboard hook state.  */
+
+static BOOL CALLBACK
+find_ime_window (HWND hwnd, LPARAM arg)
+{
+  char window_class[32];
+
+  GetClassName (hwnd, window_class, sizeof (window_class));
+  if (strcmp (window_class, "IME") == 0)
+    {
+      *(HWND *) arg = hwnd;
+      return FALSE;
+    }
+  /* keep looking */
+  return TRUE;
+}
+
 void
 initialize_w32_display (struct terminal *term, int *width, int *height)
 {
@@ -818,11 +836,14 @@ initialize_w32_display (struct terminal *term, int *width, int *height)
   else
     w32_console_unicode_input = 0;
 
-  /* Setup w32_display_info structure for this frame. */
+  /* Setup w32_display_info structure for this frame.  */
   w32_initialize_display_info (build_string ("Console"));
 
+  HWND hwnd = NULL;
+  EnumThreadWindows (GetCurrentThreadId (), find_ime_window, (LPARAM) &hwnd);
+
   /* Set up the keyboard hook.  */
-  setup_w32_kbdhook ();
+  setup_w32_kbdhook (hwnd);
 }
 
 
diff --git a/src/w32fns.c b/src/w32fns.c
index 8d4bd00b91c..32cc466d9eb 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -49,6 +49,7 @@ #define _WIN32_WINNT 0x0600
 #ifdef WINDOWSNT
 #include <mbstring.h>
 #include <mbctype.h>	/* for _getmbcp */
+#include <wtsapi32.h>	/* for WTS(Un)RegisterSessionNotification */
 #endif /* WINDOWSNT */
 
 #if CYGWIN
@@ -204,6 +205,10 @@ DECLARE_HANDLE(HMONITOR);
 typedef HRESULT (WINAPI * DwmSetWindowAttribute_Proc)
   (HWND hwnd, DWORD dwAttribute, IN LPCVOID pvAttribute, DWORD cbAttribute);
 
+typedef BOOL (WINAPI * WTSRegisterSessionNotification_Proc)
+  (HWND hwnd, DWORD dwFlags);
+typedef BOOL (WINAPI * WTSUnRegisterSessionNotification_Proc) (HWND hwnd);
+
 TrackMouseEvent_Proc track_mouse_event_fn = NULL;
 ImmGetCompositionString_Proc get_composition_string_fn = NULL;
 ImmGetContext_Proc get_ime_context_fn = NULL;
@@ -220,6 +225,8 @@ DECLARE_HANDLE(HMONITOR);
 SetThreadDescription_Proc set_thread_description = NULL;
 SetWindowTheme_Proc SetWindowTheme_fn = NULL;
 DwmSetWindowAttribute_Proc DwmSetWindowAttribute_fn = NULL;
+WTSUnRegisterSessionNotification_Proc WTSUnRegisterSessionNotification_fn = NULL;
+WTSRegisterSessionNotification_Proc WTSRegisterSessionNotification_fn = NULL;
 
 extern AppendMenuW_Proc unicode_append_menu;
 
@@ -307,6 +314,7 @@ #define WS_EX_NOACTIVATE 0x08000000L
   int hook_count; /* counter, if several windows are created */
   HHOOK hook;     /* hook handle */
   HWND console;   /* console window handle */
+  HWND notified_wnd; /* window that receives session notifications */
 
   int lwindown;      /* Left Windows key currently pressed (and hooked) */
   int rwindown;      /* Right Windows key currently pressed (and hooked) */
@@ -2744,7 +2752,7 @@ funhook (int code, WPARAM w, LPARAM l)
 /* Set up the hook; can be called several times, with matching
    remove_w32_kbdhook calls.  */
 void
-setup_w32_kbdhook (void)
+setup_w32_kbdhook (HWND hwnd)
 {
   kbdhook.hook_count++;
 
@@ -2800,6 +2808,15 @@ setup_w32_kbdhook (void)
       /* Set the hook.  */
       kbdhook.hook = SetWindowsHookEx (WH_KEYBOARD_LL, funhook,
 				       GetModuleHandle (NULL), 0);
+
+      /* Register session notifications so we get notified about the
+	 computer being locked.  */
+      kbdhook.notified_wnd = NULL;
+      if (hwnd != NULL && WTSRegisterSessionNotification_fn != NULL)
+	{
+	  WTSRegisterSessionNotification_fn (hwnd, NOTIFY_FOR_THIS_SESSION);
+	  kbdhook.notified_wnd = hwnd;
+	}
     }
 }
 
@@ -2811,7 +2828,11 @@ remove_w32_kbdhook (void)
   if (kbdhook.hook_count == 0 && w32_kbdhook_active)
     {
       UnhookWindowsHookEx (kbdhook.hook);
+      if (kbdhook.notified_wnd != NULL
+	  && WTSUnRegisterSessionNotification_fn != NULL)
+	  WTSUnRegisterSessionNotification_fn (kbdhook.notified_wnd);
       kbdhook.hook = NULL;
+      kbdhook.notified_wnd = NULL;
     }
 }
 #endif	/* WINDOWSNT */
@@ -2884,13 +2905,12 @@ check_w32_winkey_state (int vkey)
     }
   return 0;
 }
-#endif	/* WINDOWSNT */
 
 /* Reset the keyboard hook state.  Locking the workstation with Win-L
    leaves the Win key(s) "down" from the hook's point of view - the
    keyup event is never seen.  Thus, this function must be called when
    the system is locked.  */
-static void
+void
 reset_w32_kbdhook_state (void)
 {
   kbdhook.lwindown = 0;
@@ -2900,6 +2920,7 @@ reset_w32_kbdhook_state (void)
   kbdhook.suppress_lone = 0;
   kbdhook.winseen = 0;
 }
+#endif	/* WINDOWSNT */
 
 /* GetKeyState and MapVirtualKey on Windows 95 do not actually distinguish
    between left and right keys as advertised.  We test for this
@@ -4129,6 +4150,36 @@ #define S_TYPES_TO_REPORT_CHARACTER_PAYLOAD_WITH_MODIFIERS ""
   return 0;
 }
 
+/* Maybe pass session notification registration to another frame.  If
+   the frame with window handle HWND is deleted, we must pass the
+   notifications to some other frame, if they have been sent to this
+   frame before and have not already been passed on.  If there is no
+   other frame, do nothing.  */
+
+#ifdef WINDOWSNT
+static void
+maybe_pass_notification (HWND hwnd)
+{
+  if (hwnd == kbdhook.notified_wnd
+      && kbdhook.hook_count > 0 && w32_kbdhook_active)
+    {
+      Lisp_Object frame = Fnext_frame (Qnil, Qnil);
+      struct frame *f = XFRAME (frame);
+
+      if (FRAME_OUTPUT_DATA (f) != NULL
+	  && WTSUnRegisterSessionNotification_fn != NULL
+	  && WTSRegisterSessionNotification_fn != NULL)
+	{
+	  /* There is another frame, pass on the session notification.  */
+	  HWND next_wnd = FRAME_W32_WINDOW (f);
+	  WTSUnRegisterSessionNotification_fn (hwnd);
+	  WTSRegisterSessionNotification_fn (next_wnd, NOTIFY_FOR_THIS_SESSION);
+	  kbdhook.notified_wnd = next_wnd;
+	}
+    }
+}
+#endif	/* WINDOWSNT */
+
 /* Main window procedure */
 
 static LRESULT CALLBACK
@@ -5301,23 +5352,29 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 
 #ifdef WINDOWSNT
     case WM_CREATE:
-      setup_w32_kbdhook ();
+      setup_w32_kbdhook (hwnd);
       goto dflt;
 #endif
 
     case WM_DESTROY:
 #ifdef WINDOWSNT
+      maybe_pass_notification (hwnd);
       remove_w32_kbdhook ();
 #endif
       CoUninitialize ();
       return 0;
 
+#ifdef WINDOWSNT
     case WM_WTSSESSION_CHANGE:
       if (wParam == WTS_SESSION_LOCK)
         reset_w32_kbdhook_state ();
       goto dflt;
+#endif
 
     case WM_CLOSE:
+#ifdef WINDOWSNT
+      maybe_pass_notification (hwnd);
+#endif
       wmsg.dwModifiers = w32_get_modifiers ();
       my_post_msg (&wmsg, hwnd, msg, wParam, lParam);
       return 0;
@@ -11335,6 +11392,14 @@ globals_of_w32fns (void)
   set_thread_description = (SetThreadDescription_Proc)
     get_proc_addr (hm_kernel32, "SetThreadDescription");
 
+#ifdef WINDOWSNT
+  HMODULE wtsapi32_lib = LoadLibrary ("wtsapi32.dll");
+  WTSRegisterSessionNotification_fn = (WTSRegisterSessionNotification_Proc)
+    get_proc_addr (wtsapi32_lib, "WTSRegisterSessionNotification");
+  WTSUnRegisterSessionNotification_fn = (WTSUnRegisterSessionNotification_Proc)
+    get_proc_addr (wtsapi32_lib, "WTSUnRegisterSessionNotification");
+#endif
+
   /* Support OS dark mode on Windows 10 version 1809 and higher.
      See `w32_applytheme' which uses appropriate APIs per version of Windows.
      For future wretches who may need to understand Windows build numbers:
diff --git a/src/w32term.h b/src/w32term.h
index 29ace0b2797..3120c8bd71f 100644
--- a/src/w32term.h
+++ b/src/w32term.h
@@ -779,8 +779,9 @@ #define FILE_NOTIFICATIONS_SIZE 16384
 
 #ifdef WINDOWSNT
 /* Keyboard hooks.  */
-extern void setup_w32_kbdhook (void);
+extern void setup_w32_kbdhook (HWND);
 extern void remove_w32_kbdhook (void);
+extern void reset_w32_kbdhook_state (void);
 extern int check_w32_winkey_state (int);
 #define w32_kbdhook_active (os_subtype != OS_SUBTYPE_9X)
 #else
diff --git a/src/w32xfns.c b/src/w32xfns.c
index fa7d5fbdb61..3d7a1514f72 100644
--- a/src/w32xfns.c
+++ b/src/w32xfns.c
@@ -413,8 +413,16 @@ drain_message_queue (void)
 
   while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
     {
-      if (msg.message == WM_EMACS_FILENOTIFY)
-	retval = 1;
+      switch (msg.message)
+	{
+	case WM_WTSSESSION_CHANGE:
+	  if (msg.wParam == WTS_SESSION_LOCK)
+	    reset_w32_kbdhook_state ();
+	  break;
+	case WM_EMACS_FILENOTIFY:
+	  retval = 1;
+	  break;
+	}
       TranslateMessage (&msg);
       DispatchMessage (&msg);
     }
-- 
2.44.0


  reply	other threads:[~2024-03-03 16:43 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-02-12 19:04 bug#69083: Emacs's keyboard hook state is not reset on session lock (Windows) Raffael Stocker
2024-02-12 20:23 ` Eli Zaretskii
2024-02-14 20:20   ` Raffael Stocker
2024-02-15  6:36     ` Eli Zaretskii
2024-02-20 18:51       ` Raffael Stocker
2024-02-21 12:37         ` Eli Zaretskii
2024-02-21 14:13           ` Raffael Stocker
2024-02-21 15:03             ` Eli Zaretskii
2024-02-26 20:50           ` Raffael Stocker
2024-02-27  7:42             ` Eli Zaretskii
2024-02-29 20:22               ` Raffael Stocker
2024-03-01  6:41                 ` Eli Zaretskii
2024-03-03 16:43                   ` Raffael Stocker [this message]
2024-03-03 17:23                     ` Eli Zaretskii
2024-03-03 17:39                       ` Eli Zaretskii
2024-03-04 18:10                       ` Raffael Stocker
2024-03-14  8:25                         ` Eli Zaretskii

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=yplmcysbz2nn.fsf@mnet-mail.de \
    --to=r.stocker@mnet-mail.de \
    --cc=69083@debbugs.gnu.org \
    --cc=eliz@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.