2009-03-31

How to set up ruby gems on Debian Etch as non-root

This tutorial describes how to install Ruby gems to your home directory. The gems you install there will not be visible to other user, and they won't affect the system default configuration. You'll need root access in the first few steps to install Ruby and the rubygems packaging framework itself. It is possible to install those without root access as well, but that's beyond the scope of this tutorial.

You need a working ruby and rubygems. You have to install it as root:
$ su -
# apt-get update
# apt-get install ruby1.8 rubygems
(if you want to install gems which need C compilation, e.g. ruby-sqlite:)
# apt-get install ruby1.8-dev gcc libc6-dev
# ruby -v
ruby 1.8.5 (2006-08-25) [i486-linux]

This is optional, to have the latest rubygems (you can do this any time later):
$ su -
# gem install rubygems-update
...
# /var/lib/gems/1.8/bin/update_rubygems
# rm -f /usr/bin/gem
# ln -s gem1.8 /usr/bin/gem
$ gem -v
1.3.1

All the rest works as non-root:
$ export GEM_HOME=$HOME/gems
$ rm -rf $GEM_HOME
$ mkdir $GEM_HOME{,/cache,/doc,/gems,/specifications}
$ cp -a /var/lib/gems/1.8/cache/sources-*.gem $GEM_HOME/cache/
$ cp -a /var/lib/gems/1.8/gems/sources-* $GEM_HOME/gems/
$ cp -a /var/lib/gems/1.8/specifications/sources-*.gemspec $GEM_HOME/specifications/
$ gem update
Updating installed gems...
Bulk updating Gem source index for: http://gems.rubyforge.org
(this takes a few minutes)
Gems: [] updated

Try querying or installing something:
$ gem search rake --remote
$ gem install rake
Successfully installed rake-0.8.4
Installing ri documentation for rake-0.8.4...
Installing RDoc documentation for rake-0.8.4...

Try installing rails:
$ gem install rails --include-dependencies
(... takes some time)
$ ~/gems/bin/rails -v
2.3.2

Try installing something which needs C code. Please note that you have to install the C prerequisite (libsqlite3) as root:
$ su -c 'apt-get install libsqlite3-dev'
$ gem search sqlite3 --remote
$ gem install --platform ruby sqlite3-ruby
Building native extensions. This could take a while...
Successfully installed sqlite3-ruby-1.2.4
1 gem installed
Installing ri documentation for sqlite3-ruby-1.2.4...
Installing RDoc documentation for sqlite3-ruby-1.2.4...

Please note that you need the GEM_HOME environment variable set for installing gems and for running applications requiring gems. To have this variable set for you, do this:
$ echo 'export GEM_HOME=$HOME/gems' >>~/.bashrc
$ echo 'export GEM_HOME=$HOME/gems' >>~/.bash_profile

If you need the rails command without having to specify ~/gems/bin/rails, make sure you have an export PATH=$HOME/gems/bin:$PATH in your .bashrc and/or .bash_profile.

2009-03-27

How to make Flash full screen work in Ubuntu Hardy and Firefox

We were using the Flash applet on Livescribe's web site in Firefox running on Ubuntu Hardy. We were using Flash 9.0 r124, and Firefox 3.0.7. It had a full screen button, but nothing happened. It was strange, because the full screen button worked a week ago. I've found a solution on http://ubuntulinuxtipstricks.blogspot.com/2007/12/fullscreen-youtube-now-available.html :
If you're using Compiz Fusion, turn off "Unredirect Fullscreen Windows" in the General section and turn off "Legacy Fullscreen Support" in the Workarounds plugin. If you don't, the controls for the player get cut off.
We've found the settings above in our Gnome desktop > System > Preferences > Advanced desktop effects > General options. Turning both settings off solved the problem for us.

2009-03-26

ASCII isdigit, isalpha and isxdigit macros in ANSI C with arithmetic operations

The naïve way of defining a C macro which tests whether a character is a digit (assuming an ASCII-based character set) is #define ISDIGIT(c) ((c) >= '0' && (c) <= '9'). There is a fundamental problem with this naïve definition: it evaluates its argument c more than once, so for example ISDIGIT(x++) will increment x by 2 in some cases. The question naturally arises if there is a macro definition #define ISDIGIT(c) ..., which uses c exactly once. Indeed, there is:
#define ISDIGIT(c) ((c) - '0' + 0U <= 9U). The capital Us in the expression enforce unsigned calculation, so if c is less than '0', then (c) - '0' + 0U becomes a large positive number instead of a negative number with small absolute value, so the comparison will (correctly) return false.

The following code contains solutions for ISDIGIT, ISALPHA and ISXDIGIT, the latter not being practical because of the excessive use of arithmetic operations.
/* by pts@fazekas.hu at Thu Mar 26 01:10:56 CET 2009
*
* 0123456789 ISDIGIT
* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz ISALPHA
* 0123456789ABCDEF ISCAPITALHEX
* 0123456789ABCDEFabcdef ISXDIGIT
*/
#include <stdio.h>

