Look mom, no ports!

I'm currently updating a commercial HTML-based desktop application that I wrote a few years ago. Customers keep having permissions trouble on Windows with the back-end opening a TCP port for serving front-end's HTTP requests. I decided to try to refresh both the back-end (to Jetty 7.3.0) and the front-end (to WebKit running in Java via Qt Jambi) and run them in one process with no TCP ports involved. Both components have reputations for being easy to embed, so I thought it shouldn't be too hard. Yeah right...

Actually, things went very smoothly at first. Here's what I discovered:

  • Jetty accepts requests via Connectors; one of them is LocalConnector.
  • Obviously, setting up Jetty with LocalConnector is beyond the scope of this post :-)
  • LocalConnector.getResponses() accepts a String argument containing an HTTP request just as you would type it into a telnet window (like "GET /whatever HTTP/1.0" followed by two newlines) and returns an HTTP response, also as a String ("HTTP/200 OK blah blah..."). It's a primitive interface but it does work.
  • The method mentioned above doesn't work very well if you want to GET binary data such as images (yeah, you could use "Content-Encoding: base64" but come on...). There is, fortunately, a variant of LocalConnector.getResponses() that accepts a ByteArrayBuffer along with a boolean "keepOpen" argument which I simply set to false. The method otherwise works just like the String variant.

As for a WebKit front-end, once you have Qt Jambi set up properly (with -Djava.library.path etc.) it's really easy to create a QWebView, point it at a URL and splash it all over your display. To prevent HTTP traffic, however, I needed to intercept requests coming from WebKit and route them to my LocalConnector:

  • The thing to do is QWebView.page().setNetworkAccessManager(magic), where "magic" is a subclass of QNetworkAccessManager that overrides createRequest() to do the necessary, well, magic.
  • Despite superficial appearances, createRequest() actually accepts QNetworkRequest as a parameter and returns a QNetworkReply. Go figure. Additional parameters for createRequest() include an Operation enum to distinguish GETs from POSTs, and a QIODevice (think InputStream) containing the data of a POST request.
  • Since QNetworkReply is also a stream-like QIODevice, returning custom content from createRequest() involves writing a QNetworkReply subclass that knows where to look for when clients call its read*() methods. This is not quite easy to do.

Anyway. After a few hours I had a web application running in a WebKit window with not a TCP packet in sight.The trouble came when I tried to process a POST request (yes, the application does involve filling out forms). What followed was a bout of frustration, head-scratching and just plain unhappiness that I don't wish upon anyone:

  • First, you have to realize that Qt Jambi (including its WebKit component) is actually a C++ beast in sheep-like Java clothing. No matter how sweet the API is, the implementation is a bitch to debug even if, technically, you have access to the source code.
  • What happened is that my createRequest() was called with a PostOperation as it should be but the accompanying QIODevice was empty. More specifically, calling canReadLine() on it returned false.
  • Setting a breakpoint and examining the QIODevice's internals didn't show anything interesting (it was basically a proxy object for a C++ implementation).
  • I resorted to the oldest tool in a programmer's toolbox: trace statements. Here's what they gave me:
    • formData.isOpen(): true
    • formData.isReadable(): true
    • formData.isSequential(): true
    • formData.atEnd(): true
    • formData.bytesAvailable(): 0
    • formData.size(): 0
    • formData.pos(): 0

I tried routing regular HTTP post requests through the parent class and it worked even though the request objects looked exactly the same and returned exactly the same trace results! I was getting ready to give my own QNetworkRequest to the parent implementation that would trace all calls made to it but I tried getByte() and... there was data! WTF?! Point: QNetworkRequest was in a weird uninitialized state where it thought it was empty when it really wasn't. It snapped out of it when prodded for data.

Another problem surfaced when I tried to get a Dojo Tree widget going. During initialization, Dojo loads its modules via AJAX and it was failing with the dreaded "NETWORK_ERR: XmlHttpRequest Exception 101". Cost me a lot of blind paths - I tried setting the .js files in different paths relative to the referring .jsp, reading compressed Dojo sources, diagnosing with window.alert() etc. In the end, I understood that WebKit was having some problem with the QNetworkReply I was producing. I made my Content-Length calculation more robust - no change. Finally, I looked at QNetworkReply objects produced by the regular QNetworkAccessManager and made sure all the attributes it fills out were also filled out in my replies. That helped. I know, I should have done it at the start but why did it matter with AJAX requests and not with regular ones? Oh well...

