How I connected a Microsoft Bluetooth keyboard/mouse to Linux (and later, a nicer Apple Bluetooth keyboard...)

(If you're just looking for my "bthid" software package, scroll down...)

I bought a new keyboard and mouse last Sunday.  It is now Wednesday, and I am celebrating the use of my new keyboard for the first time today by typing this little note into my computer.

My new keyboard is a brand new Microsoft Bluetooth keyboard.  It was mildly expensive, and I am sure that I have contributed yet again to the wealth of Bill Gates by buying it.  But you see, it's a totally cordless keyboard.  It talks to the computer via radio.  So I can move around, put it on my lap, use it as a remote control when watching DVD movies on my computer, and generally look cool in the office.  The mouse that came with it is also the coolest looking mouse that I have ever seen.  It's got five buttons, and it's one of those new optical mice that works on any surface.

Too bad that these cool new toys didn't work with my computer.  Oh, they probably would have worked fine with this Dell Inspiron 8100 had I not modified it when it came from Texas.  But I installed Debian Linux on it from a CD-ROM the very first time that I powered it up, and I didn't bother with trying to keep the original Windows XP operating system that came preinstalled on its hard disk.  Since I never ran Windows, I never accepted its End-User License Agreement (EULA), and I should theoretically be able to return the Windows CD-ROM for a refund.  I got nowhere when I tried to do that, of course.

So, to make the new keyboard and mouse work with my computer, I had to do some hacking.

As I mentioned, these peripherals are Bluetooth devices.  Bluetooth is a new standard for wireless communication over short distances.  It is a full networking protocol suite, actually.  There is a set of Linux kernel modules called BlueZ that implement the Bluetooth protocol stack.  I downloaded BlueZ, built it, and installed it without a hitch.  I then stuck the Bluetooth transceiver into a USB port, which made it light up a cool blue color, and then started playing with the BlueZ utility programs.

It took a few attempts to educate myself about Bluetooth terminology and enjoy some success with the BlueZ utilities.  Also, it turns out that the Microsoft Bluetooth keyboard and mouse don't always respond to general inquiries.  Each of them has a little recessed button on its underside that makes it more receptive to meeting new friends.  Soon, I had learned the Bluetooth device addresses of the devices and got them to respond to general inquiries, identifying themselves as "Microsoft Keyboard" and "Microsoft Mouse".  This reassured me that the Bluetooth host computer interface (HCI), which is the blue-lighted dongle, and the peripherals themselves, were all working.  This felt like a much larger acheivement than it probably was, primarily because the BlueZ documentation is at best incomplete and at its worst completely illiterate.

Next, after coming to the grim realization that although Bluetooth human input devices (HIDs) operate a whole lot like USB HIDs, the HID code in my Linux 2.4.17 kernel is tightly tied to the USB subsystem, and there was no easy way to make it communicate with a HID over some other channel (like a Bluetooth connection).  I was going to have to do some programming.

When I have the BlueZ modules loaded, I can use Berkeley sockets to do Bluetooth network programming.  So I wrote a few little programs to try outgoing L2CAP connections to my devices (the Bluetooth analogy of TCP streams), and also to try to accept incoming connections.  A lot of experimentation revealed that both devices will accept a connection request from the host computer only during a brief period of time after they have been scanned (and remember that they respond to scans only during the brief period right after their batteries are installed or their reset button has been pressed and held for a while).  However, after the device has been connected successfully and then disconnected, it will attempt reconnection itself at the next time that it has any input to report.  So hitting a key or moving the mouse can spur an inbound connection request.  It turns out to be really important to use the right port numbers.  Well, they're not TCP port numbers, they're Bluetooth L2CAP Protocol Service Multiplexors (PSMs), but it's the same idea.

It was around this point that I did something that I should have done much earlier.  I downloaded and skimmed the HID spec from the Bluetooth Web site.  Okay, fine.  Every Bluetooth input device has a Control channel and an Interrupt channel.  Their PSMs are 17 and 19, respectively.  I put those magic numbers into my experimental code and got successful connections to both devices!  I then played around with hitting keys and moving the mouse around, dumping the inbound Interrupt packets in hex, and got a feel for how these devices encode their output (i.e., my input).  I won't waste time describing it here, but it's really painfully obvious and easy to decipher.

Bluetooth experts who read this note will howl in scorn at this point that one of the objectives of the Bluetooth HID spec is that the devices can be probed to describe their own output formats.  That's doubtless true, and it will be handy when somebody needs to write real Linux driver code that can handle arbitrary Bluetooth HIDs.  I just needed to handle my own two devices.  And I realized that it would be a lot easier to just translate their obvious output format to whatever I needed than to write a complete HID parser to query them and operate in a more general fashion.

First, the mouse.  I had already gone through the educational experience of adding a USB mouse to this computer (I'm up to four pointing devices now!) and knew that the way to get external mouse output to become input to the X server is to reconfigure the X server to read mouse output from a file.  So I set up a named pipe, or FIFO, called /dev/input/btmouse, told X to read from it (in addition to the core pointing devices and the USB mouse), and wrote a daemon program that writes to it.  This daemon scans for Bluetooth HIDs and also accepts incoming connections from them, and translates any mouse output packets that it receives into something that looks like IntelliMouse
Explorer PS/2 output, which is then written to the FIFO.  I had to find the code in the X server that reads input from mice to find out what the protocol looks like.  This took a couple of tries to get right, but it worked pretty quickly, and I could now use my cool new mouse.  Despite the number of hardware and software layers that poor mouse has to shout through in order to talk to the X server, there's no perceptible latency.  Since the "middle" mouse button is actually the mouse's scroll wheel, it's susceptible to inadvertent clicking when I scroll up, so I mapped the big side button to be button 2 and the wheel "button" to be button 4.

Second, the keyboard.  This was harder, although I spent most of the time just figuring out what the general approach should be.  It turns out that the X server can take keyboard input only from a Linux virtual terminal.  This was the primary constraint.  So I had to find a way to transfer the keyboard events generated by the Bluetooth keyboard into the bowels of the Linux kernel.  TTY handling code is ugly in every UNIX kernel that I've worked on and unfortunately Linux is not an exception.  In fact, Linux started out as a terminal emulator and eventually turned into an operating system kernel after Torvalds added disk I/O to his terminal emulator.  (This is a true story.)  So getting my keyboard to work would require connecting the latest input technology (Bluetooth) with the oldest code in the kernel.

Lucky for me, somebody has already had to do the hard part, since things like USB keyboarrds work fine with  Linux.  I read the code in the kernel for USB keyboards and realized that I could exploit it.  I wrote a small (100 lines) Linux device driver as a loadable module.  All it does is register itself as a keyboard device, and then take any keyboard input events that are written into it and hand them off to the input core as keycode events.  After remembering to load the "input" and "keybdev" modules, I loaded my new driver, and it worked the first time I tested it by creating a character device special file with "mknod" and writing some bytes into it.  The keycodes that I sent were immediately echoed as if I had typed them on the keyboard.  I was in business.

So the last step was to augment my daemon program to translate keyboard input reports into Linux input core keycode-down and -up events, and to write them into my character device special file.  It almost worked on the first try, and I typed this note in as a test of the second try.

It is entertaining in a perverse sort of way to try to count the number of times each of my keystrokes is mapped on its way from my fingers to the HTML composition application that I'm running.  Here is a (doubtless incomplete) list of the mappings I can identify.
  1. Keypress to scan code within Bluetooth keyboard
  2. Scan code to HID report
  3. HID report travels down Bluetooth protocol stack in keyboard to its transceiver and out over the airwaves
  4. Packet is received by USB Bluetooth HCI transceiver dongle and goes over USB to BlueZ protocol stack
  5. From the BlueZ stack's L2CAP layer the packet arrives in the daemon's socket and the daemon awakens
  6. Daemon translates the 10-byte input report interrupt packet into Linux input keycode events and writes them to char special file
  7. My little driver's write() routine hands these events one by one to the Linux input module
  8. The "input" module invokes the "keybdev" event handler, which translates each event into a sequence of PC keyboard scan codes that would have generated the same input, and sends those scan codes to handle_scancode() deep in the bowels of the kernel.
  9. handle_scancode doesn't do much translation, since the X server has placed the virtual terminal in RAW mode.  But there's an amazing amount of translation that it could have done before passing the mapped scan code to the line discipline layer!  Since nearly everybody uses either X or telnet/ssh to log into a UNIX box nowadays, much of this elaborate infrastructure now stands idle.
  10. The X server receives the scan codes and maps them to key codes and modifiers (shift, control, and so on).
  11. Then it maps those key codes to symbols.  I use the Dvorak keyboard layout, so I've configured X accordingly.
  12. The symbols and modifiers are sent over another socket to the X runtime library linked to Mozilla's HTML composition program, which puts the letters into its internal representation of this document and then asks the X server to display them on the screen.
  13. X maps the symbols via a font to bits on the display, which I see, completing the loop.
I did rearrange the keytops on the keyboard to match the Dvorak layout, even though that's not strictly necessary -- the keytops on the Dell's built-in keyboard still look QWERTY -- and even though the keys are not all exactly the same height.  That's because I know that several people at my office are going to pick it up and say "hey, cool keyboard" and I never want to miss a Dvorak evangelism opportunity.  The only thing I have left to do is to further augment my daemon program to implement the funky buttons on the top of the keyboard.  Some of them look like VCR buttons, and some are labelled Mail, Web/Home, My Documents, and so on.  I don't suppose that Microsoft expected anybody to map those buttons into "mplayer" commands!

The moral of the story, if any, is that a little persistence can turn the "Windows XP Only" label on a cool device's box into a lie.

Want to try to use this software yourself?  You're probably crazy.  Here is bthid 0.8 for Linux 2.4, and here is bthid 0.10 for Linux 2.6.  Let me know if you like it.  (I updated the package 1/11/2003 with some bugfixes, including polling to keep the devices awake and a better keyboard debouncing technique to cut down on spuriously repeated keystrokes.)  (And I updated it again on 1/15/2003 with major reworking and cleanup.  It should work now with other Bluetooth human input devices, since it now gets the HID descriptors out of the devices themselves.) (Updated once more 1/23/2003 with bugfixes.)(Updated 4/29/03 to use hotkeys.)(Updated 11-4-03 and 11-5-03 to add the "-p" option for keepalive pinging.)(On 12-30-03, ported bthid to Linux 2.6, which when properly configured no longer needs to be rebuilt or modified for bthid to work; no more fake.o module!)

Update

Seven months later, I'm still happily using my Bluetooth keyboard and mouse.  The daemon has been fine-tuned and well debugged.  And it seems that many other people are using the code.

I get lots of questions by e-mail.  I would like to try to answer the most common ones here.

"If I don't use the keyboard or the mouse for a few minutes, they seem to become inactive." Yes, they sure do.  Jiggle the mouse and it'll wake up in a couple of seconds.  The keyboard can be awoken with a keystroke during the first few minutes of its nap time, but if it has been asleep for a while, you may have to pop the batteries momentarily.  The new -p option enables a periodic ping that seems to alleviate the problem (at least with the Microsoft keyboard); thanks to Michal Semler for suggesting it.

"Why isn't your HID daemon part of the BlueZ packages?"  The BlueZ developers don't like my use of a new kernel module (fake.o) to implement a pseudo-device for the injection of input events into the kernel.  They insist that the evdev.o module can be used for this purpose instead, which it can't be, being a pseudo-device interface for monitoring input events, not injecting them.

"I downloaded your package, read the directions, and then went and did something else with the code, and it didn't work.  Make it work!"  Please be assured that I've foregone sleep and food while I devote my every waking moment to solving the problem that you have reported and which is certainly in no way due to any failure on your part to read or follow the directions.  That's always optional.  If you do not hear from me soon, it is because I have gone face-down onto my keyboard in a state of total exhaustion while locked in this life-or-death struggle to solve your problem and have since required extensive hospitalization or maybe even burial.  In the latter case: no, you may not have my keyboard.

"Can your driver be ported to Windows?  The Microsoft drivers suck and only work on XP SP1, which I do not have and shouldn't have to buy just in order to use a stupid keyboard."  That's a question for Microsoft, not for me.

"Whoa, there's a new Apple Bluetooth keyboard available.  Does it work with bthid?"  You bet.  I bought one of these keyboards and I like it very much.  Not only was it cheaper than the Microsoft keyboard, it seems to be made more solidly and the keys are all the same height, which makes things much less disconcerting after I've rearranged them into Dvorak's layout, and most important, it has an on/off switch!  It took some time to get it connected, primarily because the keyboard really does go through the Bluetooth pairing process with a PIN and all.  To get it to work, make sure that your hcid is configured to cough up a PIN on demand, then power up the Apple keyboard, then scan with hcitool scan.  Now fire up bthid on the keyboard's bdaddr with the options "-a -e -v", and type in your PIN on the Apple keyboard and hit return.  Use the authentication (-a) and encryption (-e) options on later connections too.  I'm typing this paragraph as a test of my first successful use of the Apple keyboard, and I'll add more information here later if anything comes up.  I may have to tweak bthid to get the keyboard's CAPS LOCK light to light up.  For now, though, the short story is that I think that I'm going to start recommending the Apple keyboard over the Microsoft one.  (But not their boneheaded one-buttoned mouse!  I've gotten way too used to the wonderful set of five buttons and the scroll wheel on the Microsoft device.  How did I ever get anything done without a scroll wheel, I wonder? :-)  And yes, I'm using the Apple keyboard with Microsoft's Bluetooth transceiver.  Could this keyboard be used on Windows systems as a replacement for the Microsoft Bluetooth keyboard?  Maybe, but you'd probably still have to buy the Microsoft keyboard just to get their software in a legal manner.  Oh well, I'm rambling, but at least this keyboard is still working.

"What about that flexible roll-up Bluetooth keyboard from Korea?" I still haven't seen one of these for sale on line or in real life, sorry.  Looks like a fun idea and I'd like to play with one.  (As an aside, I have received tantalizing bits of e-mail from people working on other top-secret Bluetooth keyboard devices to let me know that bthid works great with them.  I wish they'd send me the keyboards to try out myself, but they'd probably want a Cray X1 in exchange :-)

"What about Linux 2.6?"  I'm running 2.6.0 today on my laptop and have ported bthid forward.  The new uinput driver in 2.6 obviates the need for bthid's old fake.o driver.  It runs great.