HID macros: Software for sending keyboard macros activated from different USB devices

Overview

From version 2.0 HIDmacros can execute scripts as macro actions. Any scripting language installed to windows active scripting can be used. Windows contain by default VBscript and JScript.

Application possibilities are extended with scripting support. Inside the script several special commands provided by HIDmacros application can be used. Putting all together typical tasks handled by scripts are

Architecture

Every macro can contain script source which is executed when macro is activated. Apart of these script codes there is one global section used for global definitions and initialization code. This section is accessible on Script page.

Routines tab sheet

This is good place do define routines (procedures and functions) that will be used from macro scripts. Also any variable defined here is globally available in macro scripts. Global variables are usable for example for keyboard profiles. You can have global variable called e.g. PROFILE and based on its value (assigned by some macro = key on keyboard) other macros with script code can behave differently. This is used in scripting example described below.

There are compile buttons available to compile routines code or macro code. There is also Compile all button that compiles routines code and all scripted macros. Macro script code must be compiled. Otherwise it is not executed when macro is activated. There is also red/green indicator next to script window showing whether current code is compiled or not. Global indicator is located in application status bar. If it says "Scripts compiled" it means routines code (the global code) and all scripted code in macros are compiled.
Technical note: As all the code is scripted the process behind is not real compilation. When you hit Compile button HIDmacros just take the code and load it to scripting engine. Only syntax check is done at that time. But note that initialization code in routines source which is not inside procedure or function is executed during compilation (load to scripting engine).

Keystroke sending

There are 2 possibilities how to send key sequence from script.

  1. HIDMacros command SendKeys: procedure has one string parameter with keys sequence. Format is exactly the same as used in macro definition.
  2. using scripting language possibilities. Some script languages have wrappers for Windows API calls - this is what HIDMacros do internally.

Following example shows both methods. The example is in VBscript.

'HIDmacros command
HIDMacros.SendKeys "+a"

'Windows way
Dim wsh
Set wsh = CreateObject("Wscript.Shell")
wsh.AppActivate "Notepad"
SendKeys "Hello"

On second example you can see how to activate some window using standard VB script command. When you work with Wscript.Shell object often in your macros, it's better to dim & create this variable in routines section.

The AppActivate method takes as parameter window title or its handle. If you're not sure what is window title, check titles of all currently visible windows on Script tools tab.

Mouse events

There is a command to send mouse click:

HIDMacros.MouseClick x, y, action

Parameters are:

xx coordinate
yy coordinate
actionnumber saying what should be done at coordinates
  • 0 - just move mouse pointer
  • 1 - left click
  • 2 - middle click
  • 3 - right click
  • 4 - left double click
  • 5 - middle double click
  • 6 - right double click

All clicks returns mouse pointer to current position. It means mouse pointer moves to new position, performs operation (click or double click) and returns where it was before macro execution. For action 0 (mouse move) this return is of course not done.

A few words about delays. I've got many requests to include delays into keyboard sequence in HIDmacros. With scripting it's quite easy so there is a command HIDMacros.Sleep which takes number of miliseconds as parameter. However it's not that easy in reality. If you try to use delay together with key operations, you realize that Active scripting somehow delays keyboard messages and delay between keystrokes doesn't work. Whole execution gets delayed and keystrokes are sent at once. Mouse operations seem to work fine with delays so you can send e.g. 2 mouse clicks with delay between - giving some time to paint a menu for example. But there is one more problem. With delays and generally with scripting (loops) it can happen that macro execution takes quite a long time and HIDmacros gets blocked. My original plan was move script execution to new thread and keep HIDmacros to process other keyboard triggers. But it doesn't seem to be that easy to move all the DOM stuff around scripting to special thread, so this is my next plan and investigation. Until I have macro execution in threads, it's always your risk & responsibility to create quick macros. During macro execution another macro is not activated

So finally example of Start - Run activation at screen 1280x1024 with delay.

HIDMacros.MouseClick 49, 980, 1
HIDMacros.Sleep 600
HIDMacros.MouseClick 90, 875, 1

Windows Scripting Host

WSH can be used in macro script. Global object WScript is not available, but you can create WshShell and WshNetwork objects via CreateObject command.

In that case it is better to create such object in routines script

'Routines
Dim wsh
Set wsh = CreateObject("Wscript.Shell")

and in macro only call methods of this object