The AJAX issue turned out to be the last major obstacle. My application now runs without opening any TCP ports and both WebKit and Jetty proved reasonably embeddable. I might use this set-up in other contexts as well.


Soundgraph iMON PAD vs. Linux 2.6.36

I use a Soundgraph iMON PAD remote control to command my home-theater PC. The remote stopped working when I upgraded my kernel from 2.6.33 to 2.6.36, due to major infrastructure changes that started in 2.6.35. At first I simply reverted to the older kernel but this week I had a few spare hours to figure out what was going on. In short, the iMON driver has been cleaned up by Jarod Wilson and included in the main kernel code base (finally!). Its logic was also standardized to route its output to the Linux input layer rather than to the LIRC daemon. This obviously requires some re-configuration of the daemon, as Jarod explained on the LIRC mailing list.

On my HTPC I had to put the following settings into /etc/lirc/hardware.conf:

DRIVER="devinput"
DEVICE="/dev/input/by-id/usb-15c2_ffdc-event-mouse"

I also had to adjust key codes in /etc/lirc/lircd.conf (note that I only need only six keys for my remote-control software, Offhand; others should simply use Jarod's file):

begin codes
    KEY_BACKSPACE            0x000E
    KEY_COMPOSE              0x007F
    KEY_CONTEXT_MENU         0x01B6
    KEY_ENTER                0x001C
    KEY_KEYBOARD             0x0176
    KEY_SPACE                0x0039
end codes

Finally, I had to adjust the .lircrc file used by Offhand:

begin
      prog = offhand
      button = KEY_CONTEXT_MENU
      config = back
    end
begin
      prog = offhand
      button = KEY_COMPOSE
      config = forward
    end
begin
      prog = offhand
      button = KEY_KEYBOARD
      config = up
      repeat = 1
    end
begin
      prog = offhand
      button = KEY_ENTER
      config = down
      repeat = 1
    end
begin
      prog = offhand
      button = KEY_SPACE
      config = ok
    end
begin
      prog = offhand
      button = KEY_BACKSPACE
      config = cancel
    end

Separately, I also had to configure the iMON driver to forget about its mouse emulation mode. I did it by creating the file /etc/modprobe.d/imon.conf containing

options imon nomouse=1

Logitech diNovo Edge vs. aptosid

If you have no idea what a Logitech diNovo Edge could be, think about it for a while. You should quickly reach the obvious conclusion: it's a super-thin wireless keyboard featuring a built-in mouse pad. I happen to use one with my home-theater PC. It's a classy product, feels solid (even a bit luxurious) and works very well - that is, it used to work until I replaced the HTPC's ancient Kubuntu system with aptosid. Ever since then, I could use the keyboard in the bootloader but not once the system was up.

aptosid, formerly known as sidux, is a running-release distribution of GNU/Linux descending from Debian Sid, the so-called "unstable" flavor of Debian. Being running-release means that instead of periodic releases of the entire environment, I get a constant trickle of new package versions as they are admitted into repositories. The upside is always being up-to-date. The downside is a not quite thoroughly tested environment where things occasionally stop working.

The team managing aptosid does a pretty impressive job keeping the system stable. I'd been running it on my main notebook for a while and breakage had been pleasantly rare. I couldn't really fault the system for refusing to handle a relatively obscure piece of hardware. Given that I mostly run the HTPC with a remote and I manage the machine over the network, I had little need for the keyboard. Playing DVDs was the only scenario that was truly annoying. With a bit of time over the holidays, I decided to tackle it.

I connected the keyboard to my notebook - having another, 100% reliable keyboard on the same computer made the investigation much smoother. I quickly foud out that the keyboard would work the first time its receiver dongle was plugged in, but after dis- and re-connecting it would behave identically as on the HTPC. A telling difference was the presence of an extra USB device: upon first connect, lsusb would list three new devices while after re-connecting it displayed four.

Googling the incriminated USB IDs produced the answer: udev is configured in Sid to run hid2hci when the Edge is connected, making it a fully capable Bluetooth device rather than just a dumb USB keyboard. Except what I want is just a dumb USB keyboard.

I tried adjusting udev rules on the HTPC to prevent hid2hci from running but no matter what I tried, it still somehow managed to launch itself during the boot sequence. I ended up replacing /usr/sbin/hid2hci with a shell script looking like this:

echo "pretending to run hid2hci"

Ever since then, I've been navigating DVD menus with much more flair. The keyboard suddenly seems brand new now that it works! Dead keyboard wasn't my HTPC's only problem, however. The story is set to continue...


Akai EWI USB and Linux, part 3

In part 1 and part 2 of my Akai odyssey, I've described getting the EWI to produce sound and configuring fluidsynth (plus QSynth) for optimum performance without latency. I was happy with my setup but after a while I started craving a little convenience.

Setting up a playing session is a silly affair involving the instrument itself, my notebook, a pair of headphones, 2 cables and 4 connectors. The same goes for winding things down after playing. It used to be even more obnoxious since I had to manually start and exit the QSynth software. I knew that Linux can react to USB hot-plug events and this reaction can be configured; so I decided to make QSynth start and stop automagically.

The system-du-jour for reacting to hotplug events in Linux is currently udev. Debian's wiki happens to have a very nice udev page which explains that all one needs to do is to put a line in a file under /etc/udev/rules.d. One can either use one of the existing files under that directory or create a new one, which is what I did just to keep things tidy.

Entries in udev rule files rely on an IF-THEN syntax where the IF part indicates in what circumstances the rule should trigger and THEN stipulates what should be done. The syntax is quite restrictive in that each rule has to fit on a line - i.e. no fancy procedural tricks. The IF part offers a lot of ways to pick up an event, though. One can specify the BUS on which the event happens, the NAME of the device, the ACTION that the event represents, expected environment variable values etc. I found the options quite extensive, making it easy to specify any event.

Having to run sudo udevadm control --reload-rules after each rule update was a bit annoying but what really held me up was figuring out which events the rule should react to. I found out that plugging the EWI into and out of the USB port caused entire cascades of events with different characteristics. I eventually settled on the following rules:

ATTR{idVendor}=="09e8", ATTR{idProduct}=="006d", ACTION=="add", RUN+="/usr/local/bin/ewi"
ENV{DEVNAME}=="/dev/snd/midiC1D0", ACTION=="remove" RUN+="/usr/local/bin/ewi"

The first rule fires when I plug the device in, the second one upon unplugging. Both rules launch the same shell script when invoked. The shell script itself looks like this:

if [ $ACTION = "add" ] ; then
 set -x 
 xhost local:username
 export DISPLAY=:0.0
 su username -c qsynth &
elif [ $ACTION = "remove" ] ; then
 killall qsynth
fi

The "add" section contains incantations needed to properly launch a GUI program from a shell script. I picked them up in some forum and have unfortunately lost the link. Kudos to the original poster, anyway.

With this bit of scripting, my EWI is the very definition of Plug-and-Play, as well as Stop-Playing-and-Unplug ;-) . All is not perfect, though; one problem remains and it's completely unrelated to udev. Since I use legacy OSS emulation due to latency issues, I cannot run any other sound-producing application while QSynth is active (OSS doesn't support software mixing). I don't mind that just yet but as I get my fingerings in order and advance my playing I will need to resolve it. The saga continues...


