Search notes:

Swap ESC and CAPS LOCK with VBA

SetWindowsHookEx/IKeyboardEvent.cls

IKeyboardEvent.cls defines an interface with one method: ev.
This method is called when a key is pressed or released.
' vi: ft=basic
'
' V.1

option explicit

public function ev ( _
             vk_keyCode as long   , _
             pressed    as boolean, _
             alt        as boolean, _
             scanCode   as long   , _
             time       as long     )

'   ev = false

end function
Github repository VBA-SetWindowsHookEx, path: /IKeyboardEvent.cls

IKeyboardEvent_SwapKeys.cls

IKeyboardEvent_SwapKeys.cls defines the implementation of IKeyboardEvent_SwapKeys.cs.
This implementation reacts to pressing and releasing of the ESC (VK_ESCAPE), CAPS LOCK (VK_CAPITAL) and Right Alt Gr (VK_RMENU) key and swaps ESC with CAPS LOCK by using the SendInput() WinAPI function.
The right Alt Gr key is replaced with the right control key.
' vi: ft=basic
'
' V.1

option explicit

implements IKeyboardEvent

dim inputs(0 to 0) as INPUT_KI


public sub init()
   inputs(0).dwType      = INPUT_KEYBOARD
   inputs(0).dwFlags     = 0
   inputs(0).dwTime      = 0
   inputs(0).dwExtraInfo = 0
end sub

public function IKeyboardEvent_ev(vk_keyCode as long, pressed as boolean, alt as boolean, scanCode as long, time as long) ' {

   debug.print "vk_keyCode = " & vk_keyCode & ", pressed = " & pressed & ", alt = " & alt & ", scanCode = " & scanCode & ", time = " & time


   if     vk_keyCode = VK_ESCAPE  then

          inputs(0).wVK = VK_CAPITAL 
          debug.print "   VK_ESCAPE -> VK_CAPITAL"

   elseif vk_keyCode = VK_CAPITAL then

          inputs(0).wVK = VK_ESCAPE 
          debug.print "   VK_CAPITAL -> VK_ESCAPE"

   elseif vk_keyCode = VK_RMENU   then

          inputs(0).wVK = VK_RCONTROL
          debug.print "   VK_RMENU -> VK_RCONTROL"

   else
           IKeyboardEvent_ev = false
          debug.print "   exit function"
           exit function
   end if


   if pressed then
      inputs(0).dwFlags = 0
   else
      inputs(0).dwFlags = KEYEVENTF_KEYUP
   end if


   SendInput 1, inputs(0), lenB(inputs(0))

   IKeyboardEvent_ev = true

end function ' }
Github repository VBA-SwapKeys, path: /IKeyboardEvent_SwapKeys.cls

SwapKeys.vb

SwapKeys.vb initializes the IKeyboardEvent_SwapKeys implementation and starts the keyboard hook:
' V.1
'
option explicit

private kbsk as IKeyboardEvent_SwapKeys

sub startSwapKeys() ' {

    set kbsk = new IKeyboardEvent_SwapKeys 
    kbsk.init

    startLowLevelKeyboardHook kbsk

end sub ' }
Github repository VBA-SwapKeys, path: /SwapKeys.vb

SetWindowsHookEx\WindowsHooks.vb

'
' V.1
'
option explicit

private keyboard_ev as IKeyboardEvent

private hookId as long

sub startLowLevelKeyboardHook(kb as IKeyboardEvent) ' {

    if hookId <> 0 then
       debug.print "Keyboard hook already started"
       exit sub
    end if

    set keyboard_ev = kb

    dim callBack as longPtr ' TODO: as long?
    callBack = getAddressOfCallback(addressOf LowLevelKeyboardProc)

    hookId = SetWindowsHookEx( _
         WH_KEYBOARD_LL                         , _
         callBack                               , _
         GetModuleHandle(vbNullString)          , _
         0 )

end sub ' }

sub endLowLevelKeyboardHook() ' {
    UnhookWindowsHookEx(hookId)
    hookId = 0
end sub ' }

