powermate

The Griffin PowerMate is a USB input device. In addition to the hardware, it comes with a driver and a control panel. The PowerMate control panel allows you to map a “User Action” to a “Computer Action”. For example, when the user rotates the knob right, the page in Internet Explorer will scroll down:

For the majority of end users, this is all the customization they need. However, wouldn’t it be nice if we could program directly against the PowerMate? That’s the focus of today’s post and here’s a screenshot of the resulting app:

The first requirement to programming the PowerMate is a copy of the Windows Driver Development Kit (DDK). The Windows Server 2003 SP1 DDK is free. However, you will have to pay for shipping and handling of the CD to your door. The option to download is available only to MSDN subscribers. After installation, the directory paths will need to be set. Add the DDK directory path to the start of the search path (bottom of the list) to avoid compilation errors.

The Solution distributed with this post has two projects:

  • C++/CLI class library for the PowerMate code
  • C# application to test the PowerMate.dll

Let’s begin by listing the types and data members of the PowerMate class. First, simple enums to represent button state and rotational direction:


public: enum class ButtonState
{
    Up,
    Down
};

public: enum class RotationalDirection
{
    Left,
    Right
};
 

Next, we have a private enum representing human interface device (HID) attributes for the PowerMate hardware. These values will be compared against HID attributes for each device returned during the enumeration process.


private: enum class HidAttributes
{
    VendorID      = 0x077d,
    ProductID     = 0x0410,
    VersionNumber = 0x0311
};
 

A kernel object handle to the HID. We’ll be using this handle for read/write operations later on.


private: HANDLE handleToDevice;
 

A managed thread handle. The thread created will be used for event notification and only terminates when the exit event is triggered.


private: Thread^ inputThread;
private: ManualResetEvent^ exitEvent
 

Next, we have storage for a rotational value that is clamped between the upper and lower bounds. By default, the bounds are between -100 and 100. If a user continues rotating the knob beyond the specified range the event will be ignored and the delegate will not fire. Of course, you’re free to alter this feature.


privateint rotationValue;
privateint rotationLowerBound;
privateint rotationUpperBound;
 

Finally, delegates for button and rotation events:


publicdelegate void RotationHandler(RotationalDirection direction, int value);
publicdelegate void ButtonHandler(ButtonState bs);
private: RotationHandler^ rotationDelegate;
private: ButtonHandler^ buttonDelegate;
 

Clients can subscribe to events using the following event accessors:


publicevent RotationHandler^ RotateEvent
{
    void add(RotationHandler^ value)
    {
        rotationDelegate += value;
    }

    void remove(RotationHandler^ value)
    {
        rotationDelegate -= value;
    }
}

publicevent ButtonHandler^ ButtonEvent
{
    void add(ButtonHandler^ value)
    {
        buttonDelegate += value;
    }

    void remove(ButtonHandler^ value)
    {
        buttonDelegate -= value;
    }
}
 

Before we examine more properties of the PowerMate class, let’s first have a look at its two-stage construction. The constructor is trivial so we can safely skip it. After construction, the Initialize method needs to be called. Initialize will enumerate available HID devices and open the first PowerMate device it encounters. Once you’ve successfully programmed one PowerMate device, you can easily modify the code to suport many – perhaps by creating a PowerMateManager class to maintain a list of every PowerMate connected to the system.

The Initialize method begins by finding and shutting down the PowerMate Control Panel. Why? The PowerMate Control Panel intercepts input and executes the appropriate action stored in its settings. An example of one such setting is ”Increase volume” under Global settings. As our app responds to the user performing a right rotation, the PowerMate Control Panel also responds to this action and increases the system volume. To obtain exclusive access to the hardware, we need to close the PowerMate Control Panel (PowerMate.exe).

Please note that all of the code related to the Initialize method has been stripped clean of of error handling and conditionals for presentation purposes.


array^ powerMateProcess = Process::GetProcessesByName("PowerMate");