Disabling ACPI in OpenBSD 4.8

As I've mentioned, OpenBSD 4.8 can't boot on a HP Mini 5101 netbook due to an apparent ACPI issue. The way to work around the issue is as follows:

  • When booting, enter boot -c at the boot> prompt. A UKC> prompt will appear shortly.
  • Enter disable acpi, then quit; the kernel should proceed to boot successfully to the login prompt.
  • After logging in, enter su (sudo won't work since you haven't edited /etc/sudoers yet).
  • Enter config -e -o bsd.noacpi /bsd; you will see the UKC> prompt again.
  • Enter disable acpi, then quit; you should be back in the shell, with a new file called bsd.noacpi in the current directory.
  • Enter mv /bsd /bsd.acpi && mv bsd.noacpi /bsd to replace the current kernel with the new one.
  • Enter chmod 0644 /bsd to set proper permissions for the new kernel.
  • Enter reboot to test things out; the computer should now boot into the login prompt without any assistance.

What's going on here? UKC is the User Kernel Config, OpenBSD's tool for tweaking a kernel without re-compiling it. As we've seen, it can be used to great effect even during the boot process. What's perhaps even more impressive, UKC can also tweak the currently running kernel without having to reboot. All relevant info can be found by entering man UKC and man config in the OpenBSD shell.


Installing OpenBSD 4.8 on an HP Mini 5101

I used to be baffled by the netbook form factor. I could see no use for a toy notebook nor for an overgrown PDA. One day, as I was reviewing the specs of yet another netbook model somewhere, it hit me: this is a light-duty server with a built-in console. No more searching for a PS/2 keyboard down in the basement! What a concept!

