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.
- Keypress to scan code
within Bluetooth keyboard
- Scan code to HID report
- HID report travels down
Bluetooth protocol stack in keyboard to its transceiver and out over
the
airwaves
- Packet is received by
USB Bluetooth HCI transceiver dongle and goes over USB to BlueZ
protocol
stack
- From the BlueZ stack's
L2CAP layer the packet arrives in the daemon's socket and the daemon
awakens
- Daemon translates the
10-byte input report interrupt packet into Linux input keycode events
and writes them to char special file
- My little driver's
write() routine hands these events one by one to the Linux input module
- 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.
- 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.
- The X server receives
the scan codes and maps them to key codes and modifiers (shift,
control,
and so on).
- Then it maps those key
codes to symbols. I use the Dvorak keyboard layout, so I've
configured X accordingly.
- 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.
- 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.