2009-03-31

Using com.sun.net.httpserver

One of the things I needed for mp3d was an HTTP server. Sun added one to JDK6 – com.sun.net.httpserver – but the documentation's a bit sparse, there's no example code (and the snippet in the package description doesn't compile), and judging by the results of my web searches, it's not widely used.

Despite these obstacles, it was pretty easy to do everything I needed. Here are a few notes from my experiences.

Getting query parameters

If you're using HTML forms with the GET method, you'll need access to your query parameters. The obvious way to get them would be to call HttpExchange.getRequestURI and then call URI.getQuery on the result. Unfortunately, that's broken: URI's built-in decoder only copes with %xx escapes, and doesn't know that '+' needs to be translated to ' '. Bizarre.

What you need to do is use URLDecoder on the result of URI.getRawQuery instead.

(Either way you need to break the string into key-value pairs yourself.)

Getting form data

If, on the other hand, your HTML forms use the POST method, you'll need access to your form data. Here the obvious scheme works fine: just read the input stream. Again, I'm surprised there's no convenience method to help with this common task.

Setting headers in the HTTP response

If you want to set an HTTP header in your response, use the HttpExchange.getResponseHeaders method. That gives you a Headers object which is just a map. You can use Headers.put as if it were a plain old HashMap or whatever, but I preferred to be explicit and use Headers.add or Headers.set to make my exact intention clear.

(Getting the response headers object and calling a setter on that is pretty obvious when you know, but it took me longer to find than it should have done, in part because I wasn't sure the functionality even existed, but mainly because I was looking to start my journey with a setter, not a getter.)

Exception handling

Exceptions not caught by your HttpHandler implementations are reported, but in very spartan fashion, without stack traces. There's also no way to set an uncaught exception handler (as there is with AWT, say). You'll probably want to write your own code to deal with this. An obvious possibility is to write your own abstract class implementing HttpHandler with a "handle" method that catches and reports anything thrown by your class' new abstract method that does the actual handling of the request.

HTTP response codes

If you want constants for HTTP response codes so your code's not full of magic numbers such as 404 and 307, look at HttpURLConnection (from the standard java.net package). Then you can use, for example, the more obvious HttpURLConnection.HTTP_NOT_FOUND instead of 404.

GZIP content encoding

There's no built-in support for the GZIP content encoding, but it's pretty easy to do it yourself. Given an HttpExchange 't':

t.getResponseHeaders().set("Content-Encoding", "gzip");
t.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
final GZIPOutputStream os = new GZIPOutputStream(t.getResponseBody());
os.write(...);
os.finish();
t.close();

You might want to check the request headers to ensure that the client can cope with this encoding, but I couldn't be bothered. As the Zen master said, a bug's not a bug until it inconveniences somebody.

How does HttpServer choose the appropriate HttpContext?

When you set up your HttpServer, you add "contexts", which are basically associations between a path and the HttpHandler that corresponds to that path. You can just have one that parses the path itself, if you like. The current implementation is an unsorted linked list, but that's fine for the kinds of things you'd sensibly use this code for.

Is it kosher?

You'll have noticed that the HTTP server packages are com.sun.* packages. Although they're mentioned in the Networking Features part of the JDK6 release notes, they're not indexed in the Java Platform API Specification.

The rules for com.sun.* packages aren't mentioned in the JDK6 documentation's Note About sun.* Packages.

As I understand it, com.sun.* differs from sun.* in that Sun supports the former but not the latter. In neither case are you guaranteed that it'll be available in other implementations. (For the record, Apple's Mac OS 10.5 JDK6 does include the com.sun.net.httpserver packages.)

Past experience suggests that a com.sun.* API in one JDK release won't necessarily be in the next, so you might not want to base your business on this API. But for a weekend hack that would be equally fine with an 80-line HTTP server using little more than a ServerSocket, it's a handy jump start.

Sample code?

Even I have better things to do than write you a nice little tutorial, but the mp3d source currently contains working examples of all the above.

I'm pretty sure I spent longer writing this post than I did working any of this stuff out or writing the code.

2009-03-27

Why do I keep writing C++?

When you think of Unix daemons, you probably think of C or C++. When you think of cobbling together a program from a handful of libraries on Linux, you probably think of all the C and C++ libraries just a dependency away.

This, at least, was how I ended up writing the very first version of mp3d in C++. And it was a huge mistake. Throwing away two days of work and starting again in Java was the best move I made.

Aren't all daemons written in C/C++?

Tradition is a state, not a reason. Just because the daemons we grew up with (or wrote) years ago were in C doesn't imply that C was particularly suited to writing daemons, or that we should use it to write any more.

I think part of the problem here was that I could pretty much see the boilerplate, ready to flow out of my fingers. And that boilerplate was in C++. The opposite would have been the case if I'd been thinking of a GUI application. It wouldn't have even crossed my mind to consider C++ for one minute. The relevant boilerplate would have been ready, and this time it would have been in Java.

In the server case, such "thinking" is short-sighted. If the boilerplate is the hard part of what you're about to do, what you're about to do probably isn't worth doing at all.

But all those great libraries...

I think what really convinced me, though, was the idea that I'd be able to grab one of any number of HTTP libraries and id3v2 tag libraries if I chose C++.

Never mistake quantity for quality.

The HTTP library wasn't much of a problem, though the selection was much more limited than I'd imagined. The least unpromising choices were very much C libraries, with all the ugliness that that entails. Manual memory management everywhere, and no notion of stream or string. Three fairly fundamental things that even C++ programmers take for granted. I won't even waste breath on my chosen library's lack of const-correctness.

The id3v2 tag library situation was far worse. The situation there appears to be that there are several alternatives, but none of them is actively maintained. As far as I can tell those applications that manage to work with real-world id3v2 tags without crashing are using their own forked/patched versions of these libraries. And even though you can get a C++ (rather than C) library for handling id3v2 tags, it's quite the runniest kind of shite. It's the kind of C++ you see written by people who don't show any sign of understanding why C++ is better than C. ("Is it the // comments, perhaps?")

Don't expect to see any use of the standard library, not even std::string.

And even if they had used C++ to its fullest, you soon run up against the edges of that "fullness".

You want to cope half-way sensibly with non-ASCII text? You'd better learn and use ICU4C then. (Sure, C++ gets along okay-ish on its own in an environment where its input is UTF-8 and it doesn't actually have to process text so much as just pass it around. But as soon as you need to either deal with encodings other than UTF-8 or need to actually process the text, you need ICU4C.)

