emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Override Windows default Win-* key combinations when using Emacs


From: Jussi Lahdenniemi
Subject: [PATCH] Override Windows default Win-* key combinations when using Emacs
Date: Tue, 5 Jan 2016 14:51:36 +0200
User-agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:38.0) Gecko/20100101 Thunderbird/38.5.0

Hello,

I finally got around to submitting a patch I wrote some four years ago that enables Emacs users on Windows to override the default Windows key combinations reserved by the operating system. Especially the newer Windowses (7, 8, 10) define quite a large number of Win+* hotkeys to a variety of shell functions, restricting the available S-* keys on Emacs.

This is accomplished by running an external process (supersuper.exe) that captures keypresses, suppressing unwanted ones from the system, and informing the currently active Emacs process about them as necessary. All Win key combinations are thus blocked and made available for Emacs, except Win+L which is handled on a lower level of the operating system and cannot be intercepted.

The feature is enabled by executing (w32-supersuper-run t). nil as the argument disables the functionality.

Being new to Emacs-devel, I am not sure this is the way to go with patch submissions, but I have attached the git format-patch output below. Note that near the end of the file the "^L" should be replaced with a real ^L before applying the patch.

Please let me know if something is amiss.


---
 nt/Makefile.in  |   5 +-
nt/supersuper.c | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/w32fns.c    |  49 ++++++++++++
 3 files changed, 289 insertions(+), 1 deletion(-)
 create mode 100644 nt/supersuper.c

diff --git a/nt/Makefile.in b/nt/Makefile.in
index fc6887f..658edb7 100644
--- a/nt/Makefile.in
+++ b/nt/Makefile.in
@@ -135,7 +135,7 @@ MKDIR_P = @MKDIR_P@
 # ========================== Lists of Files ===========================

# Things that a user might actually run, which should be installed in bindir.
-INSTALLABLES = runemacs${EXEEXT} addpm${EXEEXT}
+INSTALLABLES = runemacs${EXEEXT} addpm${EXEEXT} supersuper${EXEEXT}

# Things that Emacs runs internally, which should not be installed in bindir.
 UTILITIES = cmdproxy${EXEEXT} ddeclient${EXEEXT}
@@ -242,6 +242,9 @@ cmdproxy${EXEEXT}: ${srcdir}/cmdproxy.c
 runemacs${EXEEXT}: ${srcdir}/runemacs.c $(EMACSRES)
        $(AM_V_CCLD)$(CC) ${ALL_CFLAGS} $^ -mwindows -o $@

+supersuper${EXEEXT}: ${srcdir}/supersuper.c ../src/epaths.h
+       $(AM_V_CCLD)$(CC) ${ALL_CFLAGS} $< -o $@
+
 ## Also used in ../src/Makefile.
 emacs.res ../src/emacs.res: emacs.rc ${srcdir}/icons/emacs.ico \
   ${srcdir}/icons/hand.cur ${srcdir}/$(EMACS_MANIFEST)