#define ISDIGIT(c) ((c) - '0' + 0U <= 9U)
#define ISALPHA(c) (((c) | 32) - 'a' + 0U <= 'z' - 'a' + 0U)
#define ISCAPITALHEX(c) ((((((c) - 48U) & 255) * 23 / 22 + 4) / 7 ^ 1) <= 2U)
#define ISXDIGIT(c) (((((((((c) - 48U) & 255) * 18 / 17 * 52 / 51 * 58 / 114 \
* 13 / 11 * 14 / 13 * 35 + 35) / 36 * 35 / 33 * 34 / 33 * 35 / 170 ^ 4) \
- 3) & 255) ^ 1) <= 2U)

int main(int argc, char **argv) {
int i;
(void)argc; (void)argv;
for (i = 0; i < 256; ++i) if (ISDIGIT(i)) putchar(i);
printf(" ISDIGIT\n");
for (i = 0; i < 256; ++i) if (ISALPHA(i)) putchar(i);
printf(" ISALPHA\n");
for (i = 0; i < 256; ++i) if (ISCAPITALHEX(i)) putchar(i);
printf(" ISCAPITALHEX\n");
for (i = 0; i < 256; ++i) if (ISXDIGIT(i)) putchar(i);
printf(" ISXDIGIT\n");
return 0;
}

2009-03-13

How to select an ALSA sound card and have concurrent, simultaneus playback using dmix

This blog post is tutorial which describes how to select a default sound card and run multiple playbacks simultaneously, using ALSA on Linux, without a sound server.

The quick trick is having
defaults.pcm.!card Headset
defaults.ctl.!card Headset
defaults.pcm.!device 0
defaults.ctl.!device 0
in your ~/.asoundrc. (Replace Headset with the name of the card on which you want to hear sound. Get the list of available sound cards with aplay -l | awk '/^card/{print$3}'|sort|uniq. To apply this setting for all users, add the lines above to /etc/asound.conf instead.) If this doesn't work for you, please continue reading.

Let's suppose you have a Linux system and many sound cards with an ALSA (>= 0.9) driver, and you don't use any sound server (e.g. pulse, ESD == esound or artsd). The dmix feature of ALSA does software sound mixing, so it makes it possible to run multiple playbacks simultaneously on the same card. ALSA, by default, enables dmix for all cards which don't support concurrent playback of more than one sound stream.

So you just run mplayer -ao alsa file1.mp3, and simultaneously (possibly in another terminal window) mplayer -ao alsa file2.mp3. You should hear both playbacks at the same time. (Please note that you can omit =-ao alsa= from the mplayer command line if you add ao=alsa to your ~/.mplayer/config file.) If the second mplayer doesn't start playback, but exists with an error message containing Device or resource busy, this means there is something wrong with your settings – this tutorial will help to fix that.

If even the first mplayer produces the Device or resource busy error, then close or kill all applications that might play sound. This includes the web browser (with flash), pulse, esd, artsd, mplayer, Skype, MPD and system sounds (disable everything in Gnome / System / Preferences / Sound / Sound). After that, mplayer -ao alsa file1.mp3 should start the first playback, and simultaneously, mplayer -ao alsa file2.mp3 should start the second playback.

If you have multiple sound cards (possibly the sound card in your computer, and an external USB headset), you can set the environment variable ALSA_CARD to direct playback to a specific card. Example 1: ALSA_CARD=Headset mplayer -ao alsa file1.mp3. Example 2: start Firefox as ALSA_CARD=Headset firefox to have the sound Flash movies played on the card named Headset.