I had been thinking of building a home server for some time so I went out and bought an Acer Aspire One. I put OpenBSD 4.5 on it and it has perfomed beautifully. It's served HTTP, SMTP, POP3 and the Squeezebox streaming protocol, as well as shared a printer and provided a backup destination for other boxes on the network.

My only worry was that it might break down - after all, netbooks are not designed to be turned on 24/7. I decided to buy another one and periodically sync its disk from the "master" machine so that I could just swap them in case of need. Alas, the same model was no longer available, much to my disappointment. I ended up getting a pair of HP Mini 5101s instead.

The main problem with installing an OS on a netbook is the lack of a CD-ROM drive. With the Acer I'd performed a network install using pxeboot but it was not something I would endure again if I could help it. I decided to try installing from a USB stick instead. First I had to create the installation medium, of course. I quickly found a great thread at misc@openbsd.org that basically said one can either copy the install48.iso straight onto the raw USB stick device (under Linux this would be dd if=install48.iso of=/dev/sdb or something) or one can boot the OpenBSD CD-ROM and install onto the USB stick just like onto a hard drive.

People on the thread couldn't agree on which method was better. I tried the simpler dd method first but the USB stick would not boot on the Mini. Funnily enough, it did boot on my ThinkPad (BIOS being the X factor). With a bit of trepidation, I booted the ThinkPad from the OpenBSD CD and installed onto the USB stick, hoping I would not ruin my notebook's hard disk instead. All went well and this time the USB stick did boot on the Mini.

Installation onto the Mini from the USB stick was uneventful. For the first time after years I had the luxury of installing on the entire hard drive, letting the installer do the partitioning for me. I could reach all BSD packages over the network without problems as I had an Ethernet cable plugged into the machine and DHCP worked just fine.

Rebooting after the install ended in a kernel panic, however. Neither bsd.mp nor bsd.sp at the boot prompt seemed to help. Off to Google I was yet again, with "openbsd hp mini 5101". It turned out someone had seen the problem before and they solved it by turning off ACPI at the boot prompt. It helped me as well and I was finally able to log into my new OpenBSD system. I've yet to figure out how to disable ACPI permanently. I also have to see how running without ACPI affects power consumption and temperatures. Other than that, the Mini seems to handle OpenBSD just fine.


Akai EWI USB and Linux, part 2

Part 1 of my Akai saga ended with the EWI producing sound, a disappearing symlink and a shocking latency. This was unacceptable.

To get a flavor of how things should work, I did something I hadn't done in months: boot into Windows. I dutifully installed the software from EWI's companion CD and tried a few tones. The software itself was nice, polished and simple enough, except that the latency was pretty much the same as under Linux! Google said one should install a different audio stack, "winaudio" or something, but I had no such inclination.

I pulled out the big guns instead. I installed 64studio on a spare partition. It's a specialized multimedia-production Linux with pre-configured JACK low-latency audio server etc. and I was certain that it would help. Except it wouldn't talk to my graphics card. Next up was Ubuntu Studio - same purpose, different vendor. It installed beautifully, had tons of pre-installed audio software including JACK and QSynth and... exhibited the same latency problem as my regular aptosid and Windows. It was back to the drawing board.

I knew it wasn't a hardware problem - when I ran fluidsynth in verbose mode it would print MIDI events very swiftly, only the sound was lagging. CPU load was negligible so it wasn't any slowness on my computer's part, either. I tried the only thing I could think of - further tweaking fluidsynth's options, this time on the output (audio) side. Lo and behold, when I chose the legacy OSS output driver the system started reacting like a caffeine-doped squirrel. Sound was coming out almost before I touched the pads. I had reached the Akai EWI nirvana.

The utterly perplexing aspect of this outcome is that I don't even have true OSS on my system; virtually no-one has these days. OSS on my notebook is just a special legacy emulation mode of ALSA, yet it performs better than ALSA proper. Go figure...

I had basically two options to resolve the disappearing symlink from /dev/snd/midiC1D0 to /dev/snd/midiC0D0. I could either automate its creation in a start-up script or I could remove the need for it, i.e. make fluidsynth talk to midiC1D0 as it should. The latter was obviously a cleaner solution so I started pursuing it. Tweaking the command line had been useless before, hence I downloaded the source code (apt-get source fluidsynth) and started looking around the error messages I was getting ("Unknown RawMidi" etc.).