function LowLevelKeyboardProc(byVal nCode as Long, byVal wParam as long, lParam as KBDLLHOOKSTRUCT) as long ' {
'
'   MSDN says (https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644985(v=vs.85))
'
'     nCode
'        A code the hook procedure uses to determine how to process the
'        message.
'
'        If nCode is less than zero, the hook procedure must pass the
'        message to the CallNextHookEx function without further processing and
'        should return the value returned by CallNextHookEx.
'
'        If nCode == HC_ACTION, the wParam and lParam parameters contain
'        information about a keyboard message.
'
'     wParam
'        One of
'          -  WM_KEYDOWN,
'          -  WM_KEYUP
'          -  WM_SYSKEYDOWN
'          -  WM_SYSKEYUP.
'
'     lParam
'          A pointer to a KBDLLHOOKSTRUCT structure.
'
'     Return value
'          If the hook procedure processed the message, it may return a nonzero
'          value to prevent the system from passing the message to the rest of
'          the hook chain or the target window procedure.
'
'     -----------------------------------------------------------------------------
'
'     The hook procedure should process a message in less time than the data entry specified in the
'             LowLevelHooksTimeout
'     value in the following registry key under
'             HKEY_CURRENT_USER\Control Panel\Desktop
'

    dim upOrDown as string
'   dim altKey   as boolean
    dim char     as string

    dim keyEventString as string


    if nCode <> HC_ACTION then
       LowLevelKeyboardProc = CallNextHookEx(0, nCode, wParam, byVal lParam)
       exit function
    end if

    if not keyboard_ev.ev(                             _
          vk_keyCode :=     lParam.vkCode            , _
          pressed    := not lParam.flags      and 128, _
          alt        :=     lParam.flags      and  32, _
          scanCode   :=     lParam.scanCode          , _
          time       :=     lParam.time              ) then

     '
     ' Event was not processed, pass it on:
     '
       LowLevelKeyboardProc = CallNextHookEx(0, nCode, wParam, byVal lParam)
       exit function
    end if ' }

    LowLevelKeyboardProc = 1
    exit function

end function ' }

function getAddressOfCallback(addr as longPtr) as longPtr ' {
      '
      '  TODO: use long instead of longPtr?
      '

      '  See also
      '  https://renenyffenegger.ch/notes/development/languages/VBA/language/operators/addressOf
      '
         getAddressOfCallback = addr
end function ' }
Github repository VBA-SetWindowsHookEx, path: /WindowsHooks.vb

SetWindowsHookEx\WindowsHooksWinAPI.vb

WindowsHooksWinAPI.vb contains the WinAPI definitions required for hooking keyboard events:
'
' V.1
'
option explicit

public const WH_KEYBOARD_LL   = 13 ' Low level keyboard events (compare with WH_KEYBOARD)
public const HC_ACTION        =  0
public const INPUT_KEYBOARD   =  1
public const KEYEVENTF_KEYUP  =  2 ' Used for dwFlags in INPUT_

type KBDLLHOOKSTRUCT ' {
     vkCode      as long ' virtual key code in range 1 .. 254
     scanCode    as long ' hardware code
     flags       as long ' bit 4: if set -> alt key was pressed
                         ' bit 7: transition state, 0 -> the key is pressed, 1 -> key is being released.
     time        as long
     dwExtraInfo as long
end  type ' }