You can get a list of sound cards (with some extra information) with aplay -l. For example, on my system, I get
$ aplay -l
card 0: Intel [HDA Intel], device 0: CONEXANT Analog [CONEXANT Analog]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: Intel [HDA Intel], device 1: Conexant Digital [Conexant Digital]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: TuxDroid [TuxDroid], device 0: USB Audio [USB Audio]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 1: TuxDroid [TuxDroid], device 1: USB Audio [USB Audio #1]
Subdevices: 1/1
Subdevice #0: subdevice #0
card 2: Headset [Plantronics Headset], device 0: USB Audio [USB Audio]
Subdevices: 0/1
Subdevice #0: subdevice #0
Here is how I can select each card and device:
card 0: Intel [HDA Intel], device 0: CONEXANT Analog [CONEXANT Analog]
ALSA_CARD=0
ALSA_CARD=Intel
card 0: Intel [HDA Intel], device 1: Conexant Digital [Conexant Digital]
ALSA_CARD=0 ALSA_PCM_CARD=1
ALSA_CARD=Intel ALSA_PCM_CARD=1
card 1: TuxDroid [TuxDroid], device 0: USB Audio [USB Audio]
ALSA_CARD=1
ALSA_CARD=TuxDroid
card 1: TuxDroid [TuxDroid], device 1: USB Audio [USB Audio #1]
ALSA_CARD=1 ALSA_PCM_CARD=1
ALSA_CARD=TuxDroid ALSA_PCM_CARD=1
card 2: Headset [Plantronics Headset], device 0: USB Audio [USB Audio]
ALSA_CARD=2
ALSA_CARD=Headset
Once you have the proper ALSA_CARD and ALSA_PCM_CARD setting, you can add them to your ~/.bashrc and ~/.gnomerc and ~/.xprofile (and possibly to /etc/environment and /etc/X11/Xsession.d/* and /etc/gdm/Xsession). If you log out and log in to your graphic session, you'll have these environment variables by default.

It is possible to select the default sound card without changing setting the ALSA_CARD or ALSA_PCM_CARD environment variables. To do so, add these lines to your ~/.asoundrc:
defaults.pcm.!card Headset
defaults.ctl.!card Headset
defaults.pcm.!device 0
defaults.ctl.!device 0
Each !card line corresponds to the ALSA_CARD value, and each !device line corresponds to the ALSA_PCM_CARD value.

If you set the default in ~/.asoundrc and set the environment variables as well, then the environment variables take effect.

Please note that if you specify any specific ALSA device to any program (other than default), and the program starts playback, then automatic dmix would not work until that program finishes playback and closes the device. This implies that no other program will be able to start playback (but will yield Device or resource busy) until that happens. For example, Skype keeps the sound card open while it is running. So if you want to play sound not coming from Skype while Skype is running, you have to select Default device (default) in Options / Sound Devices for both Sound Out and Ringing. A similar restriction applies to music players and other software: if you specify the playback device for them in their command line or preferences, then the software will lock the sound card, and you lose dmix and concurrent playback. The only dmix-safe ways to select a sound card are the ALSA_CARD etc. environment variables and ~/asoundrc.

To get information about the ALSA cards, run aplay -l and aplay -v -v -L.

ALSA provides OSS emulation (i.e. /dev/dsp, /dev/dsp1), but dmix doesn't work with OSS emulation. To get it work, please run the software which needs OSS using the aoss wrapper, e.g. aoss mplayer -ao oss file1.oss, and concurrently, aoss mplayer -ao oss file2.oss or mplayer -ao alsa file2.oss . If you get the error message /dev/dsp: Device or resource busy from a program, then you'll either have to change it to use ALSA, or run it within aoss.

Here are some sine wave sound generators if you don't have an MP3 ready to test playback with:
perl -e 'print pack("v",32000*sin($_/34))."\0\0"; ++$_ while 1' | aplay -f dat  # Left ear
perl -e 'print "\0\0".pack("v",32000*sin($_/34)); ++$_ while 1' | aplay -f dat # Right ear
If you have the asoundconf utility, you can use it to set up the default sound card in your ~/.asoundrc. For example, after removing ~/.asoundrc and running
asoundconf set-default-card Headset, you'll get a line <:/home/USERNAME/.asoundrc.asoundconf> in file ~/.asoundrc, and the file ~/.asoundrc.asoundconf would contain more than 50 config lines, the essential ones being
!defaults.pcm.card Headset
defaults.ctl.card Headset
defaults.pcm.device 0
defaults.pcm.subdevice -1
defaults.pcm.nonblock 1
defaults.pcm.ipc_key 5678293
defaults.pcm.ipc_gid audio
defaults.pcm.ipc_perm 0660
defaults.pcm.dmix.max_periods 0
defaults.pcm.dmix.rate 48000
defaults.pcm.dmix.format S16_LE
defaults.pcm.dmix.card defaults.pcm.card
defaults.pcm.dmix.device defaults.pcm.device
defaults.pcm.dsnoop.card defaults.pcm.card
defaults.pcm.dsnoop.device defaults.pcm.device
defaults.namehint.extended off
This seems to be too much compared to the 4 lines the beginning of this tutorial suggests.

Random notes

With ALSA 1.0.15, only plughw: works for tuxdroid (so it cannot be used with ALSA_CARD, which implies hw:). Here is how to play: mplayer -ao alsa:device=plughw=TuxDroid file1.mp3 or aplay -D plughw:TuxDroid </dev/urandom. Please note that this restriction applies to both mplayer and aplay. (Maybe that's because dmix was busy when the tuxdroid was connected -- and if we reload ALSA, maybe it will work if I connect the tuxdroid first, and then load the ALSA modules?) The reason why it doesn't work seems to be that the U8 sample format needed by the tuxdroid was introduced only in ALSA 1.0.16. I've verified with libasound 1.0.16 installed (with the same old ALSA kernel) in a chroot, and dmix works with the tuxdroid.

The setting ALSA_CARD=Foo ALSA_PCM_CARD=2 corresponds to aplay -D hw:Foo,2 and mplayer -ao alsa:device=hw=Foo.2. Please note that there is no corresponding environment variable setting for plughw instead of hw. Please also note that mplayer won't use dmix (thus it won't be able to run multiple playbacks concurrently) if you specify any other ALSA setting than mplayer -ao alsa or mplayer -ao alsa:device=default . A similar restriction applies to aplay -D: if you specify any device other than default there, it won't use dmix.

An example full mplayer error message when dmix didn't work:
[AO_ALSA] alsa-lib: pcm_hw.c:1099:(snd_pcm_hw_open) open /dev/snd/pcmC2D0p failed: Device or resource busy
[AO_ALSA] Playback open error: Device or resource busy
Could not open/initialize audio device -> no sound.
Audio: no sound