wsh.AppActivate "Notepad"

FSX events

FSX events can be sent also from script using command:

HIDMacros.FSXEvent event_name, parameter

Parameters are:

event_namename of the event as listed in documentation
parametersome events can have parameter and its value is provided here. Use 0 when there's no parameter for your event.

You can also send some text to FSX screen by command:

HIDMacros.FSXtext "Text to be sent"

Example for both commands:

HIDMacros.FSXtext "Setting NAV1 stand by frequency to 112.6"
HIDMacros.FSXEvent "NAV1_STBY_SET", &H11260

FSX variables

You can also read and write FSX variables through commands using SimConnect interface. However there are some limitations. Currently HIDmacros work only with float and sting values.

Reading FSX variable through SimConnect is not simple, because the interface is message driven. So you first ask SimConnect to send you values of some variable and then you receive messages when value changes. It means you must first tell HIDmacros that you will need some variable and only then you can read its value. Writing is easier, because here we don't need to wait for message with result.

So here are the commands for FSX variables management.

HIDMacros.RegisterFSXVariable var_name, var_units
HIDMacros.GetFSXVariable var_name
HIDMacros.SetFSXVariable var_name, var_units, var_value

Parameters are:

var_namevariable name as listed in documentation
var_unitsvariable units as listed in documentation. Use "STRING" to read string variable.
var_valuevalue in units to be set

Only some variables are writable. When possible it's better to use FSX event to control simulator. Variables should be registered only once, so routines section is good place. Do not register too many variables. Once variable is registered, FSX will send a message to HIDmacros every second when value changes, even if you don't read the variable. If you try to read variable that was not registered, you'll get 0.

Example, in routines:

'Routines
HIDMacros.RegisterFSXVariable "PLANE ALTITUDE", "FEET"
HIDMacros.RegisterFSXVariable "ATC MODEL", "STRING"

and in macro

' read
Dim MyVar
MyVar = HIDMacros.GetFSXVariable("INDICATED ALTITUDE")
if HIDMacros.GetFSXVariable("ATC MODEL") = "DHC-6" then
  HIDMacros.FSXtext(MyVar)
end if

' write
HIDMacros.SetFSXVariable "GENERAL ENG THROTTLE LEVER POSITION:1", "percent", 100

FSUIPC interface

With Peter Dowson's FSUIPC you can access simulator variables in similar way as with SimConnect for FSX. I would say Peter's interface is more "technical", but works with other simulators like FS2004 or FS2002. For all details about supported simulators, variables mapping etc please refer to Peter's web page and mainly FSUIPC SDK package.

HIDmacros brings several functions to read and write variables through FSUIPC. To use those functions FSUIPC of course must be installed.

HIDMacros.GetFSUIPCInt offset, size
HIDMacros.GetFSUIPCFloat offset, size
HIDMacros.GetFSUIPCString offset, size
HIDMacros.GetFSUIPCRaw offset, size
HIDMacros.SetFSUIPCInt offset, size, value
HIDMacros.SetFSUIPCFloat offset, size, value 
HIDMacros.SetFSUIPCString offset, size, value 

Parameters are:

offsetvariable's memory offset from FSUIPC SDK.
sizesize of variable in bytes, also mentioned in SDK
valuevalue to be set

Size parameter tells how many bytes will be read or written. For int functions (GetFSUIPCInt and SetFSUIPCInt) this can be between 1 and 8. For float functions only size 4 and 8 bytes is supported (float or double float). Function GetFSUIPCRaw read part of memory and return string representing memory dump. It means every byte is represented by 2 characters in hex format, for example byte 44 would be returned as 2C. By writing a string to offset 0x0D70 you can execute FSUIPC macro or LUA script (in registered version).

Example

' read
Dim isPaused
Set isPaused = HIDMacros.GetFSUIPCInt(&H262, 2)
Dim rawClock
Set rawClock = HIDMacros.GetFSUIPCRaw(&H238, 3)

' write
HIDMacros.SetFSUIPCInt &H262, 2, 1 'pause sim
HIDMacros.SetFSUIPCInt &H330, 2, 16096 'alt set 1006 mBars

' write string
HIDMacros.SetFSUIPCString &H3380, 128, "Hello world"
HIDMacros.SetFSUIPCInt &H32FA, 2, 5

Input buffer