type INPUT_KI   '   typedef struct tagINPUT ' {
'
'    Used in conjunction with SendInput()
'
     dwType      as long
     wVK         as integer
     wScan       as integer
     dwFlags     as long
     dwTime      as long
     dwExtraInfo as long
     dwPadding   as currency        '   8 extra bytes, because of mouse events
end type ' }

declare function GetModuleHandle     lib "kernel32"     alias "GetModuleHandleA"  ( _
     byVal lpModuleName as string) as long

' CallNextHookEx {
declare function CallNextHookEx      lib "user32"                                 ( _
         byVal hHook        as long, _
         byVal nCode        as long, _
         byVal wParam       as long, _
               lParam       as any ) as long
' }

declare function SetWindowsHookEx    lib "user32"       alias "SetWindowsHookExA" ( _
     byVal idHook     as long   , _
     byVal lpfn       as longPtr, _
     byVal hmod       as long   , _
     byVal dwThreadId as long   ) as long

declare function UnhookWindowsHookEx  lib "user32"                                ( _
     byVal hHook      as long) as long

declare function SendInput           lib "user32"                                 ( _
      byVal nInputs as long, _
      byRef pInputs as any , _
      byVal cbSize  as long) as long
Github repository VBA-SetWindowsHookEx, path: /WindowsHooksWinAPI.vb

SetWindowsHookEx\VirtualKeys.vb

VirtualKeys.vb contains constants for virtual keys:
option explicit

public const VK_LBUTTON              = &h001
public const VK_RBUTTON              = &h002
public const VK_CANCEL               = &h003 ' Implemented as Ctrl-Break on most keyboards
public const VK_MBUTTON              = &h004
public const VK_XBUTTON1             = &h005
public const VK_XBUTTON2             = &h006
public const VK_BACK                 = &h008
public const VK_TAB                  = &h009
public const VK_CLEAR                = &h00c
public const VK_RETURN               = &h00d ' Enter
public const VK_SHIFT                = &h010
public const VK_CONTROL              = &h011
public const VK_MENU                 = &h012
public const VK_PAUSE                = &h013
public const VK_CAPITAL              = &h014
public const VK_KANA                 = &h015
public const VK_HANGUEL              = &h015
public const VK_HANGUL               = &h015
public const VK_JUNJA                = &h017
public const VK_FINAL                = &h018
public const VK_HANJA                = &h019
public const VK_KANJI                = &h019
public const VK_ESCAPE               = &h01b
public const VK_CONVERT              = &h01c
public const VK_NONCONVERT           = &h01d
public const VK_ACCEPT               = &h01e
public const VK_MODECHANGE           = &h01f
public const VK_SPACE                = &h020
public const VK_PRIOR                = &h021
public const VK_NEXT                 = &h022
public const VK_END                  = &h023
public const VK_HOME                 = &h024
public const VK_LEFT                 = &h025
public const VK_UP                   = &h026
public const VK_RIGHT                = &h027
public const VK_DOWN                 = &h028
public const VK_SELECT               = &h029
public const VK_PRINT                = &h02a
public const VK_EXECUTE              = &h02b
public const VK_SNAPSHOT             = &h02c
public const VK_INSERT               = &h02d
public const VK_DELETE               = &h02e
public const VK_HELP                 = &h02f
public const VK_LWIN                 = &h05b
public const VK_RWIN                 = &h05c
public const VK_APPS                 = &h05d
public const VK_SLEEP                = &h05f
public const VK_NUMPAD0              = &h060
public const VK_NUMPAD1              = &h061
public const VK_NUMPAD2              = &h062
public const VK_NUMPAD3              = &h063
public const VK_NUMPAD4              = &h064
public const VK_NUMPAD5              = &h065
public const VK_NUMPAD6              = &h066
public const VK_NUMPAD7              = &h067
public const VK_NUMPAD8              = &h068
public const VK_NUMPAD9              = &h069
public const VK_MULTIPLY             = &h06a
public const VK_ADD                  = &h06b
public const VK_SEPARATOR            = &h06c
public const VK_SUBTRACT             = &h06d
public const VK_DECIMAL              = &h06e
public const VK_DIVIDE               = &h06f
public const VK_F1                   = &h070
public const VK_F2                   = &h071
public const VK_F3                   = &h072
public const VK_F4                   = &h073
public const VK_F5                   = &h074
public const VK_F6                   = &h075
public const VK_F7                   = &h076
public const VK_F8                   = &h077
public const VK_F9                   = &h078
public const VK_F10                  = &h079
public const VK_F11                  = &h07a
public const VK_F12                  = &h07b
public const VK_F13                  = &h07c
public const VK_F14                  = &h07d
public const VK_F15                  = &h07e
public const VK_F16                  = &h07f
public const VK_F17                  = &h080
public const VK_F18                  = &h081
public const VK_F19                  = &h082
public const VK_F20                  = &h083
public const VK_F21                  = &h084
public const VK_F22                  = &h085
public const VK_F23                  = &h086
public const VK_F24                  = &h087
public const VK_NUMLOCK              = &h090
public const VK_SCROLL               = &h091
public const VK_LSHIFT               = &h0a0
public const VK_RSHIFT               = &h0a1
public const VK_LCONTROL             = &h0a2
public const VK_RCONTROL             = &h0a3
public const VK_LMENU                = &h0a4 ' This is apparently the left  "Alt" key
public const VK_RMENU                = &h0a5 ' This is apparently the right "Alt" key
public const VK_BROWSER_BACK         = &h0a6
public const VK_BROWSER_FORWARD      = &h0a7
public const VK_BROWSER_REFRESH      = &h0a8
public const VK_BROWSER_STOP         = &h0a9
public const VK_BROWSER_SEARCH       = &h0aa
public const VK_BROWSER_FAVORITES    = &h0ab
public const VK_BROWSER_HOME         = &h0ac
public const VK_VOLUME_MUTE          = &h0ad
public const VK_VOLUME_DOWN          = &h0ae
public const VK_VOLUME_UP            = &h0af
public const VK_MEDIA_NEXT_TRACK     = &h0b0
public const VK_MEDIA_PREV_TRACK     = &h0b1
public const VK_MEDIA_STOP           = &h0b2
public const VK_MEDIA_PLAY_PAUSE     = &h0b3
public const VK_LAUNCH_MAIL          = &h0b4
public const VK_LAUNCH_MEDIA_SELECT  = &h0b5
public const VK_LAUNCH_APP1          = &h0b6
public const VK_LAUNCH_APP2          = &h0b7
public const VK_OEM_1                = &h0ba
public const VK_OEM_PLUS             = &h0bb
public const VK_OEM_COMMA            = &h0bc
public const VK_OEM_MINUS            = &h0bd
public const VK_OEM_PERIOD           = &h0be
public const VK_OEM_2                = &h0bf
public const VK_OEM_3                = &h0c0
public const VK_OEM_4                = &h0db
public const VK_OEM_5                = &h0dc
public const VK_OEM_6                = &h0dd
public const VK_OEM_7                = &h0de
public const VK_OEM_8                = &h0df
public const VK_OEM_102              = &h0e2
public const VK_PROCESSKEY           = &h0e5
public const VK_PACKET               = &h0e7
public const VK_ATTN                 = &h0f6
public const VK_EXSEL                = &h0f8
public const VK_PLAY                 = &h0fa
public const VK_NONAME               = &h0fc
Github repository VBA-SetWindowsHookEx, path: /VirtualKeys.vb

createWordDocument.wsf

createWordDocument.wsf uses VBScript MS-Office App Creator to create a Word document, which contains the swap key functionality, from the command line.
<job>
<script language="VBScript" src="VBS-MS-Office-App-Creator/create-MS-Office-app.vbs" />
<script language="VBScript">
'
'  V.1
'
   option explicit

   dim app
   dim doc

   set doc = createOfficeApp("word", currentDir() & "swapKeys.docm")
   if doc is nothing then ' {
      wscript.echo("Could not create word document")
      wscript.quit(-1)
   end if ' }

   set app = doc.application

   insertModule app, currentDir() & "SetWindowsHookEx/VirtualKeys.vb"         , "VirtualKeys"            , 1
   insertModule app, currentDir() & "SetWindowsHookEx/WindowsHooksWinAPI.vb"  , "WindowsHooksWinAPI"     , 1
   insertModule app, currentDir() & "SetWindowsHookEx/IKeyboardEvent.cls"     , "IKeyboardEvent"         , 2
   insertModule app, currentDir() & "SetWindowsHookEx/WindowsHooks.vb"        , "WindowsHooks"           , 1

   insertModule app, currentDir() & "IKeyboardEvent_SwapKeys.cls"             , "IKeyboardEvent_SwapKeys", 2
   insertModule app, currentDir() & "SwapKeys.vb"                             , "SwapKeys"               , 1

   doc.save

   if not compileApp(app) then ' {
      wscript.echo "Compilation error"
      wscript.quit(-1)
   end if ' }

   app.run "startSwapKeys"

</script>
</job>
Github repository VBA-SwapKeys, path: /createWordDocument.wsf
This source code is invoked like so:
cscript createWordDocument.wsf

See also

A written in similar program (written in C Sharp) and swap_keys.c (written in written in C).
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout

Index