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
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 ' }
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 ' }
'
' 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 ' }
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
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
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>