Multiple Scroll-Wheel Mice and XFree86

Well, I just spend about six hours poring over XFree86 source code in an attempt to get it to support my new 2-scroll-wheel mouse. I succeeded. Here's an explanation of what I did. You can also download the patch which applies the changes described below to the XFree86 3.3.6 source.

PS/2 Scroll Mice

For details of the digital-logic-level PS/2 protocol, see Adam C's PS/2 Web Page. This information isn't needed for what I'm about to say, however, so look only if you're curious. You should, however, be somewhat familiar with the procedure for using a single-scroll-wheel mouse with X.

Suppose you were to read from /dev/psaux with a mouse attached. What would you see? Every time these was mouse action, you'd see three bytes. The first one of these indicates the state of the first three mouse buttons, using bit 1 (0x01) for button 1, bit 2 (0x02) for button 2, and so forth. The bits are set when the buttons are down. Bit 4 (0x08) is always set (but I'm told Microsoft mice violate the spec by not setting it). The next two bytes contain movement information, first for X and then for Y.

So where does the scroll-wheel information come from? The scroll-wheel mice extended the protocol by adding a fourth byte, for the "z axis." By default, this byte is not sent; the mice are in "backward-compatible" mode where they send three bytes only. When the computer can deal with the fourth byte, it tells the mouse this (the exact initialization string sent differs by brand; look in mice.c in GPM or xf86_Mouse.c in XFree86 for details, or see the datasheets for the mouse controller chips, some of which describe the protocol very nicely). After the mouse gets this initialization, it starts sending the fourth byte with Z information.

What's in this fourth byte? My Logitech scroll mouse sends -1 when the wheel is scrolled up, and 1 when it is scrolled down. My recently acquired "A4 Tech WinBest 4D+ Mouse", which has two scroll wheels, sends -1 and 1 for the left one, and -2 and 2 for the right one.

How XFree86 Handles Scroll Mice

XFree86, as it currently stands, can't handle more than one scroll wheel. Why not? The following code, from hw/xfree86/common/xf86_Mouse.c in XFree86 3.3.6, illustrates the problem:
    default:    /* buttons */
      buttons &= ~(mouse->negativeZ | mouse->positiveZ);
      if (dz < 0)
        buttons |= mouse->negativeZ;
      else if (dz > 0)
        buttons |= mouse->positiveZ;
      dz = 0;
      break;
In this code snippet, mouse->negativeZ and mouse->positiveZ are the two buttons you specified on the ZAxisMapping line of your XF86Config file; dz is the value of the fourth byte received from the mouse. As can be easily seen, XFree86 look only at whether this value is less than or greater than zero, so a mouse which sends -1 and -2 to mean different things isn't going to accomplish very much.

Note that this may or may not apply to XFree86 4, about which I know absolutely nothing.

XFree86 Improvements

The first thing I did was set up X to acknowledge multiple scroll wheels - up to eight of them, in fact. The extra buttons are simply read from the configuration file; mine looks like this:
    Section "Pointer"
       Protocol        "IMPS/2"
       Device          "/dev/psaux"
       Buttons          7
       ZAxisMapping     4 5 6 7
    EndSection
The second set of buttons are used when the mouse returns -2 or 2; the third set when it returns -3 and 3, and so forth. The first set is used for -1 and 1, and also for any other values that don't match.

More XFree86 Problems

The modifications above weren't enough to get it working. XFree86 purports to support up to 12 buttons, but it doesn't, really. XFree86 stores buttons as ORed bits; the first button is 1, the second is 2, the third is 4, and so forth. It turns out that the order of the first three buttons is the opposite of what most people actually want, going from 3 to 1 instead of the other way around. XFree86 reverses them for your convenience. Because they're stored as ORed bits, XFree86 uses a lookup table to do the reversal, for efficiency. However, the lookup table has only 32 entries - which means it doesn't cover anything with button 6 or higher set. This seems to be completely arbitrary, and introduces an artificial limitation that needn't be there.

Thus, I apply the lookup table only to the low three bits of the button value. This allows buttons 6 and 7, and in fact all the buttons up to 12, to pass through unscathed.

Application Support

With these changes, I was able to run xev and see buttons 6 and 7 being returned by the second scroll wheel. However, many applications don't support buttons higher than 5. My windowmanager, ctwm, has a compiled-in limit of 5 buttons - but if the line that says:
    #define MAX_BUTTONS 5
in twm.h is changed to a higher number, ctwm is perfectly able to use the higher buttons. You're unlikely to be able to get support from closed-source programs, however; forget about using the second scroll wheel in Netscape, at least directly as buttons 6 and 7.