Sometimes you need enter some value for your script. You may wish to enter how many times macro action will be repeated. Or you may wish to enter numeric value and then use it to set heading in flight simulator. Or tune some radio.

There's special buffer built in HIDMacros. It can be set by script or directly from keyboard. Its value is accessible via script commands.

To set buffer content via keyboard you have to define new macro for each key and as action choose "Send to buffer".

Send to buffer

So if you define such macro for example for numpad "3" and then hit this key, value "3" will be added to the buffer. In settings tabs you can define if buffer content is shown or not. If content is shown the buffer value will appear in application title in task bar. If application is minimized to tray content is shown as baloon hint.

In settings you can also choose if buffer content should be cleared after some timeout. In this case you press keys and add values in the buffer. Once you stop adding content in the buffer, its value is cleared after defined period of time. The value is entered in miliseconds and 0 means buffer is never cleared automatically. In this case you must clear the buffer using script command.

Buffer settings

There are several commands available for buffer operations:

HIDMacros.GetBuffer 'gets buffer content as string
HIDMacros.SetBuffer value 'sets whole buffer to value
HIDMacros.AddToBuffer value 'adds value to the end of buffer
HIDMacros.ClearBuffer 'clears buffer
HIDMacros.StrRPad value, append, count

StrRPad is string padding function that adds append char to value string to have count characters. If provided string is longer than count, it's truncated from left.

Example

' suppose we have numpad numbers defined as "send to buffer"

' set heading
HIDMacros.FSXEvent "HEADING_BUG_SET", HIDMacros.GetBuffer

' set NAV1 stand by frequency
Dim frq
frq = HIDMacros.GetBuffer
frq = HIDMacros.StrRPad(frq, "0", 5)
HIDMacros.FSXEvent "NAV1_STBY_SET", "&h"&frq

Game devices

Game devices are supported because their buttons can be used as toggle switches (with permanent on or off position). To use this funcionality in scripts there is special method:

HIDMacros.IsButtonPressed DeviceName, ButtonNumber

Parameters are:

DeviceNameGame device name from Devices tab
ButtonNumberButton number, starting from 1

Example

' set NAV1 or NAV2 radio based on toggle switch
if HIDMacros.IsButtonPressed("Saitek X52 Flight Controller", 13) then
  HIDMacros.FSXEvent "NAV1_STBY_SET", &h11450
else
  HIDMacros.FSXEvent "NAV2_STBY_SET", &h11450
end if

Game device axis

There is special function that returns position of game device axis (e.g. joystick position).

HIDMacros.GetAxis DeviceName, AxisName

Parameters are:

DeviceNameGame device name from Devices tab
AxisNameName of the axis (e.g. X, Y...)

Of course you have a question: What are the names of axis that my joystick supports? The answer is easy: check yourself. Switch to Script support tab and Game axis sub-tab and see all possible axis names that HIDMacros recognize and moving you joystick you can see what is the axis name and what are possible values.

Axis inquiry

Want more? It's nice to read axis value in the script when button or key is pressed. But what if you need to control something directly with axis. What if axis movement could call a script...

Sub AxisXChange(dev, axis, val)
  Dim Message
  Message = "Axis " & axis & " of device " & dev & " moved, value is " & val
end sub

HIDMacros.RegisterAxisEvent "Saitek X52 Flight Controller", "X", "AxisXChange", 5

With RegisterAxisEvent you can tell HIDMacros to call some procedure present in routines section every time when axis of your game device changes by some delta value. Parameters are:

DeviceNameGame device name from Devices tab
AxisNameName of the axis (e.g. X, Y...)
ProcedureNameName of procedure present in your routines section. This procedure is called with 3 arguments: device name, axis name and axis value. For 1:1 registration parameters can be redundant, but you can register more axis to call one procedure and then it's quite usefull to see what axis has changed.
DeltaAfter what change of axis value the procedure is called. Choose bigger number for better performance (script is not called so often) and lower number for precise control.

With this feature you can control more things with one axis, you can use keys to change axis meaning, you can set up your own null zones for axis etc. There's also similar function to unregister axis callback:

HIDMacros.RegisterAxisEvent DeviceName, AxisName

For most axis values provided by DirectX are between 0 and 65535. Usually you need to recalculate this value to your own range. There are 3 supportive functions:

HIDMacros.Axis2Int AxisValue, LowValue, HighValue
HIDMacros.Axis2Float AxisValue, LowValue, HighValue
HIDMacros.AxisRemap AxisValue, RangeLow, RangeHigh, LowValue, HighValue, DefaultValue

Function Axis2Int takes AxisValue between 0 and 65535 and returns appropriate integer value from interval between LowValue and HighValue. It means LowValue is returned for axis value = 0 (and close values) and HighValue for axis value 65535 (and close values). If LowValue is bigger than HighValue axis seems to be reversed. For axis value 0 you again get LowValue but it's bigger then HighValue this time. So rising the axis value you'll be getting lower numbers down to highValue. Axis2Float works in the same way but with float (fraction) values = no rounding to integer.

Function AxisRemap is used for mapping part of device axis to some range of values. It has following parameters

AxisValueAxis value between 0 and 65535
RangeLowBottom range of axis that should be mapped. Float fraction value between 0 and 1 (e.g. 50% of axis range is 0.5).
RangeHighTop range of axis that should be mapped. Float fraction value between 0 and 1 (e.g. 50% of axis range is 0.5).
LowValueResult float value that will be returned for AxisValue at RangeLow
HighValueResult float value that will be returned for AxisValue at RangeHigh
DefaultValueResult float value that will be returned when AxisValue is out of input range