It turned out that when fluidsynth documentation says I should specify a MIDI device, it doesn't really mean "a path to a device file" but rather "an ALSA device ID", at least when alsa_raw is specified as the MIDI driver. ALSA device IDs have a special syntax that looks like "hw0,0", with the first number identifying a sound card and the second one determining a feature of that sound card one wants to work with. I tried to formulate a device ID for midiC1D0 but couldn't figure it out.

Looking through the code, I noticed that when no MIDI device is explicitly specified fluidsynth deduces an ID from various ALSA settings, mainly the default sound card. It occurred to me that I could set the EWI as the default sound card. I remembered that ALSA reads user-specific configuration from ~/.asoundrc and a short trip to Google yielded this incantation: defaults.rawmidi.card 1. Having that line in my .asoundrc finally let fluidsynth talk to the EWI without a symlink hack. This wasn't as exciting as doing away with the latency but I felt satisfied that things were finally set up the way they should be.

In summary, all I had needed from the start was that single line in .asoundrc and a simple fluidsynth -m alsa_raw -a oss TimGM6mb.sf2 (I use the soundfonts provided on EWI's companion CD). It took a while to get there but I finally had a straightforward way to play the EWI through my notebook.

Once I knew what to set in fluidsynth I configured QSynth with the same settings to get a nice graphical UI for switching sounds and tweaking the effects (fluidsynth has built-in reverb and chorus). This was really comfortable and I became spoiled after a while. It started bugging me that I had to launch QSynth by hand each time I wanted to play. I knew that the computer should be able to do it automatically when the EWI gets plugged in. It should even be able to exit QSynth when the EWI gets unplugged. I decided to tackle that one as well to make my playing truly comfortable. It's a story for another post, however...


Akai EWI USB and Linux, part 1

As I've mentioned before, I recently came into posession of an Akai Electronic Wind Instrument, USB edition. To be of any use, this wonderful artifact must be connected to a computer running proper software. That on EWI's companion CD supports only MS Windows and Mac OS X so with my GNU/Linux notebook I was on my own.

I had obviously checked on-line for other people's experiences with the EWI and Linux before purchasing it but all I knew was that yes, it does work. I use a rolling-release Debian-based distro called aptosid (formerly known as sidux) which means I tend to be fairly up-to-date as far as the kernel and drivers are concerned. I wasn't expecting any major problems but I was ready for anything.

I started with the basics: I saved the listing of /dev and /dev/snd into a text file and plugged the EWI in. A bunch of messages in dmesg confirmed that the device was detected and recognized. Comparing the listing of /dev/snd with the previous version yielded /dev/snd/midiC1D0 as a new device and sudo cat /dev/snd/midiC1D0 produced a flurry of line noise as I tried playing the instrument. The low-level set-up, then, was exactly as it should be: pure plug-n'-play.

With my confidence boosted, I turned straight to QSynth which is a GUI front-end for fluidsynth, a powerful SoundFont-based command-line software synthesizer. QSynth has a nice setup dialog with separate tabs for the MIDI side (input) and the audio side (output). Unfortunately, the configuration options accurately reflect the mess that is Linux audio support, with four MIDI drivers and five audio drivers to choose from and fiddle with. To make a long story short, I failed to find a combination of settings that would work.

I needed clarity and precision, hence I turned to working with fluidsynth directly. After reading the manual, the alsa_raw driver looked like the most promising option for MIDI input as I had used ALSA to get a MIDI port on another computer going a few years ago. When I used alsa_raw without specifying a MIDI device path, however, fluidsynth would say "Error opening ALSA raw MIDI port". When I did specify /dev/snd/midiC1D0 as the MIDI device I got "Unknown RawMidi /dev/snd/midiC1D0". I somehow remembered that during my previous MIDI experiments the device was midiC0D0 rather than midiC1D0 so I tried ln -s /dev/snd/midiC1D0 /dev/snd/midiC0D0 and ran fluidsynth without giving it a device path. Bingo! I had sound!

There were two serious problems with this setup. First, the midiC0D0 symlink disappeared at every reboot, forcing me to re-create it over and over. More seriously, the audio lagged some 100 to 300 ms behind MIDI input, making the EWI so sluggish as to be unplayable. What was I to do? I will reveal the dramatic resolution of both issues in another post; stay tuned...


« Page 5 / 6 »
Proudly powered by Pelican, which takes great advantage of Python.