About Cheap Hacks.

This is the first in what will hopefully be a irregular series,
tentatively (and dubiously) entitled Cheap Hacks. The general idea
will be a varying range of hackish material, consisting of code and
explanatory text. At the very least, if linmagau.org keeps getting
published, I have enough ideas for a few more thousand words. The
intent is to provide tutorial style information, accessible to a
typical programmer. If you are 'leet, then you might as well move on
now.

If you notice that I have made a mistake in any of this, or have
suggestions for how to improve anything I have presented here, please
let me know, and I will try to present updates and errata in a future
column.

Also, I'd rather read this column than write it. If you'd like to
write this column (even for just one month), feel free!

				Brad Hards (cheaphacks@frogmouth.net)



Introduction.

This article discusses a little program I wrote that allows you to
check and change the status of some Logitech USB mice. Even if you
don't have such a mouse, hopefully there will be something that you
can take away from the various concepts.

The origins of this program came from a posting by Robert Love to the
linux-usb-devel mailing list. He wrote:

"I just got that new Logitech MX700 and before that I had a Logitech
Wireless Optical Mouseman.  Both are USB.

I believe the Windows driver for both has the ability to read the
battery strength.  Greg said the information may be encoded in the HID
information and maybe someone on this list can help.

What I want to do is find a way to grab this battery data (and any other
unique features, too).  I would prefer to not reinvent the wheel but
just write a small driver on top of the full HID driver, if that is
possible.

Any idea where to find docs?  If that is wishful thinking, whats the
easiest way to figure out what has the information?

Any help is appreciated.  Thanks,

        Robert Love"

Yeah, that's the same Robert Love of gory kernel scheduler hacks fame...

I had contact addresses with a couple of the Logitech developers, and
managed to extract the required information. It turns out that there
is actually a few other things that you can do, apart from just
extracting the battery state, but not all mice can do all the
features. After staring at the documentation for a while, it turns out
there are really three feature sets - the ability to switch the mouse
to a higher resolution, the ability to switch the "Smart Scroll"
feature, and Cordless Status Reporting.

Some Logitech mice have switchable optical sensors which default to
400 counts per inch. You can change this to 800 counts per inch by
sending a certain USB vendor command, and switch it back with a
slightly different vendor command. You can also tell what resolution
you currently have the mouse set to.

Smart scroll (also referred to as Cruise Control in some of the
documentation)uses a pair of small additional buttons on the mouse,
one which just in front of the scroll wheel, and one that is just
behind the scroll wheel. If you hold one of these buttons down, you
get the equivalent of a continuous scroll - holding the forward "smart
scroll" button is the same as scrolling upwards with the scroll wheel,
and holding the aft "smart scroll" button is the same as scrolling
downwards with the scroll wheel. This is enabled by default, but you
might want to disable it if you were going to map those buttons to a
particular function.

Cordless Status Reporting is, as the name suggests, only applicable to
cordless mice, and allows for reporting a wide variety of elements,
from the battery status to the number of buttons available, and the
shape of the plastic shell.

In the end, it breaks down like this:

Model           Description			 400/800  SS    CSR
M-BJ58		"Wheel Mouse Optical"		  Yes	  No	No
M-BJ79		"MouseMan Traveler"		  Yes	  No	No
M-BL63B		"MouseMan Dual Optical"		  Yes	  No	No
M-BP82		"MX300 Optical Mouse"		  Yes	  No	No
M-BP81A		"MX500 Optical Mouse"		  Yes	  Yes	No
M-UT58A		"iFeel Mouse (silver)"		  Yes	  No	No
C-BA4-MSE	"Mouse Receiver"		  No	  No	Yes
C-UA3-DUAL	"Dual Receiver"			  No	  No	Yes
C-BD9-DUAL	"Cordless Freedom Optical"	  No	  No	Yes
C-BG17-DUAL	"Cordless Elite Duo"		  No	  Yes	Yes
C-BF16-MSE	"MX700 Optical Mouse"		  No	  Yes*	Yes
C-BA4-MSE	"Cordless Optical TrackMan"	  No	  Yes	Yes
C-UF15		"Receiver for Cordless Presenter" No	  No	Yes

* Actually, sort of. See later discussion.

[Supplement - there are more mice supported, not listed in this article]

Quirky behaviour...
Now I wanted an application that could control and monitor the various
features. However since various mice only support a subset of the
features, and I wanted a single application, I needed to figure out
how to represent this. I already knew how to figure out what kind of
mouse it was (based on the USB vendor and product ID numbers), but I
needed to associate that mouse with a particular set of
capabilities.

A fairly common idiom in kernel device drivers is a "quirks table" or
"blacklist table", and I used a variation on the one in the Linux USB
HID driver. Despite the name, such a table is just a way of encoding
"per model" type information, and it doesn't have to have negative
connotations, although it often does, because it is used to record
"alternative interpretations" of the specification.

My table code looks like this:

#define HAS_RES 0x01  /* mouse supports variable resolution */
#define HAS_SS  0x02  /* mouse supports smart scroll control */
#define HAS_CSR  0x04  /* mouse supports cordless status reporting */