Example 1: Following example should make this complicated description more clear. Imagine we have throttle axis that should be mapped to forward throttle and reverse with some null zone between. Forward value (sent to simulator) is between 0 and 100, reverse value is between 0 and 60. And to make it more complex (however based on my real Saitek's throttle lever) let's have game axis returning 0 for fully extended lever (100% throttle) and 65535 for no throttle. If we want to map reverse between 0 and 20% then have 20-30% null zone and 30-100% throttle then AxisRemap should be called with those parameters.

Axis remap example

Example 2: use joystick to emulate keyboard. Store letter to buffer and emulate key press on button.

' rotuines section
Sub Axis2Char(dev, axis, val)
  Dim CharCode
  CharCode = HIDMacros.Axis2Int(val, 122, 97)
  if (HIDMacros.GetAxis("Saitek X52 Flight Controller", "X") > 50000) then
    CharCode = CharCode - 32 ' capital letters
  end if
  HIDMacros.SetBuffer Chr(CharCode)
end sub

HIDMacros.RegisterAxisEvent "Saitek X52 Flight Controller", "Y", "Axis2Char", 5

' macro on joystick button
if (HIDMacros.GetBuffer > "") then
  HIDMacros.SendKeys HIDMacros.GetBuffer
end if

Example 3: map reverse to throttle axis in FSX. Don't forget to unassign axis in FSX, throttle is controlled by script only. AxisRemap is not used here to demonstarte how to calculate values in script.

' rotuines section
  Dim ZeroT
  Dim AxisPercent
  Dim FinalThrottle
  ZeroT = 0.2 ' bellow 20% apply reverse 
  AxisPercent = val / 65535
  if (AxisPercent < ZeroT) then
    ' set reverse, which is negative throttle for FSX
    FinalThrottle = (AxisPercent-ZeroT)*100 / ZeroT
  else
    ' set throttle
    FinalThrottle = (AxisPercent - ZeroT)*100 / (1-ZeroT)
  end if
  HIDMacros.SetFSXVariable "GENERAL ENG THROTTLE LEVER POSITION:1","Percent", FinalThrottle
  HIDMacros.SetFSXVariable "GENERAL ENG THROTTLE LEVER POSITION:2","Percent", FinalThrottle

X-plane interface

X-plane variables can be read and set by script commands. List of accessible variables can be found in X-plane SDK.

From version 2.1 it is also possible to execute X-plane commands. Those commands are the same as in controls assignment in XPL. XPL commands can be executed via script command or directly in macro definition.

Important: For X-plane access special plugin must be added to X-plane which HIDmacros uses to exchange data. This plugin is file HidMacrosXpl.xpl located in xpl subdirectory of HIDmacros. You have to manually copy this file to X-plane Resources\plugins subdirectory.

Following methods are used for X-plane access:

HIDMacros.GetXplVariable pName 'returns variable content
HIDMacros.SetXplVariable pName, pValue 'sets variable content
HIDMacros.GetXplArrayItem pName, pIndex  'returns array item
HIDMacros.SetXplArrayItem pName, pIndex, pValue  'sets array item
HIDMacros.XPLCommand pCommand  'executes command pCommand
HIDMacros.XPLCommandBegin pCommand  'starts command pCommand
HIDMacros.XPLCommandEnd pCommand  'ends command pCommand

For array operations indexes are zero based. To find out array size, provide any negative number as pIndex. Strings are also supported in GetXplVariable and SetXplVariable. However X-plane offers only a few string variables.

Example

' get tail number (string)
var = HIDmacros.GetXplVariable("sim/aircraft/view/acf_tailnum")

' get flaps position (float)
var = HIDmacros.GetXplVariable("sim/flightmodel/controls/wing1l_fla1def")

' get current heading (float)
var = HIDmacros.GetXplVariable("sim/cockpit/gyros/psi_vac_ind_degm")

' get NAV1 active frequency (integer)
var = HIDmacros.GetXplVariable("sim/cockpit/radios/nav1_freq_hz")

' array example
' array size
var = HIDmacros.GetXplArrayItem("sim/cockpit/engine/fuel_pump_on", -1)
' fuel pump no 1 position (0/1)
var = HIDmacros.GetXplArrayItem("sim/cockpit/engine/fuel_pump_on", 0)
' fuel pump no 2 position (0/1)
var = HIDmacros.GetXplArrayItem("sim/cockpit/engine/fuel_pump_on", 1)

' set NAV1 frequency to 111.30
HIDmacros.SetXplVariable "sim/cockpit/radios/nav1_freq_hz", 11130

' set autopilot's heading bug to 190
HIDmacros.SetXplVariable "sim/cockpit2/autopilot/heading_dial_deg_mag_pilot", 190

' turn on both fuel pumps
HIDmacros.SetXplArrayItem "sim/cockpit/engine/fuel_pump_on", 0, 1
HIDmacros.SetXplArrayItem "sim/cockpit/engine/fuel_pump_on", 1, 1

'toggle landing gear
HIDMacros.XPLCommand "sim/flight_controls/landing_gear_toggle"
'engage starters
HIDMacros.XPLCommandBegin "sim/engines/engage_starters"
'release starters
HIDMacros.XPLCommandEnd "sim/engines/engage_starters"

Complex example

With my keyboard I used numeric pad to quickly set autopilot heading or VOR1 course bug to specific value.

Numpad for course selection

Buttons HDG and CRS at the top switch between heading and course bug, button Curr HDG sets value to current heading.

Source code below was copied from routines and macros for each key

.
' Routines:
Dim Mode
Mode = "HDG"

HIDMacros.RegisterFSXVariable "PLANE HEADING DEGREES MAGNETIC", "degrees"
HIDMacros.RegisterFSXVariable "AUTOPILOT HEADING LOCK DIR", "degrees"
HIDMacros.RegisterFSXVariable "NAV OBS:1", "degrees"

Sub SetHdgCrs(value)
  if Mode = "HDG" then
    HIDMacros.FSXEvent "HEADING_BUG_SET", value
  end if
  if Mode = "CRS" then
    HIDMacros.FSXEvent "VOR1_SET", value
  end if
end sub

function CurHdgCrsValue()
  CurHdgCrsValue = 0
  if Mode = "HDG" then
    CurHdgCrsValue = HIDMacros.GetFSXVariable("AUTOPILOT HEADING LOCK DIR")
  end if
  if Mode = "CRS" then
    CurHdgCrsValue = HIDMacros.GetFSXVariable("NAV OBS:1")
  end if
end function

' Macro heading 0
call SetHdgCrs(0)

' Macro heading 45
call SetHdgCrs(45)

' etc

' Macro set as current heading
Dim CurHdg
CurHdg = HIDMacros.GetFSXVariable("PLANE HEADING DEGREES MAGNETIC")
call SetHdgCrs(Round(CurHdg))

' Macro +10
call SetHdgCrs(Round(CurHdgCrsValue()+10))

' Macro +1 - the easiest way
if Mode = "HDG" then
  HIDMacros.FSXEvent "HEADING_BUG_INC", 0
end if
if Mode = "CRS" then
  HIDMacros.FSXEvent "VOR1_OBI_INC", 0
end if

My whole FSX keyboard is used mainly for Twin Otter offline flying. It's wireless and has those stickers:

keyboard with stickers

XML configuration file for FSX can be downloaded here.