diff --git a/nt/supersuper.c b/nt/supersuper.c
new file mode 100644
index 0000000..2f3fd9b
--- /dev/null
+++ b/nt/supersuper.c
@@ -0,0 +1,236 @@
+/**
+ * @file   supersuper.c
+ * @author Jussi Lahdenniemi <address@hidden>
+ * @date   2011-09-30 23:54
+ *
+ * @brief  supersuper keyboard hook
+ *
+ * Hooks the keyboard, provides supersuper services to emacs.
+ */
+
+#define STRICT
+#ifdef _WIN32_WINNT
+# undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0501
+#include <windows.h>
+#include <stdio.h>
+#include <string.h>
+
+#ifndef WM_WTSSESSION_CHANGE
+# define WM_WTSSESSION_CHANGE  0x02B1
+# define WTS_SESSION_LOCK      0x7
+#endif
+
+static HANDLE h_quit; /**< Event handle: quit the hook executable */ +static HANDLE h_lwin; /**< Event handle: left Windows key pressed */ +static HANDLE h_rwin; /**< Event handle: right Windows key pressed */
+
+static int  lwindown  =  0;     /**< Left Windows key currently pressed */
+static int  rwindown  =  0;     /**< Right Windows key currently pressed */
+static int  capsdown  =  0;     /**< Caps lock currently pressed */
+static int winsdown = 0; /**< Number of handled keys currently pressed */ +static int suppress = 0; /**< Suppress Windows keyup for this press? */ +static int winseen = 0; /**< Windows keys seen during this press? */
+
+/**
+ * Determines whether an Emacs is currently on the foreground.
+ *
+ * @return nonzero if Emacs, zero if not.
+ */
+static int emacsp()
+{
+  HWND fg = GetForegroundWindow();
+  if( fg != 0 )
+    {
+      TCHAR cls[16];
+      GetClassName( fg, cls, 16 );
+      return memcmp( cls, TEXT("Emacs"), sizeof(TEXT("Emacs"))) == 0;
+    }
+  return 0;
+}
+
+/**
+ * The keyboard hook function.
+ *
+ * @param code Negative -> call next hook
+ * @param w    Keyboard message ID
+ * @param l    KBDLLHOOKSTRUCT'
+ *
+ * @return nonzero to terminate processing
+ */
+static LRESULT CALLBACK funhook( int code, WPARAM w, LPARAM l )
+{
+  KBDLLHOOKSTRUCT const* hs = (KBDLLHOOKSTRUCT*)l;
+  if( code < 0 || (hs->flags & LLKHF_INJECTED))
+    {
+      return CallNextHookEx( 0, code, w, l );
+    }
+
+  if( hs->vkCode == VK_LWIN ||
+      hs->vkCode == VK_RWIN ||
+      hs->vkCode == VK_CAPITAL )
+    {
+      if( emacsp() && w == WM_KEYDOWN )
+        {
+          /* pressing key in emacs */
+          if( hs->vkCode == VK_LWIN && !lwindown )
+            {
+              SetEvent( h_lwin );
+              lwindown = 1;
+              winseen = 1;
+              winsdown++;
+            }
+          else if( hs->vkCode == VK_RWIN && !rwindown )
+            {
+              SetEvent( h_rwin );
+              rwindown = 1;
+              winseen = 1;
+              winsdown++;
+            }
+          else if( hs->vkCode == VK_CAPITAL && !capsdown )
+            {
+              SetEvent( h_lwin );
+              capsdown = 1;
+              winsdown++;
+            }
+          return 1;
+        }
+      else if( winsdown > 0 && w == WM_KEYUP )
+        {
+          /* releasing captured key */
+          if( hs->vkCode == VK_LWIN && lwindown )
+            {
+              lwindown = 0;
+              winsdown--;
+              if( !capsdown ) ResetEvent( h_lwin );
+            }
+          else if( hs->vkCode == VK_RWIN && rwindown )
+            {
+              rwindown = 0;
+              winsdown--;
+              ResetEvent( h_rwin );
+            }
+          else if( hs->vkCode == VK_CAPITAL && capsdown )
+            {
+              capsdown = 0;
+              winsdown--;
+              if( !lwindown ) ResetEvent( h_lwin );
+            }
+          if( winsdown == 0 && !suppress && winseen )
+            {
+              /* Releasing Win without other keys inbetween */
+              INPUT inputs[2];
+              memset( inputs, 0, sizeof(inputs));
+              inputs[0].type = INPUT_KEYBOARD;
+              inputs[0].ki.wVk = hs->vkCode;
+              inputs[0].ki.wScan = hs->vkCode;
+              inputs[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
+              inputs[0].ki.time = 0;
+              inputs[1].type = INPUT_KEYBOARD;
+              inputs[1].ki.wVk = hs->vkCode;
+              inputs[1].ki.wScan = hs->vkCode;
+ inputs[1].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
+              inputs[1].ki.time = 0;
+              SendInput( 2, inputs, sizeof(INPUT));
+            }
+          if( winsdown == 0 )
+            {
+              suppress = 0;
+              winseen = 0;
+            }
+          return 1;
+        }
+    }
+  else if( winsdown > 0 )
+    {
+      /* S-? combination detected, do not pass keypress to Windows */
+      suppress = 1;
+    }
+  return CallNextHookEx( 0, code, w, l );
+}
+
+/**
+ * Window procedure for the event listener window.
+ *
+ * @param wnd     Window handle
+ * @param msg     Message
+ * @param wparam  Parameter
+ * @param lparam  Parameter
+ *
+ * @return Message result
+ */
+static LRESULT WINAPI wndproc( HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam )
+{
+  if( msg == WM_WTSSESSION_CHANGE && wparam == WTS_SESSION_LOCK )
+    {
+      /* Clear keypress status on lock event - otherwise, when
+         the user presses Win+L to lock the workstation with
+         emacs as the foreground application, the Windows key
+         gets "stuck down" and after unlock all keys result in
+         S-* key combinations until Win is pressed and released. */
+      lwindown = 0;
+      rwindown = 0;
+      capsdown = 0;
+      winsdown = 0;
+      suppress = 0;
+      winseen = 0;
+    }
+  return DefWindowProc( wnd, msg, wparam, lparam );
+}
+
+/**
+ * Main function for the application.
+ *
+ * @param inst        Instance handle
+ * @param HINSTANCE   Not used
+ * @param LPSTR       Not used
+ * @param int         Not used
+ *
+ * @return Process exit code
+ */
+int CALLBACK WinMain( HINSTANCE inst, HINSTANCE prev, LPSTR args, int cmdshow )
+{
+  MSG msg;
+  HHOOK hook;
+  WNDCLASSEX wcex;
+  HWND wnd;
+
+  (void)prev; (void)args; (void)cmdshow;
+
+  h_quit = CreateEvent( 0, TRUE, FALSE, "supersuper.quit" );
+  if( GetLastError() == ERROR_ALREADY_EXISTS )
+    {
+      /* do not run twice */
+      CloseHandle( h_quit );
+      return 0;
+    }
+  h_lwin = CreateEvent( 0, TRUE, FALSE, "supersuper.left" );
+  h_rwin = CreateEvent( 0, TRUE, FALSE, "supersuper.right" );
+  hook = SetWindowsHookEx( WH_KEYBOARD_LL, funhook, inst, 0 );
+
+ /* Create a dummy window so that we receive WM_WTSESSION_CHANGE messages */
+  memset( &wcex, 0, sizeof(WNDCLASSEX));
+  wcex.cbSize = sizeof(wcex);
+  wcex.lpfnWndProc = wndproc;
+  wcex.hInstance = inst;
+  wcex.lpszClassName = "sswc";
+  RegisterClassEx( &wcex );
+  wnd = CreateWindow( "sswc", "", WS_POPUP, 0, 0, 0, 0, 0, 0, inst, 0 );
+
+ while( MsgWaitForMultipleObjects( 1, &h_quit, FALSE, INFINITE, QS_ALLINPUT ) != WAIT_OBJECT_0 )
+    {
+      while( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
+        {
+          TranslateMessage( &msg );
+          DispatchMessage( &msg );
+        }
+    }
+
+  UnhookWindowsHookEx( hook );
+  CloseHandle( h_lwin );
+  CloseHandle( h_rwin );
+  CloseHandle( h_quit );
+  DestroyWindow( wnd );
+  return 0;
+}
diff --git a/src/w32fns.c b/src/w32fns.c
index c1d9bff..8140f35 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -2218,6 +2218,20 @@ sync_modifiers (void)
 }

 static int
+supersuper_winkeystate (int vkey)
+{
+  static HANDLE evh_left = NULL;
+  static HANDLE evh_right = NULL;
+  HANDLE* h = (vkey == VK_RWIN) ? &evh_right : &evh_left;
+  if (*h == NULL)
+    *h = OpenEvent (SYNCHRONIZE, TRUE,
+ (vkey == VK_RWIN) ? "supersuper.right" : "supersuper.left");
+  if (*h == NULL)
+    return (GetKeyState (vkey) & 0x8000);
+  return WaitForSingleObject (*h, 0) == WAIT_OBJECT_0;
+}
+
+static int
 modifier_set (int vkey)
 {
   /* Warning: The fact that VK_NUMLOCK is not treated as the other 2
@@ -2248,6 +2262,8 @@ modifier_set (int vkey)
       else
        return (GetKeyState (vkey) & 0x1);
     }
+  if (vkey == VK_LWIN || vkey == VK_RWIN)
+    return supersuper_winkeystate(vkey);

   if (!modifiers_recorded)
     return (GetKeyState (vkey) & 0x8000);
@@ -8223,6 +8239,38 @@ The following %-sequences are provided:
   return status;
 }

+DEFUN ("w32-supersuper-run", Fw32_supersuper_run, Sw32_supersuper_run, 1, 1, 0,
+       doc: /* Control running of the supersuper keyboard hook application.
+Specify nil as RUN to terminate supersuper, non-nil to start it.
+Returns t if the operation succeeds, nil if it fails. */)
+  (run)
+  Lisp_Object run;
+{
+  if (NILP (run))
+    {
+      /* Terminate supersuper by setting the quit event */
+ HANDLE quit = OpenEvent (EVENT_MODIFY_STATE, TRUE, "supersuper.quit");
+      if (quit == NULL)
+        return Qnil;
+      SetEvent (quit);
+      CloseHandle (quit);
+      return Qt;
+    }
+  else
+    {
+      /* Start up the supersuper app */
+      STARTUPINFO sui;
+      PROCESS_INFORMATION pi;
+      sui.cb = sizeof(STARTUPINFO);
+      GetStartupInfo (&sui);
+ if (!CreateProcess (NULL, "supersuper.exe", NULL, NULL, TRUE,0, NULL, NULL, &sui, &pi))
+        return Qnil;
+      CloseHandle (pi.hProcess);
+      CloseHandle (pi.hThread);
+      return Qt;
+    }
+}
+
^L
 #ifdef WINDOWSNT
 typedef BOOL (WINAPI *GetDiskFreeSpaceExW_Proc)
@@ -9628,6 +9676,7 @@ This variable has effect only on Windows Vista and later. */);
   defsubr (&Sw32_window_exists_p);
   defsubr (&Sw32_battery_status);
   defsubr (&Sw32__menu_bar_in_use);
+  defsubr (&Sw32_supersuper_run);
 #if defined WINDOWSNT && !defined HAVE_DBUS
   defsubr (&Sw32_notification_notify);
   defsubr (&Sw32_notification_close);
--
2.6.2






reply via email to

[Prev in Thread] Current Thread [Next in Thread]