struct device_table {
    int idVendor;
    int idProduct;
    char* Model;
    char* Name;
    int flags;
} device_table[] = {
    { VENDOR_LOGITECH, 0xC00E, "M-BJ58", "Wheel Mouse Optical", HAS_RES },
    { VENDOR_LOGITECH, 0xC00F, "M-BJ79", "MouseMan Traveler", HAS_RES },
    { VENDOR_LOGITECH, 0xC012, "M-BL63B", "MouseMan Dual Optical", HAS_RES },
    { VENDOR_LOGITECH, 0xC024, "M-BP82", "MX300 Optical Mouse", HAS_RES },
    { VENDOR_LOGITECH, 0xC025, "M-BP81A", "MX500 Optical Mouse", HAS_RES | HAS_SS },
    { VENDOR_LOGITECH, 0xC031, "M-UT58A", "iFeel Mouse (silver)", HAS_RES },
    { VENDOR_LOGITECH, 0xC501, "C-BA4-MSE", "Mouse Receiver", HAS_CSR },
    { VENDOR_LOGITECH, 0xC502, "C-UA3-DUAL", "Dual Receiver", HAS_CSR },
    { VENDOR_LOGITECH, 0xC504, "C-BD9-DUAL", "Cordless Freedom Optical", HAS_CSR },
    { VENDOR_LOGITECH, 0xC505, "C-BG17-DUAL", "Cordless Elite Duo", HAS_SS | HAS_CSR },
    { VENDOR_LOGITECH, 0xC506, "C-BF16-MSE", "MX700 Optical Mouse", HAS_SS | HAS_CSR },
    { VENDOR_LOGITECH, 0xC508, "C-BA4-MSE", "Cordless Optical TrackMan", HAS_SS | HAS_CSR },
    { VENDOR_LOGITECH, 0xC702, "C-UF15", "Receiver for Cordless Presenter", HAS_CSR },
    { 0, 0, 0, 0, 0 }
};

Lets look at what this does in a bit of detail. It defines a
structure with a few element fields. The only fields that are really
important are the idVendor, idProduct and flags - the two strings are
only for making nice labels. It then declares an array of those
structures, consisting of information about . The flags element is
set up as a bit array, so that the least significant bit is set on all
mice that have the switchable resolution, but isn't set on the mice
that can't do this switching. This applies equally to the next two
bits, and the remaining bits are available for future capabilities.

So if we have the vendor and product ID numbers (in variables
imaginatively called idVendor and idProduct), we can figure out the
capabilities with a test that looks like:

for (n = 0; device_table[n].idVendor; n++)
    if ( (device_table[n].idVendor == idVendor) &&
         (device_table[n].idProduct == idProduct) ) {
	if (0 != (device_table[n].flags & HAS_RES)) {
	    /* do stuff for mice with resolution switching */
	}
	if (0 != (device_table[n].flags & HAS_SS)) {
	    /* do stuff for mice with smart scroll */
	}
	if (0 != (device_table[n].flags & HAS_CSR)) {
	    /* do stuff for mice with cordless status reporting */
	}
    }

This basically loops over each entry in the device_table[], finding the
entry that has the correct combination of vendor and product ID
numbers. It then tests if a particular capability is set in the flags
field. The reason why the for() loop works when written like this is
because of the terminating entry of all zeros in device_table[].

Of course there are other ways to do this, but code that does stuff
like the following:

if (VENDOR_LOGITECH == idVendor) {
   if ( (0xC00E == idProduct) || (0xC00F == idProduct) ||
	(0xC012 == idProduct) || (0xC024 == idProduct) ||
	(0xC025 == idProduct) || (0xC031 == idProduct) ) {
       /* do stuff for mice with resolution switching */
       }

tends to be hard to get right, and very hard to modify if you need to
add an additional mouse type. The "quirks table" is an idiom worth
knowing, or at least copying.

User input.

In addition to knowing what the device can do, applications normally
need to figure out what the user wants. In a command-line type
application, this normally means parsing command line arguments. There
might be a case for writing code to parse argv[] yourself, but I've
never heard one. I personally like getopt (part of glibc), but popt
(part of the RPM tool, see http://www.rpm.org) is also worth a look if
your needs are a little more complex. 

I won't go into detail on these, and will instead refer you to the
manual pages for both getopt(3) and popt(3). I just used a standard
getopt approach - there is an example of this usage in the getopt(3)
man page. I provided options for getting smart scroll status,
disabling smart scroll, enabling smart scroll, getting current
resolution and switching resolution to and from 800cpi. I didn't
bother to control cordless status reporting in this version, although
it would be trivial to add. I also provided the normal options for
printing the current version string, and a basic usage message.

I've helped out with user support for the Linux USB team, and knowing
which version is being used is often incredibly useful. Adding this
much code isn't that hard:

void version(void)
{
    printf("Logitech Mouse Applet, Version %s\n", VERSION);
}

and it may help you when you are trying to diagnose a problem. Of
course, you should #define VERSION once, and use it everywhere, or
you'll eventually release a version where the versions don't agree. 

The business end - USB

Universal Serial Bus devices are remarkably easy to use. You can
pretty much just plug the device into your PC, and the system can
pretty much figure out what kind of device it is, and how to drive
it. This much flexibility naturally comes at a cost though, and that
cost is complexity on the system side, which includes a fair number of
application programming interface (that's "API" to you acronym
fanatics) options. 

The interface I chose for the logitech mouse applet was the USB
file system (usbfs on recent kernels, previously usbdevfs). Most people
know this as the thing that provides /proc/bus/usb/devices, which
shows the various USB devices that are attached to the system. However
it also provides a file-like entry for each USB device, which you can
use ioctl() commands on. Those ioctl() commands allow you to send
various types of messages to the device, and receive data
back. However dealing with ioctl() commands isn't that nice, and would
also mean that the mouse applet would only work with Linux. I could
have written an abstraction layer to hide the ioctl() commands, and to
provide for future portability to other operating systems, except that
I am too clueless to get it right, and too lazy to get a clue.

Instead, I downloaded Johannes Erdfelt's libusb
(http://libusb.sourceforge.net) package, which not only provides a
nice abstraction, it already works with other operating systems. That
allowed me to write code like:

int get_resolution(struct usb_device *dev)
{
    char resolution;
    int result;
    usb_dev_handle *usb_h;

    usb_h = usb_open(dev);
    if (0 > usb_h) {
        printf("Error opening usbfs file: %s", usb_strerror());
        return -1;
    }

    result =  usb_control_msg(usb_h,
                              USB_TYPE_VENDOR | USB_ENDPOINT_IN,
                              0x01,
                              0x000E,
                              0x0000,
                              &resolution,
                              0x0001,
                              100);

    if (0 > result) {
        printf("Error getting resolution from device : %s", usb_strerror());
    }

    usb_close(usb_h);

    return resolution;
}

This code sends a certain vendor control command to the mouse that is
specified in the function argument. This makes the device return the
current resolution in the one byte "resolution" variable. The magic
numbers are basically extracted from the documentation provided by
Logitech. If I didn't have that documentation, I probably wouldn't
have bothered writing the application at all, although I could
potentially have used USB Snoopy (on SourceForge)  to reverse engineer
the protocol under Windows.

Similarly, to set the resolution, I used the following code:

/* resolution should be 0x03 for 400cpi, 0x04 for 800cpi */
int set_resolution(struct usb_device *dev, int resolution)
{
    usb_dev_handle *usb_h;

    usb_h = usb_open(dev);
    usb_control_msg(usb_h, USB_TYPE_VENDOR, 0x02, 0x000E, resolution,
                    NULL,  0x0000, 100);
    usb_close(usb_h);

    return 0;
}

Note that in this second case, there is no data segment, so the
payload is set to NULL, and the length of that payload is zero.

The use of libusb does complicate things a bit, in that the applet
won't compile without libusb, but it substantially simplifies the code
base, and makes the end application much, much more portable.

There is similar code for getting and setting the smart scroll
feature, and also for getting the cordless status reports. However I
had a lot of trouble getting the smart scroll status on the MX700 that
I bought specifically to test that bit of the code. I had no problem
enabling and disabling the scrolling, but I couldn't determine what
the current state was. I was going nuts trying to figure out what I
was doing wrong, and eventually decided that it had to be a broken
device. My contact at Logitech investigated it, and confirmed that the
MX700 firmware doesn't support smart scroll reporting, and that
Logitech were going to take the command out of the documentation...

If you want to see the rest of the code, you can download it from
http://www.frogmouth.net/mouse-applet/logitech_applet-0.4.tar.gz.

The full listing shows some of the other function calls necessary to
make use of libusb, and how the cordless status reporting information
is encoded. It also shows how I work around the fact that the MX700
doesn't do smart scroll status reporting - see the quirks table in the
code, and the quirks code presented above.

The importance of using appropriate libraries cannot be
overstated. Code in libraries may not do all of what you want, but if
you choose your libraries wisely, they will be relatively bug free,
and will save you a lot of time. I consider it entirely appropriate to
spend a third of the development time for a project evaluating
libraries for ease of use, code quality, suitable documentation, and
compatible licensing.

Summary, and Future

This was a fun, cheap hack, and although the packaging leaves a bit to
be desired, the end product does work. For hardware vendors - at least
one person (other than me:) based a buy decision on the release of
this data. 

For developers, remember that the documentation is very useful, but it
may not be all that you would hope for. Treat it with caution too.

This sort of mouse control functionality is ideally integrated into
the control centre type functionality on KDE and GNOME. If you can
hack GUIs and want to work on this, I'd be happy to help you with the USB side.

Acknowledgments.

I'd like to thank JE for writing and maintaining libusb, the FSF for
GNU getopt, and rml for the initial inspiration. I'd also like to
thank Logitech for the documentation and additional assistance. I'd
also like to thank you for reading this far.