Or maybe you'd like threads? Or maybe you need concurrent collections? Perl regular expressions?

I can't remember the last program I wrote that didn't need all of these things.

I'm not saying that all Free Java libraries are great. Of course they're not. 90% of everything is crud. But you don't need nearly as many third-party libraries because what you get "built in" is mostly pretty good, and certainly covers a lot of ground. And it's much easier to write a decent Java library than a decent C/C++ library. I've yet to see a Java library invent its own string class, for example. And there's much less effort expended on questions of ownership (though there are certainly traps for the unwary, should they hand out mutable objects they intend to continue using).

Why do you keep fooling yourself?

I fear C and C++ fall into the same class as awkward editors. Why do people use emacs or vi rather than a proper editor? Why do people make scale models of intricate buildings using only matchsticks? Sometimes the very fact that a thing is hard is what makes it fun. (Actress, bishop.)

I evidently don't know how to stop myself falling into this trap, but maybe it will help I come out in public more often and confess. Along the lines of "hi, I'm Elliott, and I wasted a couple of days trying to start my project in C++, fighting an awkward language and a stunted standard library and several really badly-built libraries, and in the end I realized I had nothing to show for these two days, came to my senses, started again in Java, and after just a couple of hours I had something I could actually use".

It's not like using sensible tools takes all your fun away; it's just a way of trading a puerile unproductive kind of fun for the fun of doing a good job of something useful, in a shorter time than the job would otherwise have taken. Trading short-term gratification for long-term satisfaction.

I guess I'll be fighting that part of human nature for a long time.

2009-03-26

mp3d

The few lines of code in last post, Registering a service with DNS-SD from Java were lifted directly from one of the things I'm working on at the moment. As you could tell from the service name, this program is called mp3d. If you know what an mp3 is, and you know that Unix tends to name daemons something ending in 'd', you've probably got the picture already.

All I wanted was a little web server that would let me play my music from any computer, with the sound coming from the one computer that has decent speakers.

At this point, you may well be muttering "reinventing the wheel again, dude?". And you'd be forgiven for thinking so. I certainly assumed that the internet must be positively dripping with examples of the program I was looking for. Indeed, I spent as long searching the web for such a thing as it actually took me to cobble together the first version when I finally gave up looking.