HANDLE hp = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE,
                        FALSE,
                        powerMateProcess[0]->Id);

HWND hWnd = FindWindow(NULL, L"PowerMateWnd");
PostMessage(hWnd, WM_CLOSE, 0, 0);
WaitForSingleObject(hp, 5000);
 

Now it’s time to put that DDK to work. This section of the Initialize method involves the following steps:

  1. Get the HID GUID.
  2. Obtain a handle to a device information set.
  3. Enumerate device interfaces.
  4. Open a device.
  5. Check for matching HID attributes.

Step 1: Get the HID GUID.
This GUID represents a device interface that the driver exposes. Device interfaces with the same functionality are grouped together in interface classes. For example, all joysticks on the system could be members of the joystick device interface class.


GUID hidClass;
HidD_GetHidGuid(&hidClass);
 

Step 2: Obtain a handle to a device information set.
A device information set consists of device information elements. A device information element contains a list of all device interfaces associated with a particular device. The SetupDiGetClassDevs function retrieves a set of devices for the specified class. In other words, a list of HID-compliant devices.


HDEVINFO hDevInfoSet = SetupDiGetClassDevs(&hidClass,
                                           NULL,
                                           NULL,
                                           DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
 

Step 3: Enumerate device interfaces.
To communicate with the device, we need to open the device. To open the device, we need a device path. The device path is retrieved during the enumeration process and involves the following steps:

  • For each device class in the device information set, call SetupDiEnumDeviceInterfaces.
  • Each call to the above function returns an SP_DEVICE_INTERFACE_DATA structure.
  • We pass the above structure to SetupDiGetDeviceInterfaceDetail.
  • The above method returns an SP_DEVICE_INTERFACE_DETAIL_DATA structure.
  • The SP_DEVICE_INTERFACE_DETAIL_DATA structure contains the device path.

SP_INTERFACE_DEVICE_DATA interfaceData;
interfaceData.cbSize = sizeof(interfaceData);

for (int i = 0;
     SetupDiEnumDeviceInterfaces(hDevInfoSet,
                                 NULL,
                                 &hidClass,
                                 i,
                                 &interfaceData);
     ++i)
{
    // determine buffer size first
    DWORD bufferLength;
    SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
                                    &interfaceData,
                                    NULL,
                                    0,
                                    &bufferLength,
                                    NULL);

    // then allocate
    PSP_INTERFACE_DEVICE_DETAIL_DATA interfaceDetail =
        (PSP_INTERFACE_DEVICE_DETAIL_DATA)new char[bufferLength];

    interfaceDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);

    SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
                                    &interfaceData,
                                    interfaceDetail,
                                    bufferLength,
                                    NULL,
                                    NULL);
 

Step 4: Open a device.
With a successful call to SetupDiGetDeviceInterfaceDetail above we now have a device path to pass into CreateFile. Upon success, the CreateFile function will open the device and return a handle to it.


HANDLE hDevice = CreateFile(interfaceDetail->DevicePath,
                            GENERIC_READ | GENERIC_WRITE,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            NULL,
                            OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                            NULL);
 

Step 5: Check for matching HID attributes.
We check the attributes of the newly opened device to verify it’s a PowerMate input device.


HIDD_ATTRIBUTES hidAttr;
BOOLEAN result = HidD_GetAttributes(hDevice, &hidAttr);

if ((int)HidAttributes::ProductID == hidAttr.ProductID &&
    (int)HidAttributes::VendorID  == hidAttr.VendorID)
{
    // ...

    inputThread = gcnew Thread(gcnew ThreadStart(this, &PowerMate::InputThread));

    inputThread->Priority = ThreadPriority::Normal;
    inputThread->Start();

    return true;
}
 

When a PowerMate input device is found, an input thread is started for event notification. We”ll have a look at the input thread shortly, but first two-stage destruction:


publicvoid Shutdown()
{
    if (nullptr != exitEvent)
    {
        exitEvent->Set();
        inputThread->Join();
    }

    if (NULL != handleToDevice)
        CloseHandle(handleToDevice);

    Process^ p = Process::Start(
        "C:Program FilesGriffin TechnologyPowerMatePowerMate.exe");

    HWND hWndTaskBar   = FindWindowEx(NULL, NULL, L"Shell_TrayWnd", NULL);
    HWND hWndTrayNotify= FindWindowEx(hWndTaskBar, NULL, L"TrayNotifyWnd", NULL);
    HWND hWndSysPager  = FindWindowEx(hWndTrayNotify, NULL, L"SysPager", NULL);
    HWND hWndToolBar   = FindWindowEx(hWndSysPager, NULL, L"ToolbarWindow32", NULL);

    RECT rect;
    ::GetClientRect(hWndToolBar, &rect);

    for (WORD x = 0; x < rect.right; x += 8)
    {
        ::SendMessage(hWndToolBar,
                      WM_MOUSEMOVE,
                      0,
                      MAKELPARAM(x,0));
    }
}
 

Shutdown begins by signaling the exit event. Join blocks the calling thread until the input thread terminates.
Next, we start a new instance of the PowerMate Control Panel (after shutting it down during Initialization). It”s hardwired to the default installation directory. However, it’s advisable that you pull this path out of the registry. The InstallDir key is located under: HKEY_LOCAL_MACHINESOFTWAREGriffin TechnologyPowerMate1.5 and you can use regedit to verify this.

The last part is silly and if you have a better way to fix it please let me know. The icon in the task notification area does not update automatically. Here, we simulate a mouse traveling across the notification area to force an update.

The input thread drops into a while loop that only breaks when it receives an exit event. The exit event is checked during wait timeouts which occur every 2000 milliseconds. With each iteration a call to ReadFile is made. The OVERLAPPED structure is used for asynchronous I/O. The event member of the OVERLAPPED structure is signaled when the read operation is complete. Upon a successful read, a report buffer consisting of eight characters is returned. Indices in the report buffer are checked for a button or rotational event and the corresponding delegate is fired.


