Wednesday, May 4, 2011

The dream, AutoHotkey-powered foot pedal for programmers and ergonomanics

As a hobby programmer and someone who in general use the computer a lots, I'm acutely aware of my hands/arms's general well-being ever since I felt the symptoms of numbness a few years back. Often times not using the computer or typing less is not an option, and a foot pedal is a godsend so that my feet can relieve my hands' duties from time to time.


Some foot pedals out there designed (as they claim) explicitly for ergonomically-minded people are just outrageously expensive, not to mention that their drivers / software do not seem to be very flexible. So, instead of those pedals, I picked up an Infinity transcription foot pedal (INFINITY-IN-USB-1) for less than 40 bucks on eBay, and hacked my own driver for it. The Infinity pedal is designed for transcription (i.e. has Play/Rew/Fwd buttons), but it has 3 sturdy buttons and is actually a great pedal to be re-purposed.


AutoHotkey is the key (puns not intended) to getting this pedal to work exactly the way I wanted. AutoHotKey is an amazing macro / hotkey scripting tool on Windows. It's lightweight, portable, and very powerful - albeit with a slightly awkward syntax. The important backend to my foot pedal "driver" is a few extra functions for AutoHotkey to deal with input from a Human Interface Device (HID), and is due to some helpful forum poster at the AutoHotKey forum, adapted by me.

The Infinity pedal registers itself to Windows as a HID device, and presses and releases of the buttons send combinations of numbers (see code below for details). It is technically capable of distinguishing simultaneous pedal presses as well, though my foot dexterity is not so great and I never utilize this particular possibility.

My AutoHotkey script is below. To use it, download AutoHotKey from its website; grab the script at the end of this post and save it to something like footpedal.ahk, and drag the script (in Windows Explorer) to the AutoHotKey.exe icon to run it. This script is not intended to be feature complete; rather, it's a skeleton for you to build your own dream foot pedal setup. You can make executable from that script as well (see AutoHotKey's website for details), though it might be much more useful for you to adapt the code to do exactly what you want before you compile it into an executable.

How I use it? The big "Play" button is my generalized page down key. It sends page down by default, though when certain windows are in focus, it sends other keys that act more like page down in that context (for example, I get some of my daily news dosage from Google Fast Flip, and sending the "n" key on that webpage will move to the next story). The left button acts as Alt-Tab to switch windows, and the right button just simulate a left mouse click. (In the past, the left and right button served as extra Ctrl and Alt for me since I am an avid Emacs user, though I found myself using them not so often).

To avoid confusion, though, I leave out those custom code below, and the script below only opens useless message boxes about which pedal is pressed or which is up. You can easily change those MsgBox lines to make them do whatever you want (AutoHotkey permitting :)). The only functions that you need to change should be PressKey() and ReleaseKey().

Without further ado, here is the script. Feel free to adapt it to your heart's content ;)

OnMessage(0x00FF, "InputMessage")
 
RegisterHIDDevice(12, 3) ; Register Foot Pedal
 
PedalLastPress := 0
 
Return
 
ProcessPedalInput(input)
{
    global PedalLastPress
     
    ; The input are combinations of 1, 2, 4 with 00 appended to the end
    ; indicating which pedals are pressed.
    ; For example, pressing the leftmost pedal triggers input 100, middle pedal 200, etc.
    ; all three pedals presses together will trigger 700 (1 ^ 2 ^ 4 = 7)
    ; Release of pedal trigger an input indicating what pedals are still held down
     
    input := input//100
    If (input > PedalLastPress)
      PressKey((PedalLastPress & input) ^ input)
    Else
      ReleaseKey((PedalLastPress & input) ^ PedalLastPress)
     
    PedalLastPress := input
}
     
PressKey(bits)
{
    ;ToolTip Pressing %bits%
    If (bits & 1) ; left pedal
    {
  Msgbox Left pedal pressed!
    }
    Else If (bits & 4) ; right pedal
    {
     Msgbox Right pedal pressed!
    }
    Else ; center pedal
    {
  MsgBox Center pedal pressed!
    }
    }

ReleaseKey(bits)
{
    ;ToolTip Releasing %bits%
    If (bits & 1)
     MsgBox left pedal up!
    Else If (bits & 4)
     MsgBox Right pedal up!
    Else
  MsgBox Center pedal up!
    
}
 
Mem2Hex( pointer, len )
{
A_FI := A_FormatInteger
SetFormat, Integer, Hex
Loop, %len%  {
Hex := *Pointer+0
StringReplace, Hex, Hex, 0x, 0x0
StringRight Hex, Hex, 2          
hexDump := hexDump . hex
Pointer ++
}
SetFormat, Integer, %A_FI%
StringUpper, hexDump, hexDump
Return hexDump
}
 
