Search notes:

Windows: swap keys (like ESC and Caps Lock) with a C-Sharp program

SwapKeys.cs is a small C# keyboard remaping program program that swaps the Caps Locks and the ESC keys.
This program is dependent on Win32Hook.cs (which is found in this Github repository).
The program can be compiled and run with load-swapKeys.ps1.
On a computer, I ran into a problem in which the program would crash once in a while. Therefore, I also wrote startAgain.bat, a simple cmd.exe batch file, which continuously restarts the program.
The sources of SwapKeys.cs, load-SwapKeys.ps1 and startAgain.bat are found in this Github repository.
Similar programs are

SwapKeys.cs

// vi: foldmarker={{{,}}} foldmethod=marker
//
// V0.3
//
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;            // Use for Application.Run

using TQ84;

namespace TQ84 {

   public class SwapKeys {

     [DllImport("USER32.dll")]
      static extern short GetKeyState(int nVirtKey);

      private static Win32.INPUT[] inputs;
      private static int sizeOfInputs_1;

      public static void go() {

         inputs = new Win32.INPUT[1];

         inputs[0].type             = 1; // 1 = INPUT_KEYBOARD;
         inputs[0].U.ki.wScan       = 0;
         inputs[0].U.ki.dwFlags     =           0;
         inputs[0].U.ki.time        = (int)     0;
         inputs[0].U.ki.dwExtraInfo = (UIntPtr) 0;

         sizeOfInputs_1 = System.Runtime.InteropServices.Marshal.SizeOf(inputs[0]);

         if (GetKeyState((int) Win32.VirtualKey.CAPITAL) == 1) {
          //
          // V0.3: Caps Lock is pressed. Because this is probably not desired when
          // the application starts, we de-press Caps Lock here.
          // TODO: Should GetAsyncKeyState() be used?
          //
             inputs[0].U.ki.dwFlags = 0; // Win32.KEYEVENTF.KEYDOWN;
             replaceVKWith(Win32.VirtualKey.CAPITAL);

             inputs[0].U.ki.dwFlags = Win32.KEYEVENTF.KEYUP;
             replaceVKWith(Win32.VirtualKey.CAPITAL);
         }

         TQ84.Win32.Hook.KeyboardLL(keyboardEvent);
         Application.Run();
      }

      private static void replaceVKWith(Win32.VirtualKey vk) {
         inputs[0].U.ki.wVk = vk;
//       if (Win32.Hook.SendInput(1, inputs, System.Runtime.InteropServices.Marshal.SizeOf(inputs[0])) != 1)
         if (Win32.Hook.SendInput(1, inputs, sizeOfInputs_1) != 1)
         {
             Console.WriteLine("SendInput() did not return 1");
         }
      }

      public static bool keyboardEvent(IntPtr wParam, Win32.Hook.KBDLLHOOKSTRUCT kbd) {

          if (wParam == (IntPtr) 0x0101 /* WM_KEYUP */ || wParam == (IntPtr) 0x0105 /* WM_SYSKEYUP*/ ) {
              inputs[0].U.ki.dwFlags = Win32.KEYEVENTF.KEYUP;
          }
          else {
              inputs[0].U.ki.dwFlags = 0;
          }

          if (  ( kbd.flags & Win32.Hook.KBDLLHOOKSTRUCTFlags.LLKHF_INJECTED ) == 0 ) {

               if (kbd.vkCode == (uint) Win32.VirtualKey.CAPITAL) {
                   replaceVKWith(Win32.VirtualKey.ESCAPE);
                   return true;
               }

               if (kbd.vkCode == (uint) Win32.VirtualKey.ESCAPE) {
                   replaceVKWith(Win32.VirtualKey.CAPITAL);
                   return true;
               }

               if (kbd.vkCode == (uint) Win32.VirtualKey.RWIN) {
                   replaceVKWith(Win32.VirtualKey.LCONTROL);
                   Console.WriteLine("RWIN");
                   return true;
               }
           //
           //  V0.2: Removing this line seemed to generally improve typing speed and reduce Null pointer exception errors.
           //
           //  Console.WriteLine("scan: " + kbd.scanCode + ", vk = " + kbd.vkCode + ", wParam = " + wParam);
           //
               if (kbd.vkCode == (uint) Win32.VirtualKey.SNAPSHOT) { // Prt Scr
                  Win32.Hook.Stop();
                  Application.ExitThread();

                //  Never reached:
                    return true;
              }
           }

           return false;
      }
   }
}
Github repository cs-SwapKeys, path: /SwapKeys.cs

load-SwapKeys.ps1

load-SwapKeys.ps1 expects that the dependent Win32Hook.dll assembly was already created with create-assembly.ps1 (Event hooking with C#).
$assemblyPath = "$pwd/Win32-Hook/Win32Hook.dll"
[System.Reflection.Assembly]::LoadFile($assemblyPath);

$srcSwapKeys = get-content -raw swapKeys.cs

add-type                             `
  -typeDefinition       $srcSwapKeys `
  -referencedAssemblies              `
     System.Windows.Forms,           `
     $assemblyPath

[TQ84.SwapKeys]::go()
Github repository cs-SwapKeys, path: /load-SwapKeys.ps1

startAgain.bat

@cd %~dp0

:loop
   echo %time% >> started.at
   powershell -c . .\load-SwapKeys.ps1
goto loop
Github repository cs-SwapKeys, path: /startAgain.bat

install-autorun.ps1

The following PowerShell script adds the startAgain.bat batch file to the HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run registry key so that the keys are automatically remapped when the user logs on.
set-itemProperty                                      `
  HKCU:\Software\Microsoft\Windows\CurrentVersion\Run `
  swapKeys                                            `
 "$pwd\startAgain.bat"
Github repository cs-SwapKeys, path: /install-autorun.ps1

History

V0.2 Remove (comment) unnecessary (and even slowing down) Console.WriteLine that printed scan codes.
V0.3 Check if Caps Lock is pressed when hook is started (and if so, de-press it).

See also

Configuring/modifying the keyboard layout
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout

Index