privatevoid InputThread()
{
    char reportBuffer[8] = {0};
    DWORD dwBytesRead = 0;
    BOOL result = FALSE;

    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    OVERLAPPED overLap;
    ZeroMemory( &overLap, sizeof(overLap));
    overLap.hEvent = hEvent;

    while(true)

    {
        result = ReadFile(handleToDevice,
                          reportBuffer,
                          sizeof(reportBuffer),
                          &dwBytesRead,
                          &overLap);

        DWORD dw = WaitForSingleObject(hEvent, 2000);

        switch(dw)
        {
            case WAIT_OBJECT_0:
            {
                // test for pushbutton
                if (reportBuffer[1] == 1)
                {
                    ButtonState bs = ButtonState::Down;
                    EventsHelper::Fire(buttonDelegate, bs);
                }
                else if (reportBuffer[2] == 0 && reportBuffer[1] == 0)
                {
                    ButtonState bs = ButtonState::Up;
                    EventsHelper::Fire(buttonDelegate, bs);
                }

                // test for knob rotation
                if (reportBuffer[2] != 0)
                {
                    rotationValue += reportBuffer[2];
                    if (rotationValue < rotationLowerBound)
                    {
                        rotationValue = rotationLowerBound;
                        break;
                    }

                    if (rotationValue > rotationUpperBound)
                    {
                        rotationValue = rotationUpperBound;
                        break;
                    }

                    EventsHelper::Fire(rotationDelegate,
                                       (reportBuffer[2] < 0) ?
                                           RotationalDirection::Left :
                                           RotationalDirection::Right,
                                       rotationValue);

                } // end ifbreak;

            case WAIT_TIMEOUT:
            {
                if (exitEvent->WaitOne(20,false))
                {
                    CloseHandle(hEvent);
                    return;
                }
            } break;

        } // end switch

// end while

// end method
 

The EventsHelper class continues to publish events even if a subscriber throws an exception:


public ref class EventsHelper
{
    publicstatic void Fire(Delegate^ del, ... array^ args)
    {
        if (nullptr == del)

            return;

        array^ delegates = del->GetInvocationList();
        for each (Delegate^ sink in delegates)
        {
            try
            {
                sink->DynamicInvoke(args);
            }
            catch(Exception^ e)
            {

            }
        }
    }
};
 

Finally, we can start manipulating properties of the PowerMate input device. Setting various properties involve the same basic steps:

  • Filling out report buffers and transmitting them.
  • Receiving report buffers and acting on them.

For example, getting or setting the LED brightness:


publicproperty Byte LedBrightness
{
    void set(Byte value)
    {
        if (NULL != handleToDevice)
        {
            HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

            OVERLAPPED overLap;
            ZeroMemory(&overLap, sizeof(overLap));
            overLap.hEvent = hEvent;

            UCHAR reportBuffer[2];
            reportBuffer[0] = 0;
            reportBuffer[1] = value;
            DWORD dwBytesTransmitted = 0;

            BOOL result = WriteFile(handleToDevice,
                                    reportBuffer,
                                    sizeof(reportBuffer),
                                    &dwBytesTransmitted,
                                    &overLap);

            DWORD dw = WaitForSingleObject(hEvent, 42);

            switch(dw)
            {
                case WAIT_OBJECT_0:
                    break;

                case WAIT_TIMEOUT:
                    break;
            }

            CloseHandle(hEvent);
        }
    }

    Byte get()
    {
        UCHAR reportBuffer[7] = { 0 };

        if (NULL != handleToDevice)
        {
            BOOLEAN error = HidD_GetInputReport(handleToDevice,
                                                reportBuffer,
                                                sizeof(reportBuffer));

            if( FALSE == error )
                Trace::WriteLine("Failure to get input report - brightness");
        }

        return reportBuffer[4];
    }
}
 

The two other properties are ”pulse on” and ”pulse speed”. Please reference the code for implementation details.

Those are the basics of PowerMate programming. Special thanks to Dustin at Griffin for making this possible. The code still needs refactoring and more support for error handling. However, it’s surely enough to get you started. You can download the source here.

a little programming history

I remember the first book I ever read on programming:

  • Publisher: Macmillan Digital Publishing (September, 1995)
  • ISBN: 1568302371

I was intent on programming and read this book twice. It was a little difficult to understand because I didn’t own a computer at the time. Eventually, I managed to get my hands on an Apple Macintosh 9800 and a copy of CodeWarrior. After that I quickly moved on to this book:

  • Publisher: Wiley Publishing; Book & CD edition (December 20, 1995)
  • ISBN: 1568843496

I’m not sure how many times I tried pushing my way through this book. It was very difficult because I was trying to build applications using the Mac Toolbox before I knew a programming language! I look back on myself and laugh. It was quite fun to deduce fundamental programming constructs in realtime.

on laying concrete

Substantial applications require a solid infrastructure. An infrastructure or framework dictates the organization and interaction of core components. The design of a framework and the derivative applications to follow is an expression of art.

Software is built in small incremental steps, layer upon layer. The layout and modularity of these layers or building blocks will affect every facet of development from that point on. Careful consideration must be given to laying a solid foundation. The underpinnings of your architecture can ease further development or hinder it. Think about it. The way in which you structure your application today may grind development to a halt tomorrow. It may become too costly to extend the system. Even worse, the system may become completely unusable beyond a certain point.

Unfortunately, doing things the wrong way is often the case. This is expected because the problem domain is fuzzy. As development continues the picture becomes clearer and the problem crystallizes. A well-defined problem reveals a well-defined solution. Code must constantly be refactored as new insights are brought into the fold. Charge off into the unknown. Just make sure you come back and refactor what you’ve learned into an elegant solution.