; Keyboards are always Usage 6, Usage Page 1, Mice are Usage 2, Usage Page 1,
; HID devices specify their top level collection in the info block  
RegisterHIDDevice(UsagePage,Usage)
{
; local RawDevice,HWND
RIDEV_INPUTSINK := 0x00000100
DetectHiddenWindows, on
HWND := WinExist("ahk_class AutoHotkey ahk_pid " DllCall("GetCurrentProcessId"))
DetectHiddenWindows, off
 
VarSetCapacity(RawDevice, 12)
NumPut(UsagePage, RawDevice, 0, "UShort")
NumPut(Usage, RawDevice, 2, "UShort")    
NumPut(RIDEV_INPUTSINK, RawDevice, 4)
NumPut(HWND, RawDevice, 8)
 
Res := DllCall("RegisterRawInputDevices", "UInt", &RawDevice, UInt, 1, UInt, 12)
if (Res = 0)
MsgBox, Failed to register for HID Device
}       
InputMessage(wParam, lParam, msg, hwnd)
{
RID_INPUT   := 0x10000003
RIM_TYPEHID := 2
SizeofRidDeviceInfo := 32
RIDI_DEVICEINFO := 0x2000000b
 
 
DllCall("GetRawInputData", UInt, lParam, UInt, RID_INPUT, UInt, 0, "UInt *", Size, UInt, 16)
VarSetCapacity(Buffer, Size)
DllCall("GetRawInputData", UInt, lParam, UInt, RID_INPUT, UInt, &Buffer, "UInt *", Size, UInt, 16)
 
Type := NumGet(Buffer, 0 * 4)
Size := NumGet(Buffer, 1 * 4)
Handle := NumGet(Buffer, 2 * 4)
 
VarSetCapacity(Info, SizeofRidDeviceInfo)  
NumPut(SizeofRidDeviceInfo, Info, 0)
Length := SizeofRidDeviceInfo
 
DllCall("GetRawInputDeviceInfo", UInt, Handle, UInt, RIDI_DEVICEINFO, UInt, &Info, "UInt *", SizeofRidDeviceInfo)
 
VenderID := NumGet(Info, 4 * 2)
Product := NumGet(Info, 4 * 3)
 
;   tooltip %VenderID% %Product%
 
 
if (Type = RIM_TYPEHID)
{
SizeHid := NumGet(Buffer, (16 + 0))
InputCount := NumGet(Buffer, (16 + 4))
Loop %InputCount% {
Addr := &Buffer + 24 + ((A_Index - 1) * SizeHid)
BAddr := &Buffer
Input := Mem2Hex(Addr, SizeHid)
If (VenderID = 1523 && Product = 255) ; need special function to process foot pedal input
ProcessPedalInput(Input)
Else If (IsLabel(Input))
{
Gosub, %Input%
}
 
}
}
}

9 comments:

Osgaldor said...

I am a programming student who was disabled by a bad stroke in 2009. I need to re-task my infinity pedal (I used to be a medical transcriptionist before my stroke took the ability to type with two hands away from me.) so that the right button is a right click, the left button is a shift key, and the big main button is a left click. Or alternatively, right button is ALT, left is CTRL and main is left click.

When I run the script "as is" I get an error on line 040: target label does not exist.

I'm not yet sophisticated enough to troubleshoot this script. I am familiar with AHK but not this kind of more complex script. Could I get you to help me fix this and create the functionality I need I will be happy to make a donation. Please email me at osgaldor at gmail dot com. Thanks!

x said...

Hi Osgaldor,

I modified the script above a little to make it less confusing. In your case, you might want to replace the functions mentioned above with the following code. (If you haven't realized, the lines starting with ";" are comments)

PressKey(bits)
{
;ToolTip Pressing %bits%
If (bits & 1) ; left pedal
{
Send {LButton}
}
Else If (bits & 4) ; right pedal
{
Send {RButton}
}
Else ; center pedal
{
Send {Shift Down}
}
}

ReleaseKey(bits)
{
;ToolTip Releasing %bits%
If (bits & 1)
{
;MsgBox left pedal up!
}
Else If (bits & 4)
{
;MsgBox Right pedal up!
}
Else
{
Send {Shift Up}
}
}

Osgaldor said...

Thank you very much!

Osgaldor said...

I re ran the example script it works now, except when I press the left pedal, the messagebox that pops up says center pedal up.

Thank you so very much. I will be making a donation soon.

Timothy J. McGowan said...

I was having the same symptom Osgaldor reports.

Fix for me: Copy the script. Open Notepad. Maximize Notepad to full-screen. Paste script. Save.

If Notepad wraps any of the comment lines in mid-line, those become commands. Maximizing Notepad before pasting and saving prevents that (assuming you're not using a huge system font, I suppose).

My FlexPro and ErgoLogic keyboards do not have WinKeys on them. Let's see whether we can get a foot pedal to emulate those keys...

-- Tim
Timothy J. McGowan

Timothy J. McGowan said...

I can Send {LWin} with a pedal press, but I can't seem to use it as a shift key. That is, I can't use LeftPedal+R to get the Run dialog.

Any solution that you know of?

-- Tim
Timothy J. McGowan

Timothy J. McGowan said...

In the immortal word of Homer J. Simpson: D'oh!

I wanted the left pedal to be my WinKey replacement. I use the foot pedals in another program to play back dictation, but I don't use the left pedal for anything.

So I've modified only the left foot pedal, as follows:
PressKey(bits)
{
;ToolTip Pressing %bits%
If (bits & 1) ; left pedal
{
Send {LWin Down}
}
Else If (bits & 4) ; right pedal
{
;Msgbox Right pedal pressed!
}
Else ; center pedal
{
;MsgBox Center pedal pressed!
}
}

ReleaseKey(bits)
{
;ToolTip Releasing %bits%
If (bits & 1)
{
Send {LWin Up}
}
Else If (bits & 4)
{
;MsgBox Right pedal up!
}
Else
{
Send {Shift Up}
}
}

Works great! Thanks so much! Where's my credit card?

-- Tim
Timothy J. McGowan

Timothy J. McGowan said...

A very kind soul on the AutoHotkey forums has updated this script to work with the 64-bit, and current, version of AutoHotkey. It's too long for me to post here. See the solution and the explanation of the comments by "just me" here: http://www.autohotkey.com/board/topic/91506-broken-dllcall-to-registerrawinputdevices/

Robert Patrician said...

I'd like to use this for various video game features, but those involve not just sending a keypress but rather holding down a key. For example if I wanted the center to be shift for running, it would only send one moment of shift if I put in "send shift." How can I get this to work like I want it?