(If you ignore my unfortunate detour into the world of C++ that I may talk about some other time. For now though, let's pretend I was a sensible boy and chose Java from the outset.)

Distraction: RhythmWeb

The first candidate I found on the web was a plugin for RhythmBox, which sounded ideal. I was at that time using RhythmBox as my mp3 player, and although RhythmBox isn't great, I could have lived with a web interface to it.

Unfortunately, RhythmBox and RhythmWeb didn't get on very well with each other. I'd hoped for some kind of unified state, but they seemed to be fighting amongst themselves and keeping track of their combined state (and thus what my actions would do) was pretty confusing.

I could have stopped using RhythmBox locally and used the web browser even on the box with the speakers, but there was a worse problem: RhythmWeb was slow. So slow it was almost impressive. I don't have a large mp3 collection – less than 5000 tracks – but RhythmWeb couldn't cope with that collection. Not in any reasonable time frame, anyway. Not even when only asked to display a few matches. Python: all the elegance of a silly walk, and all the speed of a dead parrot.

RhythmWeb did look quite nice, though, in a minimalist kind of way. I've tried to keep something of that style in mp3d.

Distraction: mpd

The only other candidate I came across that seemed close to what I was looking for was mpd. But that turned out to not be quite what I expected. My idea was basically "a web server that lets me play my music". What I found in mpd was more "a server that would play my music if I ran an appropriate client to make the necessary RPC calls".

There followed an interlude where I looked at a bunch of clients before eventually giving up and deciding to write my server.

Frankenstein's Mp3 Player: mp3d

I started off with directory-indexing code copied & pasted from one of my other projects. I started with the id3v2 tag parser recommended by Jesse Wilson in his roundup (though I eventually wrote my own, which I may talk about another time). I used Sun's com.sun.net.httpserver.HttpServer which was a blessed relief after suffering libmicrohttpd in the C++ version. I remembered just about enough HTML to knock together a TABLE. I copied RhythmWeb's CSS. I spawned mplayer(1) to actually play the mp3s. That was the first version, done in a couple of hours.

The code's all in code.google.com/p/enh/, home to projects that are still in early states (and dumping ground for dead projects). mp3d's been through a few changes since the first version, and it's got a way to go still, but it's already the only mp3 player used on any of the computers at home. You're welcome to try it if you're interested, but I'd no intention of packaging it in the near future, so you'll have to build from source.

I bring it up mainly as an introduction to the next few posts, which will cover various things that cropped up while working on mp3d.

2009-03-24

Registering a service with DNS-SD from Java

DNS-based service discovery (DNS-SD, but better known as Rendezvous, Bonjour, or ZeroConf) is a great way for services to announce themselves, and for potential clients to see what services are available. Mac OS has mDNSResponder, Linux has Avahi, and Windows – as far as I know – has its own competing alternatives.

I've played with this stuff a few times in the past, but never really needed it. Recently though, I wrote a small web service that will mainly be used from Mac web browsers on the same network. What's the easiest way of finding the server? Probably the fact that registered _http._tcp services automatically appear in both Camino and Safari's bookmarks menus. Unfortunately, Firefox does not support anything like this, even on Linux. Nor, as far as I'm aware, does Chrome. (Epiphany does, though, if you're looking for a Linux browser with DNS-SD support.)

So. What's the easiest way to get a Java HTTP service registered?

avahi-publish(1)

Since my server is running on Linux, and I have the avahi-utils package installed, avahi-publish(1) is a convenient choice. Given my ProcessUtilities class, it's just one extra line to spawn a child that publishes news of my service. And, conveniently, that child dies when I die, so I don't need to do anything special about deregistering. Normal process semantics take care of things well enough.

Here's the single line it took to get my service (called "mp3d") registered:

ProcessUtilities.spawn(null,
    "/usr/bin/avahi-publish", "-f",
        "-s", "mp3d", "_http._tcp", Integer.toString(portNumber));

Pros: trivial to use, from any language.

Cons: Linux-only.

Avahi bindings

I did look at Avahi's bindings, but they don't include Java. And since that wouldn't actually solve the "con" of avahi-publish(1), it's not really obvious that I'd gain anything by going down such a route even if it were open to me.

Pros: none.

Cons: neither useful nor written yet.

mDNSResponder

Apple published mDNSResponder as open source under the APL 2. It supports Mac OS (even the prehistoric pre-Unix variant that it's believed the cavemen used), Windows, and "POSIX platforms". This does come with Java bindings, but they're just that: bindings to the native code using JNI.

This would provide portability, but by this time I'd realized that ideally, I wanted a pure Java solution.

Pros: somewhat portable.

Cons: awkward JNI dependency, recompilation requirements.

waiter

The waiter project is very proud about being written using Test Driven Development and what kind of mocks it uses, but not terribly interested in telling you how to use it. Or what state of development it's at. The code looked a bit odd, too. More like a Java-hater's parody of Java than anything.

Pros: none.

Cons: no documentation or sample code, weird attitude, not obviously alive.

mahalo-mdns

The mahalo-mnds mahalo-mdns project was a fork of JmDNS. A fork on which apparently nothing of substance happened.

Pros: none.

Cons: dead.

JiveDNS

The JiveDNS project was a fork of JmDNS. A fork that apparently didn't get as far as actually committing any code.

Pros: none.

Cons: dead.

JmDNS

So what about the project everyone else seems to fork but then not do anything with? This appears to be the best of the bunch, though it's not without problems.

I tried version 2.1, and the API looked reasonable. More Java-like than the others, certainly. The documentation seems to think the API uses constructors, but the code itself has changed. Luckily, the example programs it ships with have been kept up to date.

I noticed that the project's site has bug reports about deadlock and dodgy use of JVM shutdown hooks going back years. Given that the second of their example programs did indeed deadlock in a shutdown hook, leaving me having to kill -9 the JVM after suspending it, I'm ready to believe those reports.

Anyway, here's the single line I needed after dropping the jmDNS JAR file into my project's lib/jars/ directory:

JmDNS.create().registerService(ServiceInfo.create("_http._tcp.local.", "mp3d",
                                                  portNumber, ""));

And sure enough, if you add something like that to your program, you'll find your program too will deadlock if interrupted. Suboptimal.

Under other circumstances, I'd suggest jmDNS was ripe for a forking, but that doesn't seem to have worked in the past!

Pros: portable.

Cons: deadlock-prone, under-maintained.

Conclusion

So what am I going to use? avahi-publish(1). I was only expecting to run on Linux anyway, so I'll worry about portability when I come to need it.

But... it turns out that I can't use DNS-SD for what I originally wanted it for. My plan was to switch from a hard-coded port number for my HTTP server (8888, just like we used to use in the good old days) to just accepting whatever ephemeral port I got. I thought that if I announce my service using DNS-SD it wouldn't matter what port I was on.

To a certain extent that's true. The missus doesn't need to know the port number if she uses the Bonjour bookmark to get there. But if she wants to get there via the history and the server's restarted, she's screwed. Likewise if the server restarts while she has a browser window open, when she comes back to that window, it's "broken". So it turns out I can't really get away with accepting an ephemeral port after all. This seems like a real pity. Maybe one day we'll be able to use DNS-SD service names as virtual port names, as in http://lithium.local.:mp3d./ say. But not today.

So it's back to the four fat ladies of my hard-coded port number, but I'll leave the DNS-SD registration in anyway. I'm used to it now, and it'll be a comfort in my old age, when my memory's completely gone.

2009-03-17

How to fix dnsmasq after an Ubuntu upgrade

I mentioned before in Desktop Linux suckage: DNS caching that I use dnsmasq as a local DNS cache to work around lame ISP DNS, because Linux doesn't tolerate lame DNS as well as Mac OS will. (I never even noticed I had a problem until I switched from Mac OS to Linux.)

Using dnsmasq as a local DNS cache worked well, and I had no trouble until I upgraded to Ubuntu 8.10. Here, for my own reference next time I upgrade, is what I needed to do to fix things. (I'm assuming that this breakage will happen on every upgrade, because it would be rather sad if literally the only difference I noticed between 8.04 and 8.10 is that my local DNS cache stopped working.)

The symptom was that "nameserver 127.0.0.1" was missing from /etc/resolv.conf. Hasty optimistic fool that I was, I manually reinserted the missing line, and all was well... for several hours, when my DHCP lease was renewed. At which point it appears dhcp3-client rewrote /etc/resolv.conf for me.

(I really wish the perpetrator had had the decency to write a comment in /etc/resolv.conf saying what was going on. It might also have been nice to make the file read-only, as an added hint that humans shouldn't waste their time editing the file.)

So the real fix, it seems, is to edit /etc/dhcp3/dhclient.conf and uncomment this line:

prepend domain-name-servers 127.0.0.1;

I thought the daemon (dhclient3) might watch its configuration file for changes, but it didn't appear to. The daemon also appears to die if you send it SIGHUP, a signal those daemons too stupid to notice changes to their configuration files used to take as an indication that they should re-read their configuration.

Anyway, this – though perhaps more forceful than necessary – did the trick and saved me from wasting any more time on this nonsense:

sudo /etc/init.d/networking restart

Yeah, I know: "here's a nickel, kid".

2009-03-07

Apple Keyboard first impressions

This time, it's war

The box containing my "old" Apple Keyboard with Numeric Keypad had a sticker on the side that said something to the effect of "this keyboard requires Mac OS 10.4.7 or later". Apart from the fact I happened to be running a buggy Linux kernel at the time (thanks, Ubuntu), it actually worked fine on my Linux boxes. It worked fine on my Macs, too, though they were running suitable versions of Mac OS.

Anyway, the new Apple Keyboard (MB869LL/A), the one without the numeric keypad, its box says something like "blah blah Mac OS 10.5.6 or later". (I'd quote exactly, but the poltergeist that throws my stuff out while leaving its own stuff lying around appears to have paid another visit while I was at work yesterday.)

I scoffed when I saw this, but it turns out it's actually true.

Mac OS 10.4 compatibility

On Mac OS 10.4.11, the function keys' functions don't match their glyphs. If you have fond memories of the good old days when it was F12 to activate Dashboard, you'll love this keyboard. Despite the Dashboard glyph being on F4 and F12 having the increase volume glyph, F12 activates Dashboard.

Also, despite the fact that this keyboard looks exactly like a MacBook keyboard, unlike the MacBook, you can't use fn and the arrow keys to get home/end and page up/down. Presumably you can if you're running 10.5, but those of us planning on going straight to 10.6 are out of luck.

Linux compatibility

It should go without saying that if Mac OS 10.4 users are out of luck, Linux users are screwed, but none of Ubuntu 6.06, 8.04, or 8.10 have any clue what to do with this keyboard. I've mailed USB id updates to the guy who maintains the list (so at least lsusb(1) will show what you've got connected), but I had the strangest sense of deja vu as I was doing so, and a strong feeling that my previous submissions never made it into the list.

Windows compatibility

I don't have a Windows machine, but I see no reason to assume things are any different there. The box certainly didn't say anything about Windows compatibility. This probably presents quite a problem for Windows users. I can suffer without home/end and page up/down, as I used to on my sort-of-Happy Hacking keyboard, but as I understand it, Windows users can't even log in without a delete key. And though Mac keyboards say delete, they mean backspace, and if you really want delete, you need a working fn key.

So if you're a Windows user who likes Apple's keyboards, I'd be really cautious about this particular one, though the Apple Keyboard with Numeric Keypad had two delete keys, one of which actually was delete, so you'd probably be fine there. I certainly used to use it on Linux, judging by the number of times I've hit the desk where that key ought to be over the past couple of days.

First impressions

Having the mouse closer to hand is great. That feels good. The downside is that I'm actually having to use the mouse more because I'm without the editing keys.

I'm coping with the scrunched-up arrow keys, though I still have to look for them, and sometimes hit the wrong one, even with my rather slender fingers.

I'm not missing home and end much, though I'd rather I still had them. I'm missing page up/down more, though I can cope. The surprise winner of most-missed-key is actually delete (aka forward delete, as opposed to backspace). I'd really like a working fn key, and can't really recommend this keyboard to anyone not running Mac OS 10.5 right now, because that seems to be the only OS with the right magic. I don't know enough about USB keyboards to understand the problem here, but it seems quite an unfortunate situation. Surely pseudo-modifiers like fn should be handled in the keyboard itself, and some kind of uniform key code (plus uniform set of modifiers) sent to the computer? Surely requiring each OS to understand each keyboard's layout (and its international variants) is something from the dark ages?

I wasn't expecting the fn key to be where it is. Despite the fact that it's in exactly the same place on my MacBook Pro. I guess I just don't really use my MacBook Pro enough to have noticed or cared; it's a glorified web tablet, really. A heavy battery-hungry one at that.

Anyway, the trouble with the fn key's position isn't where it is so much as that it moves the control key across one place. Which moves the alt key across one place. So if you've ever experienced modifier-key confusion, expect to experience it even worse now. On the other hand (the other metaphorical hand, that is), the control key's new position actually makes it possible for me to type control-shift combinations without ridiculous discomfort. Previously I'd have to bend my left thumb so far back to reach the right edge of the control key that it literally hurt. The combination of a more accessible control key and an out-of-place alt key might actually get me to switch to using control-shift instead of alt in Linux terminal emulators.

Assuming I stick with the new keyboards. I'm left more than ever wishing for the "Apple Keyboard without Numeric Keypad but with all those Useful Keys that come between Keyboard and Numeric Keypad". If I had a working fn key, that would be one thing. But genuinely losing my home/end and page up/down keys is harsh.

And I don't even know whether I need a new kernel, X server, xkb configuration, or some GNOME thing to fix my problem. Too many layers of bureaucracy fighting for control of a triviality.

2009-03-06

Apple Keyboard unboxing

So my 3 Apple Keyboards arrived today. These are small keyboards. Put one next to a laptop, and it looks small. Put it next to a full-size keyboard, and it looks tiny.

Each keyboard comes in a box 8.5cm by 32cm by 13.5cm. I ordered three of these, and their three boxes came inside another box. This other box was 31cm by 32cm by 56cm. So the keyboards took up 7% of the space inside this enormous box.

Or, alternatively, I could have ordered 30 keyboards and have them arrive in the same box.

How did they not rattle about? The rest of the box was filled with a paper snake, like the ones you see Chinese people dancing about in during Jackie Chan movies. To be precise, one 1080cm by 76cm sheet of brown paper, scrunched into a tube, and then bent back on itself many times to fit inside the box. I pulled out so much paper that I feared the box wouldn't actually contain my keyboards.

To get an idea of the size of this paper snake, imagine you pull your arm out of your sleeve and let the sleeve hang like Nelson (the Vice-Admiral, not the Simpsons character). Then stuff your sleeve with paper, so it's as full as if it still contained a rather chubby arm. Then extend your sleeve and arm until they're eleven meters long. That's the length of six men standing on each other's heads. That's long enough to reach to the bottom of an olympic diving pool and back to the surface again. That's about the length of a whale penis.

By anyone's standards, that's pretty big.

2009-03-04

Apple Keyboard

I've complained about the numeric keypad for well over a decade now, and it's been a particularly frustrating decade. When I have a software problem, I can either decide I don't really care all that much, or get pissed off enough to actually fix it myself. With hardware problems, I just have to take it.

Most recently, just last year, I complained that Apple's current metal keyboards only offered two choices: wireless without numeric keypad, or wired with. Hater of batteries and numeric keypads that I am, I wanted wired without.

Today, Apple quietly added that missing keyboard to their lineup.

Even more interestingly, they changed the name of the old wired keyboard to "Apple Keyboard with Numeric Keypad". The old wireless keyboard with no numeric keypad remains the "Apple Wireless Keyboard". The new wired keyboard with no numeric keypad is the "Apple Keyboard".

This is it, friends. This is the beginning of the end of the accountants' reign of numeric keypad tyranny!

Actually, the beginning was probably the generation for whom computers were mostly laptops, and desktops were either their parents' computers, or the boring computers work makes them use.

But what's important is that "with numeric keypad" is now an exception worth pointing out in your product description. At least for Apple. But even Dell eventually gives up on parallel ports and floppy drives and VGA and whatever. (I mention Dell only because the last computer I saw with a floppy drive happened to be a Dell. Really I mean "all the computer companies that just drift on the tide". At least Dell never made a transparent computer; I'll give them credit for ignoring that bizarre fashion.)

Some of you will remember that what I actually wanted was the full keyboard without the numeric keypad. That is, I wanted to keep the "editing pad" (or whatever you call the bit between the main part of the keyboard and the vestigial numeric keypad). Maybe I'll learn to live with cramped-up arrow keys and no separate page or home/end keys. I lived similarly before, when I had a Happy Hacking keyboard that didn't have arrow keys at all, so the question is really whether I'll learn to be happy living like this.

Whatever, my mouse will relish the extra desk space, and I'll not have to bother with batteries or Bluetooth.

I'll take three, please, Steve.