2009-10-25

How to enable autologin in Ubuntu Jaunty with GDM

To enable automatic login in Ubuntu Jaunty running GDM (default) without clicking, add something like this to the [daemon] section of /etc/gdm/gdm.conf-custom:
TimedLoginEnable=true
TimedLoginDelay=10
TimedLogin=myusername
Once done, restart gdm (this will force an immediate logout):
$ sudo /etc/init.d/gdm restart

2009-10-23

Using the Hama MCE remote on Linux with MPlayer

This blog post describes how to control your MPlayer media playback using the Hama MCE remote control on Linux. The instructions and software were tested on Ubuntu Jaunty, but they should work on any Linux 2.6 system which has sysfs mounted and which has USB HID support compiled to the kernel (can be checked with ls -l /sys/bus/usb/drivers/usbhid).

The instructions are for the model Hama MCE Remote 52451, also known as VRC-1100, but similar instructions might work with other remotes as well.

If you want to use the Hama MCE with XBMC, see these instructions.

The Hama MCE dongle, when connected to the USB port of a Linux machine, registers itself as two USB HID devices: a keyboard and a mouse. Most of its buttons generate regular keyboard events, for example, the numeric keys correspond to numpad keys, and its Enter key corresponds to the Enter key. You can use its mouse controls to move the mouse or click (the left and right buttons). On Ubuntu Jaunty, even the volume buttons work (they adjust the master volume).

However, if you don't want to use your Hama MCE remote as a regular keyboard or mouse, but you'd like to control MPlayer (and possibly some few other applications you specify) with it, then you need special software. LIRC, the de facto standard remote control driver and server does support USB HID devices in general, but the Hama MCE sends some quite unusual events which LIRC seems to be impossible to make recognize. (For example, the hashmark button sends Shift, 3 and 5, and some other buttons send Shift or Control too, and LIRC doesn't seem to be able to track the Shift and Control state, which would be needed to distinguish some buttons from each other.)

So I've implemented my own lircd, hama_mce_lircd.py, which can read button presses and other events from the Hama MCE remote, and it can broadcast them to applications via the socket /dev/lircd, using the traditional lircd protocol. The link above contains installation, usage and configuration instructions for controlling MPlayer with Hama MCE, using hama_mce_lircd.py.

Each button works and can be bound to any MPlayer input.conf command, with the following limitations (of the dongle hardware):

  • the ok and enter buttons are the same;
  • the play and pause buttons are the same;
  • the right click and the info buttons are the same;
  • most buttons don't repeat when held down for a long time.

An alternative and more generic Python Linux event reading and mangling library with Hama MCE support is available at http://github.com/rmt/pyinputevent/.

Here is how to use hama_mce_lircd.py with MPlayer:

$ wget -O /tmp/lircrc.hama \
  http://pts-mini-gpl.googlecode.com/svn/trunk/hama-mce-linux/lircrc
$ wget -O /tmp/hama_mce_lircd.py \
  http://pts-mini-gpl.googlecode.com/svn/trunk/hama-mce-linux/hama_mce_lircd.py
$ sudo killall lircd
$ sudo mkdir -p /var/run/lirc
$ sudo python /tmp/hama_mce_lircd.py /var/run/lirc/lircd
(connect the USB dongle, wait 3 seconds, and watch the script detect it)
(keep it running, open another terminal window)
$ mplayer -lircconf /tmp/lircrc.hama ${VIDEO_FILENAME}

The most important Hama MCE remote keys supported by lircrc.hama: Pause, Stop, Volume Up, Volume Down, Channel Plus (to speed up playback), Channel Minus (to slow down playback), Info (for showing/hiding OSD with the time indicator) the big round button (for seeking), the up and down arrows (for adjusting the subtitle delay).

2009-10-22

Design and print your own geek clock with LaTeX and TikZ

Would you like to have your custom clock for geeks (geek clock, math clock)? You can
design and print the front plate of your own analog geek clock using LaTeX and TikZ. Here is how I did it (download):

% geek_clock.tex
% by pts@fazekas.hu at Thu Oct 22 14:24:35 CEST 2009
\documentclass[a4paper]{article}
\usepackage{tikz}
\usepackage[latin2]{inputenc}
\usepackage{t1enc}
\usepackage{lmodern}

\pdfpagewidth=\paperwidth
\pdfpageheight=\paperheight

\newenvironment{fullpage}{%
\shipout\vbox\bgroup\kern-1in\moveleft1in\vbox to\paperheight\bgroup
\parindent0pt\hsize\paperwidth
}{%
\vfil\egroup\egroup
}

\begin{document}

% Define a few constants for easy configuration
\def\framehalf{9.2cm}
\def\radius{8.8cm}
\def\onedegrad{8.6cm}
\def\fivedegrad{8.5cm}
\def\tendegrad{8.2cm}
\def\labelrad{8.3cm}

\begin{fullpage}
\vfil\noindent\hfil
\begin{tikzpicture}[scale=1]
\draw (-\framehalf,-\framehalf) --
(\framehalf,-\framehalf) --
(\framehalf,\framehalf) --
(-\framehalf,\framehalf) -- cycle;
\draw (0,0) circle (\framehalf);
\draw (0,0) circle (\radius);
\draw[fill=black] (0,0) circle (2.5mm);
\node[draw, circle, inner sep=.2mm] (a) at (0,0) {};
% main lines
\foreach \x in {0, 6,...,360}
\draw[line width=1.5pt] (\x:\onedegrad) -- (\x:\radius);
% labels and longer lines at every 6 degrees
\foreach \x in {30, 60, ..., 360} {
\draw[line width=4pt] (\x:\tendegrad) -- (\x:\radius);
}
\node[scale=3,anchor=north east] at (450-1*30:\labelrad)
{$B'_L$\kern-1ex};
\node[scale=2.5,anchor=east] at (450-2*30:\labelrad)
{\lower8ex\hbox{$\displaystyle \sum_{i=0}^{\infty}1/2^i$\kern-2ex}};
\node[scale=3,anchor=east] at (450-3*30:\labelrad)
{$7\odot-5$};
\node[scale=3,anchor=south east] at (450-4*30:\labelrad)
{$\lceil\pi\rceil$};
\node[scale=3,anchor=south east] at (450-5*30:\labelrad)
{$(2\varphi-1)^2$\kern-4ex};
\node[scale=3,anchor=south] at (450-6*30:\labelrad)
{$3!$};
\node[scale=3,anchor=south west] at (450-7*30:\labelrad)
{$\!6.\overline{9}$};
\node[scale=4,anchor=west] at (450-8*30:\labelrad)
{\raise1ex\hbox{${\bullet}{\circ}{\circ}{\circ}$}};
\node[scale=2.5,anchor=west] at (450-9*30:\labelrad)
{$\lfloor(7\cdot.1\!-\!.7)^{\textrm{-6e-2}}\rfloor$};
\node[scale=2.5,anchor=west] at (450-10*30:\labelrad)
{\lower6ex\hbox{$\displaystyle{5\choose2}$}};
\node[scale=2.5,anchor=north west] at (450-11*30:\labelrad)
{\lower3ex\hbox{\kern-3ex$7^5\mathop{\mathrm{mod}} 13$}};
\node[scale=3,anchor=north] at (450-12*30:\labelrad)
{$\sqrt[3]{1728}$};
\end{tikzpicture}
\end{fullpage}

\end{document}
I used pdflatex and TikZ in TeX Live 2008 to compile the file above to a PDF file, which I printed, cut and glued to a stock clock.

The PDF version of the clock face is available here. It looks like this:

References:

2009-10-21

Screen blanking, DPMS, screen saver control and timeout settings on X11

This blog post explains how to blank and unblank the screen manually, how to activate and deactivate display power saving (DPMS), and how to specify inactivity timeouts for screen blanking on X11. The examples were tried with Ubuntu Jaunty, X.Org 7.4 and icewm. This blog post doesn't explain how to make your changes permanent after logout, or how to change your GNOME settings.

The results below may be distorted if gnome-screensaver is running. To get rid of it, do this:
$ gconftool-2 --type boolean -s /apps/gnome_settings_daemon/screensaver/start_screensaver false
$ killall gnome-screensaver
Without setting start_screensaver to false above, gnome-settings-daemon would restart gnome-screensaver whenever a GNOME application gets started. (Firefox a GNOME Terminal is GNOME applications in Ubuntu Jaunty, but icewm and xterm aren't.)

X11 supports two distinct features for power saving when the user is away from the computer: screen blanking and power saving. Confusingly enough, the man page for xset uses the term screen saver instead of screen blanking. In our terminology, screen blanking is a feature to display a blank (solid black) screen after long enough user inactivity. However, screen saver is a program which gets started upon long enough user inactivity, which draws some simple animation on the screen, and asks the user's password before letting him back to his screen and applications. Power saving (DPMS turns the display off or puts it to some power-saving mode after long enough user inactivity. In power saving mode the display doesn't show anything, but it consumes much less power, see the DPMS link above for typical watt values for the power-saving modes standby, suspend and off (and also in the mode on, when power saving is inactive). User inactivity means that the user doesn't use the keyboard or the mouse for a configurable number of seconds. The typical default inactivity timeouts are around 2 minutes. By default, the X server disables screen blanking and power saving as soon as the user presses a key or moves the mouse. From now on we assume that there is no screen saver running, or its timeout is set to be high enough so it won't ever trigger. Screen blanking and power saving are independent features, they have to be configured independently.

How to set up the timeouts

  • To disable screen blanking, run xset s off
  • To make the screen blank after 600 seconds of user inactivity, run xset s 600
  • To put the display to standby after 100 seconds, to suspend after 200 seconds, and turn it off after 300 seconds, run xset dpms 100 200 300
  • To disable power saving, run xset dpms 0 0 0
  • To make the screen blanking display thee X11 logo on a blue background instead of displaying a solid black screen, run xset s noblank
  • To make the screen blanking display a solid black screen (default), run xset s blank

How to change the screen state immediately

  • To blank the screen, run xset s activate
  • To unblank the screen, run xset s reset
  • To turn the screen off, run xset dmps force off
  • To activate the suspend power-saving mode (good savings, good resume time), run xset dpms force suspend
  • To activate the standby power-saving mode (minimal savings, very quick resume time), run xset dpms force standby
  • To turn the display on, run xset dpms force on; xset s reset (the latter is necessary because the screen was blanked automatically when power saving was activated).

How to prevent screen blanking and power saving in MPlayer

Most media player applications disable blanking and power saving while they are playing a video. To enable this functionality in MPlayer, start it with mplayer -stop-xscreensaver or add the line stop-xscreensaver=1 to your ~/.mplayer/config . By doing so, MPlayer will simulate user activity (just as if the user has moved the mouse) upon startup and then each 30 seconds. So this will prevent the screen from blanking or going to power saving mode if all your timeouts are larger than 30 seconds. Please note that the name of the flag is confusing, because its effect isn't related to screen savers, it just simulates user activity. If a screen saver is running, it may or may not pick up the simulated activity generated by MPlayer. To prevent screen saver activation in this case, you may have to run mplayer -heartbeat-cmd "xscreensaver-command -deactivate" or mplayer -heartbeat-cmd "gnome-screensaver-command -p" .

2009-10-04

JavaScript and ActionScript performance for big integer multiplication

This blog post documents the speed measurement of various JavaScript and ActionScript (and other) implementations multiplying 2048-bit big integers. I needed big integer multiplication for the Diffie-Hellman key exchange in one of my projects.

Since neither JavaScript nor ActionScript has big integer support built in, I had to implement it. I represent a big integer as a JavaScript array of digits in base 32768, in the base To multiply two 2048-bit integers use Karatsuba multiplication, which does 3 multiplications with 1024-bit operand size. To multiply two 1024-bit integers, I use Karatsuba multiplication (again), which does 3 multiplications with 512-bit operand size. To multiply two 512-bit integers, I use traditional O(n^2) ``schoolbook'' multiplication. (The Karatsuba multiplication would have been slower for this size.) All JavaScript and ActionScript code is heavily optimized, constants are inlined, and functions are instantiated for different operand sizes.

I did at least 500 multiplications of 2048-bit operand size. Here is the average number of milliseconds needed for 1 multiplication in the various implementations on an Intel Core 2 Duo 1.5 GHz machine running 32-bit Unbuntu Hardy:
  • 00.0156 ms: Python (C) 2.4 gmpy (mpz of libgmp)
  • 00.0396 ms: Java 1.6.0_16 BigInteger
  • 00.0516 ms: Ruby (C) 1.9.1 Bignum
  • 00.0650 ms: Python (C) 2.4 long
  • 01.3030 ms: JavaScript Google Chrome V8 1.1.1.4
  • 02.1750 ms: ActionScript 3 for Flash Player 10
  • 02.7120 ms: JavaScript Google Chrome V8 1.1.1.4 using BigInt.js' mult()
  • 04.5150 ms: JavaScript Firefox 3.5 TraceMonkey from Mercurial on 2009-10-04
  • 05.0400 ms: JavaScript Firefox 3.5.2 TraceMonkey
  • 05.2860 ms: JavaScript Firefox 3.0.14 TraceMonkey (?) JavaScript
  • 06.9050 ms: ActionScript 3 for Flash Player 9 on Flash Player 9
  • 08.5320 ms: ActionScript 3 for Flash Player 9 on Flash Player 10
  • 10.2900 ms: JavaScript SpiderMonkey command-line interpreter 1.7.0 2007-10-03
The speed tests were run on multiple 32 and 64-bit Debian and Ubuntu installations, and the ratios came out almost the same as above.

The JavaScript and ActionScript (and some other) sources are avaialble at http://code.google.com/p/pts-mini-gpl/source/browse/#svn/trunk/javascript-multiplication.

Conclusion: If you need big integer arithmetic in the browser, detect Java and use its BigInteger from JavaScript. Otherwise, if the browser is Google Chrome, then do it in JavaScript. Otherwise, if there is Flash Player 10, do it in ActionScript 3. Otherwise, use JavaScript (and suggest installation of Java or Flash to the user).

How to create an ActionScript 3 (AS3) flash movie (SWF) without a GUI

This blog post explains how to create a Flash movie completely automatically, without using a GUI, just by writing a program in the language ActionScript 3 (AS3). The post gives instructions for Linux, but they should work with minor modifications on other operating systems as well since the compilers to be used are either written in Java or they are available for multiple systems.

ActionScript is a language similar to JavaScript, with some differences and incompatibilities. The most important difference is that you can declare types of variables (strongly recommended in ActionScript 3), and it has class-based inheritance, so classes are declared and use differently from JavaScript, more similarly to Java. To get a quick introduction to ActionScript, read http://en.wikipedia.org/wiki/ActionScript. The reference manual is hosted by Adobe, at http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/. The filename extension is .as.

ActionScript has two major versions in used today: ActionScript 2 (AS2) is supported by Flash Player 7, 8, 9 and 10. ActionScript 3 (AS3) is supported by Flash Player 9 and 10. ActionScript 2 is about 3.5 times (or even more) slower than the JavaScript interpreter in Firefox 3.0 (SpiderMonkey). ActionScript 3, as implemented in Flash Player 9, has just about the same speed as Firefox 3.0 JavaScript. ActionScript 3, as implemented in Flash Player 10, can be up to 4 times faster than FireFox 3.0 JavaScript if int is used instead of Number for integer computations, and Vector.<int> is used instead of Vector. Both Flash Players 9 and 10 have a JIT engine that compiles ActionScript to native code, but this doesn't seem to become faster than Firefox 3.0 JavaScript in some number-crunching applications.

It is possible to call a JavaScript function from ActionScript and vice versa, using the ExternalInterface ActionScript class (flash.external.ExternalInterface). Please note that ExternalInterface doesn't work for the file:/// protocol due to security reasons (as documented by Adobe here) – you have to upload both your .swf and .html files to the same web server to make use of ExternalInterface.

To compile ActionScript 2 to an SWF file, the free MTASC compiler is recommended, which is very fast, works at the command line, and is said to be safer than the compilers written by Adobe. An example ActionScript2 script:
// Tuto2.as, an example ActionScript 2 script
import flash.external.ExternalInterface;
class Tuto2 {
static var app : Tuto2;
function Tuto2() {
_root.createTextField("tf",0,0,0,800,200);
_root.tf.mutliline = true;
_root.tf.text = "first";
var myformat = new TextFormat();
myformat.color = 0x00000;
myformat.underline = true;
myformat.size = 30;
// Set this, because the default font, "Times New Roman" may not be available on Linux.
myformat.font = "serif";
_root.tf.setTextFormat(myformat);
_root.tf.text = ExternalInterface.call + "\nHello";
// Call this again after changing the text, otherwise the formatting is lost.
_root.tf.setTextFormat(myformat);
}
static function main(mc) { // Entry point.
app = new Tuto2();
}
}
To compile it, download MTASM first:
$ wget http://www.mtasc.org/zip/mtasc-1.12-linux.tgz
$ mkdir mtasc
$ cd mtasc
$ tar xzvf ../mtasc-1.12-linux.tgz
$ cp .../Tuto2.as .
Then compile with:
$ ./mtasc -version 8 -swf tuto2.swf -main -header 800:200:20 Tuto2.as
The corresponding HTML would look like this for Firefox:
<embed src="tuto2.swf"
quality="high" bgcolor="#eeeeee" fgcolor="#000000" width="800" height="2
name="tuto" id="tuto" align="middle" allowScriptAccess="always"
allowFullScreen="true" type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer" />
To make it cross-browser, use SWFObject to embed it. Once created, load the HTML in your browser to view the Flash animation you've just created.

From now on we focus on ActionScript 3 and Flash Player 10. Adobe provides the Flex SDK, which contains a compiler (called mxmlc, or mxmlc.exe on Windows) which can compie ActionScript 3 .as source files to .swf. The Flex SDK is free to use and is partially open source, and it is implemented in Java. Download version 3.4 (which is the most recent stable version at the time of writing this blog post, and is about 120 MB compressed) from http://opensource.adobe.com/wiki/display/flexsdk/Downloads. Also make sure you have Java (at least a JRE) installed. Here are the corresponding download command-lines:
$ wget http://fpdownload.adobe.com/pub/flex/sdk/builds/flex3/flex_sdk_3.4.0.9271.zip
$ mkdir flex
$ cd flex
$ unzip ../flex_sdk_3.4.0.9271.zip
$ bin/mxmlc -help
Here is a sample ActionScript 3 program:
// Tuto3.as, a sample ActionScript3 program
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.getTimer;
import flash.external.ExternalInterface;
public class Tuto3 extends Sprite {
function Tuto3() : void {
var circle : Sprite = new Sprite();
circle.graphics.beginFill(0xFF7744);
circle.graphics.drawCircle(0, 0, 30);
circle.graphics.endFill();
addChild(circle);
//myInit();
//addEventListener(Event.ADDED_TO_STAGE, myInit);
ExternalInterface.addCallback("myFunction", myInit);
}
public function myInit(e : Event = null) : String {
var startTime : Number = getTimer();
var tField : TextField = new TextField();
var tFormat : TextFormat = new TextFormat();
tFormat.size = 20;
// Set this, because the default font, "Times New Roman" may not be available on Linux.
tFormat.font = "serif";
tField.autoSize = "left";
tField.background = true;
tField.border = true;
tField.multiline = true;
tField.x = 20;
tField.y = 40;
tField.text = "hello1";
tField.setTextFormat(tFormat);
var endTime : Number = getTimer();
trace('hello2 ' + startTime + ' ' + (endTime - startTime));
tField.text = 'ms=' + (endTime - startTime);
// Call this again after changing the text, otherwise the formatting is lost.
tField.setTextFormat(tFormat);
addChild(tField);
}
}
}
Here is how to compile it (creates Tuto3.swf):
$ ./bin/mxmlc -target-player 10 -default-size 800 200 -optimize Tuto3.as
Here is the corresponding HTML:
<html><head><script type="text/javascript">
function myonload() {
// document.tuto for Firefox, window.tuto for Internet Explorer
var tuto = document.tuto || window.tuto || document.getElementById("tuto");
if (tuto && tuto.myFunction)
document.getElementById("result").innerHTML = tuto.myFunction();
}
</script></head><body onload="myonload()">
<embed src="Tuto3.swf"
quality="high" bgcolor="#eeeeee" fgcolor="#000000" width="800" height="200"
name="tuto" id="tuto" align="middle" allowScriptAccess="always"
allowFullScreen="true" type="application/x-shockwave-flash"
pluginspage="http://www.macromedia.com/go/getflashplayer" />
<div id="result">no result yet</div>
</body></html>
The HTML above also demonstrates calling an ActionScript method form JavaScript. To call a JavaScript function from ActionScript, do
ExternalInterface.call("JavaScriptFunctionName", arg1, ...)
Don't forget to copy the .html and the .swf to a http:// location, otherwise ExternalInterface won't work because the security limitations imposed by the Flash Player.

Please note that according to Adobe, AVM2, the virtual machine in Flash Player 9 and 10 for ActionScript 3 execution does a JIT for all functions and methods except for constructors. So don't put performance-critical code to the constructor. I couldn't measure any speed difference in the constructor and other methods. The last statement that the constructor is not JITted I could find is from 2007 (http://blog.pixelbreaker.com/flash/as30-jit-vs-interpreted/).

2009-10-03

How to compile TraceMonkey for the Linux command-line

This blog posts describes how to compile TraceMonkey, Firefox's 3.5 fast JavaScript interpreter at the i386 32-bit Linux command-line. The created executable, js will not work in a browser, but it can be used for benchmarking JavaScript code (without HTML or DOM). The instructions below have been tested on an Ubuntu Hardy system.

The corresponding old, slow JavaScript interpreter (shipping Firefox 3.0 and older) is SpiderMonkey. The command-line version can be installed with
$ apt-get install spidermonkey-bin
After that, the command smjs file.js is available. In the executed JavaScript, the function print(expr) can be used to print a line to the terminal window, and load(filename) can be used to load another .js file.

To get the same functionality with TraceMonkey, you have to compile TraceMonkey for yourself. Here is how to do it:
$ sudo apt-get install gcc g++ make autoconf2.13 mercurial
$ hg clone http://hg.mozilla.org/tracemonkey/
$ cd tracemonkey/js/src
$ CC='gcc -m32' CXX='g++ -m32' AR=ar \
./configure --disable-debug --enable-optimize --target=i686-pc-linux-gnu
$ make
$ ls -l shell/js
This creates the shell/js executable, which can be used just like smjs.

To get a statically linked version of the TraceMonkey command-line interpreter, run this after the regular compilation with make:
$ bash -c '
cd shell || exit 1; rm -f js
function c++() { command c++ -static "$@"; }
function g++() { command g++ -static "$@"; }
eval "`make js`"; cd ..; ls -l shell/js'
More information about compiling and benchmarking TraceMonkey can be found on http://blog.mozilla.com/nnethercote/2009/07/27/how-i-work-on-tracemonkey/.

2009-09-29

How to fix F-Spot tags for renamed files on Ubuntu Hardy

The photo manager F-Spot stores all picture file names in the SQLite database ~/.gnome2/f-spot/photos.db. This blog post explains how to update the database manually if picture files were renamed or moved. Without the manual update, F-Spot would not recognize the moved files, and it it will not see the tags and other information attached to them.

To view what files F-Spot knows about, install the command-line SQLite tool:
apt-get install sqlite3
Then get the list with
sqlite3 ~/.gnome2/f-spot/photos.db "SELECT uri FROM photos"
Please note that newer versions of Ubuntu have the database file as ~/.config/f-spot/photos.db instead, so you may have to change the command above.

Create a backup of the photos table in F-Spot database file:
sqlite ~/.gnome2/f-spot/photos.db ".dump photos" >f-spot.table_photos.sql
Copy the backup before modification:
cp f-spot.table_photos.sql f-spot.modified_photos.sql
Modify the file f-spot.modified_photos.sql in your favorite text editor to rename the files, and update the table with
sqlite ~/.gnome2/f-spot/photos.db "DROP TABLE photos"
sqlite -init f-spot.modified_photos.sql ~/.gnome2/f-spot/photos.db
As an alternative, if you don't want to use a text editor, run something like
sqlite3 ~/.gnome2/f-spot/photos.db "UPDATE photos SET uri=REPLACE(uri, '/OLD_DIR/', '/NEW_DIR/')"
If F-Spot doesn't notice the changes, restart F-Spot.

References

Another solution to the same problem: http://ubuntuforums.org/showthread.php?t=394241

2009-09-20

How to refresh /dev/disk on Linux

To refresh /dev/disk/by-label or /dev/disk/by-uuid on Linux (tested on Ubuntu Hardy), run
sudo udevadm trigger
and wait a few seconds. It's OK that the system becomes unresponsive for 5 seconds.

2009-09-06

How to get rid of PulseAudio on Debian and Ubuntu

This blog post describes how to get rid of pulseaudio (the PulseAudio sound server) on a Debian or Ubuntu system. The solution presented here is tested with Debian Etch, Ubuntu Hardy and Ubuntu Intrepid. As a side effect, the GNOME system sounds (the short sound effect played when clicking etc.) won't work.

PulseAudio contains a sound server used for software mixing, multiplexing (audio playback from multiple programs at the same time) and filtering. However, in some cases it is the sound server which actually prevents proper multiplexing, because it locks the sound card, so if a program (e.g. mplayer) is using pulseaudio for playback, and the second program (e.g. Skype) wants to use ALSA, the second program will not be able to start playback because the sound card is locked by pulseaudio. One simple solution is to get rid of pulseaudio, and let applications use ALSA with the default output device. This takes advantage of hardware mixing or Dmix, which does software mixing for simultaneous playbacks.

To get rid of the pulseaudio sound server, run:

$ sudo apt-get remove pulseaudio
$ sudo killall pulseaudio
$ sudo killall pulseaudio
pulseaudio: no process killed
$ (echo Package: pulseaudio; echo Pin: release a=fakerepo; echo Pin-Priority: 1999) |
sudo tee -a /etc/apt/preferences

The last command (the one which appends to /etc/apt/preferences) is a hack to make the pulseaudio package impossible to install (i.e. apt-get install pulseaudio will fail with an unhelpful error message).

On Ubuntu Karmic (as of 2010-03-02), removing PulseAudio prevents the volume control panel applet from working. One workaround is to install some replacement packages from the audiohacks repository (https://launchpad.net/~dtl131/+archive/ppa). The commands are:

$ echo 'deb http://ppa.launchpad.net/dtl131/ppa/ubuntu karmic main' |
  sudo tee /etc/apt/sources.list.d/audiohacks.list'
$ sudo apt-get update
$ sudo apt-get install gnome-applets gnome-applets-data gnome-media \
  gnome-media-common gnome-session-canberra gnome-settings-daemon \
  libcanberra-gtk-module libcanberra-gtk0 libcanberra0 libgnome-media0

On Ubuntu Lucid (as of 2010-06-11), removing PulseAudio also prevents the volume control panel applet from working. One workaround is to install some replacement packages from the audiohacks repository (https://launchpad.net/~dtl131/+archive/ppa). The commands are:

$ sudo add-apt-repository ppa:dtl131/ppa
$ sudo apt-get update
$ sudo apt-get remove pulseaudio gstreamer0.10-pulseaudio
$ sudo apt-get install gstreamer0.10-alsa gnome-alsamixer alsa-oss \
  python-alsaaudio gnome-applets gnome-media gnome-settings-daemon \
  libcanberra0 libcanberra-gtk-module libcanberra-gtk0 libgnome-media0 \
  gnome-applets-data libcanberra-gstreamer alsamixergui alsa-tools

On Lucid, run gstreamer-properties, and change the output to ALSA.

If you are logged in graphically, log out and log in again. (Logging out kills all running interactive applications.) After logging in, right click on an empty area on your GNOME panel, select Add to Panel, select Volume Control, and add it.

2009-09-05

How to parse optional arguments without braces or brackets in TeX

This blog post shows the two winning solutions for the TeX argument parsing problem proposed by Kees van der Laan on the EuroTeX 2009 conference on 2009-08-31. My conclusion is that TeX macro programming, especially undelimited input parsing is tricky, and can become ugly since there are no powerful string inspection and matching operations built into TeX.

The basic problem

The basic problem is to write the argument parsing part of a TeX macro called \jpg, which can be used to include external (JPEG) images, optionally overriding the image width and/or height, like below:
\jpg file.jpg  % at original size
\jpg width5cm file.jpg % scale proportionally (keeping aspect ratio)
\jpg height6cm file.jpg % ditto
\jpg width5cm height6cm file.jpg % resize ignoring aspect ratio
\jpg height6cm width5cm file.jpg % ditto
It is OK to assume that no file name starts with width, height or depth. The space after the dimension (e.g. 5cm) is mandatory.

Solution for the basic problem

This is my winning solution (download source) which solves the basic problem:
%
% jpgcmd_simple.tex: a simple braceless image inclusion TeX macro
% by pts@fazekas.hu at Mon Aug 31 13:42:49 CEST 2009
%
% This is one of the winning solutions for the problem proposed by Kees
% van der Laan on the conference EuroTeX 2009 (2009-08-31).

\def\jpgfilename#1 {%
\egroup % end of the \setbox0\vbox
%\showthe\dp0
\ifdim\dp0=0pt \else \errmessage{depth specified for jpg}\fi%
%\pdfximage
% \ifdim\wd0=0pt \else width\wd0\fi
% \ifdim\ht0=0pt \else height\ht0\fi
% {#1}%
%\pdfrefximage\pdflastximage
\message{JPG file=#1 width=\the\wd0 \space height=\the\ht0;}%
\endgroup}
\def\jpg{%
\begingroup
\setbox0\vtop\bgroup
\hsize0pt \parindent0pt
\everypar{\jpgfilename}%
\hrule height0pt }

ABC\jpg height3cm smiley.jpg
DEF\jpg width3cm smiley.jpg
GHI\jpg width4cm height3cm smiley.jpg
JKL\jpg width3cm smiley.jpg

MNO\jpg smiley.jpg % test for \par in the line below

PQR\jpg depth5mm smiley.jpg

\end
The basic solution above creates a vbox (using \vrule) containing an \hrule of the specified size, and then measures the size of the vbox. Thus the parsing of the optional width and height arguments gets delegated to to the TeX \hrule primitive. A \vtop is used instead of a \vbox so a depth can be detected.

A more versatile solution

This is another winning solution of mine (download source) which solves the basic problem, but it allows for more versatile optional arguments:
%
% jpgcmd_versatile.tex: a versatile braceless image inclusion TeX macro
% by pts@fazekas.hu at Thu Sep 3 11:01:07 CEST 2009
%
% This is one of the winning solutions for the problem proposed by Kees
% van der Laan on the conference EuroTeX 2009 (2009-08-31).

% If #2 starts with #1, then do #3{#z}, otherwise do #4. We get #z by removing
% #2 from the beginning of #1.

\def\ifprefix#1#2#3#4{%
\ifprefixloop#1\hbox\vbox!#2\hbox\vbox!{#3}{#4}%

}

\def\firstoftwo#1#2{#1}
\def\secondoftwo#1#2{#2}
\def\ifxgroup#1#2{%
\ifx#1#2\expandafter\firstoftwo\else\expandafter\secondoftwo\fi}
\def\striphbox#1\hbox\vbox!#2#3{#2{#1}}
\def\ifprefixloop#1#2\vbox!#3#4\vbox!{%
\ifxgroup#1\hbox{\striphbox#3#4\vbox!}{%
\ifxgroup#1#3{\ifprefixloop#2\vbox!#4\vbox!}\secondoftwo}}

% Tests.
\def\paren#1{(#1)}
\message{\ifprefix{}{barden}{1\paren}{0}!}

\message{\ifprefix{bar}{barden}{1\paren}{0}!}
\message{\ifprefix{bar}{bad}{1\paren}{0}!}

\message{\ifprefix{bar}{ba}{1\paren}{0}!}

\newdimen\jpgwidth
\newdimen\jpgheight
\newdimen\jpgscale

\def\jpg{%
\jpgwidth0pt
\jpgheight0pt
\jpgscale0pt
\jpgparse}

% #2 can start with or without =.
\def\jpgsetdimen#1#2{#1#2 \jpgparse}

\def\skipuntilhbox#1\hbox{}

% #2 can start with or without =.
% #2 can end with `pt' or not.
\def\jpgsetfloat#1#2{
\afterassignment\skipuntilhbox
#1#2pt\space\space\hbox\jpgparse}

\def\jpgdeptherror#1{%
\errmessage{depth specified for jpg}\jpgparse}

\def\jpgparse#1 {%
\ifprefix{height}{#1}{\jpgsetdimen\jpgwidth}{%
\ifprefix{width}{#1}{\jpgsetdimen\jpgheight}{%
\ifprefix{depth}{#1}{\jpgdeptherror}{%
\ifprefix{scale}{#1}{\jpgsetfloat\jpgscale}{%
\jpgshow{#1}}}}}}

\def\jpgshow#1{%
\message{JPG file=#1 width=\the\jpgwidth\space height=\the\jpgheight\space
scale=\the\jpgscale;}%
}

ABC\jpg height3cm smiley.jpg
DEF\jpg width3cm smiley.jpg
GHI\jpg width4cm height=3cm smiley.jpg
JKL\jpg width3cm scale=4 smiley.jpg

% test for \par in the line above

MNO\jpg width3cm scale=4pt smiley.jpg
PQR\jpg smiley.jpg
STU\jpg depth5cm smiley.jpg

\end
This solution accepts the optional argument scale with or without a unit after its optional argument (the default unit is pt), and it also accepts an equals sign (=) after the optional argument keywords. The implementation is a lot longer now since it has to parse the optional arguments manually. It uses some well-known TeX macro programming tricks to scan a string character-by-character. The \ifxgroup macro is worth mentioning: it is an \ifx, but it accepts the code for the then and else branches as brace-delimited arguments. Using braces here is a fundamental trick for implementing nested ifs and recursion, because otherwise the the macros in the then and else branches would receive \else and \fi (respectively) instead of the next token.

2009-08-11

ASRock ION 330 nettop with Ubuntu 9.04 (Jaunty Jackalope)

This is a collection of random notes about using the ASRock ION 330 nettop with Ubuntu Jaunty. This nettop is a perfect candidate for a Linux-based HTPC for me:
  • it has low power consumption (about 30 watts);
  • it is quiet;
  • its CPU is powerful (Intel Atom dual-core 1.6GHz);
  • it has a hard drive of a decent size (320 GB);
  • it can play full HD videos on Linux (with 35% CPU consumption) using the VDPAU acceleration;
  • it can drive 2 monitors at the same time (one VGA, one HDMI or DVI);
  • it is suspendable on Linux (not in every software configuration!);
  • it is wakeable on LAN;
  • almost all its hardware works out-of-the-box with Ubuntu 9.04 (Jaunty Jackalope), both i386 and amd64;
  • it works with MythTV;
  • it is cheap.

Installing Ubuntu Jaunty

Both the 32-bit (i386) and the 64-bit (amd64) desktop edition of Ubuntu Jaunty can be installed without problems either from CD or pen drive (created using UNetbootin, either a network or a normal install). The 64-bit edition is recommended, because the hardware-accelerated (VDPAU) video playback consumes much less CPU in 64-bit mode than in 32-bit mode (see below). The hard drive, the CD-ROM, the USB ports, the sound card and the LAN (ethernet) port work out-of-the box with Ubuntu Jaunty. The video controller, however, operates in compatibility mode (VESA) because Jaunty doesn't have the the proper display driver (see below for extra installation instructions), and the one shipping with Jaunty doesn't support the chipset.

Installing the nVidia display driver

Add the Medibuntu repository and the Avenard repository. Install packages with
sudo apt-get update && sudo apt-get install nvidia-glx-190 nvidia-190-libvdpau
If you don't have a Driver "nvidia" line in your /etc/X11/xorg.conf, then run sudo nvidia-xconfig to generate the proper lines. Make sure that your InputDevice in that file is not /dev/psaux; it should be Option "Device" "/dev/input/mice". Now do these as root:
# /etc/init.d/gdm stop  # kills your X session
# rmmod nvidia # may fail
# modprobe nvidia
# echo nvidia >>/etc/modules
# /etc/init.d/gdm start
Now you should have full video acceleration

Suspending

Suspending works out-of-the-box in the Gnome menu for both 32-bit and 64-bit mode. Click with the mouse or press a key to resume.

To suspend remotely, issue command /etc/acpi/sleep.sh sleep in a root SSH session. (This doesn't work with the 64-bit version and the nVidia driver version 190: the system wakes up a few seconds after going to sleep. It does work with 64-bit and nVidia driver version 180. I remember it working with the 32-bit version, but I cannot remember for sure.)

To suspend so the computer can be woken up on ethernet, first run sudo ifconfig to get the MAC address of eth0, then run ethtool -s eth0 wol g; echo NMAC > /proc/acpi/wakeup as root (maybe one of these two commands is enough), then send it to sleep. To wake it up, run etherwake AA:BB:CC:DD:EE:00 (with its MAC address substituted) on another Linux machine on the local network. You may have to install the package etherwake (or something similar) for that.

Full HD video playback with mplayer

You need a recent nVidia display driver (see above) and a corresponding mplayer (from the Avenard repository). Version 190 of the nVidia display driver is recommended (from the testing Avenard repository). Version 185 may also work, but earlier versions (such as 180) don't support the ION chipset. If your package lists are set up correctly (Jaunty Universe + Jaunty Multiverse + Medibuntu + Avenard Release + Avenard Testing), apt-get install nvidia-glx-190 nvidia-190-libvdpau mplayer will install both the newest nVidia display driver and the mplayer which supports hardware-accelerated video playback. However, sometimes (e.g. on the 64-bit system for me), apt-get insists on installing and older mplayer, which would trigger the removal of nvidia-glx-190. The good mplayer package is in the Avenard repository, and it can be installed with something like this:
$ wget http://www.avenard.org/files/ubuntu-repos/testing/mplayer_1.0-svn29435-x264-vdpau-190-0ubuntu2_amd64.deb
$ sudo dpkg -i mplayer_1.0-svn29435-x264-vdpau-190-0ubuntu2_amd64.deb
$ sudo apt-get -f install
Play the video with mplayer -vo vdpau -vc ffmpeg12vdpau,ffwmv3vdpau,ffvc1vdpau,ffh264vdpau, FILENAME.VID. This will consume between 30% and 40% of CPU (as measured using top) with OSD or subititles and between 8% and 9% without OSD or subtitles. Please note that the comma at the end of the -vc argument specifies that MPlayer should try other video codecs if those specified in -vc don't work for the video being played. Please note that that -vo vdpau supports non-accelerated software codecs as well, but there is a performance cost (e.g. for a non-accelerated 320x200 video file I tried -vo xv consumed 5% CPU and -vo vdpau consumed 15% CPU), but -- unfortunately -- it is not possible to make MPlayer autodetect when to use -vo xv and when to use -vo vdpau.

If you don't have a full HD video file at hand, get one from http://www.apple.com/trailers/. Use the browser's View page source functionality to get the download URL, e.g. http://movies.apple.com/movies/fox/allaboutsteve/allaboutsteve-tlrc_1080p.mov You have to insert a h in front of 1080, so the download command would be
wget http://movies.apple.com/movies/fox/allaboutsteve/allaboutsteve-tlrc_h1080p.mov
Get more information about HD video playback on Ubuntu and an nVidia ION board at http://gagravarr.livejournal.com/137571.html. If you don't want to specify the accelerated codec flags every time on the command line, you should copy them to your ~/.mplayer/config file, for example:
vo=vdpau
vc=ffmpeg12vdpau,ffwmv3vdpau,ffvc1vdpau,ffh264vdpau,
#vo=xv
ao=alsa
#subcp=latin2
stop-xscreensaver=1
font=Arial
subfont-text-scale=3
subfont-osd-scale=3

HDMI and S/PDIF audio output

When diagnosing sound problems, make sure that pulseaudio is not running:
sudo killall pulseaudio
Please also make sure that no other program might be using the soundcard: disable system sounds, kill all web browsers (to kill the flash player), and kill all media players.

To get default audio output on the analog stereo jack, run
$ mplayer -ao alsa $FILE
To force audio output on the analog stereo jack, run
$ mplayer -ao alsa:device=front=NVidia $FILE
To force audio output on HDMI, run
$ mplayer -ao alsa:device=hdmi=NVidia $FILE
To force output on S/PDIF, run
$ mplayer -ao alsa:device=iec958=NVidia $FILE
To list all available audio output devices, run
$ aplay -L
Please note than when specifying anything other than -ao alsa to MPlayer, you may lose DMIX support, so you won't be able to run two playbacks at the same time, and MPlayer would print:
[AO_ALSA] Unable to set hw-parameters: Device or resource busy
It is possible to fix that by using plain -ao alsa and specifying the the output device in $HOME/.asourdrc. Using the environment variable ALSA_CARD does work, but ALSA_PCM_DEVICE doesn't seem to work. Set up your $HOME/.asoundrc like this:
defaults.pcm.!card NVidia
defaults.ctl.!card NVidia
defaults.pcm.!device 0
defaults.ctl.!device 0
The device number 0 is analog output, 1 is S/PDIF and 3 is HDMI. The device numbers come from the output of aplay -l.

2009-08-08

How to disable Skype chat notification popup boxes on the Mac OS X

This blog post explains how to disable the notification box with dark gray (black) background in the top right corner of the Mac OS X screen whenever somebody sends you a Skype chat message. The instructions given here have been verified and found working on Mac OS X 10.5.8 (Leopard), but they may not work in Snow Leopard (10.6).

One of the following solutions should work. Please try both.

  • You will not find the setting in Skype's preferences. To disable these notifications, you have to modify your Growl preferences. In the Apple menu (Apple logo in the top left corner of the screen), select System Preferences / Other / Growl / Application / Skype / Configure / Notifications. (If you don't have the item named Growl above, then that's probably because you are using Snow Leopard, and this howto cannot help you.) Here you can change the settings for various notification types. Disable Contact is typing and all containing message received.
  • (boakananda's solution) In Skype 5.0 for the Mac, the default setting is to have visual popup notifications, which are built into Skype and independent of whether Growl is installed or not. (You have the option of having Growl control the notifications, but the default is to have the notifications built-in from Skype). If you want to get rid of the notifications, go to the Skype menu, Preferences, click on the Notifications button, and select the check box Disable all visual notifications.

How to set up keys PageUp, PageDown, Home and End properly in the Mac OS/X Terminal application

This blog post explains how to set up keys PageUp, PageDown, Home and End and their shifted variants in the Mac OS/X Terminal application, so these keys work as in a Linux xterm or gnome-terminal, i.e. PageUp, PageDown, Home, End, Shift-Home, Shift-End send the corresponding escape sequence, and PageUp and PageDown navigate up and down in the scrollback buffer.

Start the Terminal, and copy-paste these commands:
S=$(defaults read com.apple.Terminal "Window Settings" | perl -0777 -pe '$X=q#
"$F729" = "\033[1;2H";
"$F72B" = "\033[1;2F";
"$F72C" = "scrollPageUp:";
"$F72D" = "scrollPageDown:";
F729 = "\033[H";
F72B = "\033[F";
F72C = "\033[5~";
F72D = "\033[6~";
#; s@(keyMapBoundKeys *= *\{.*?)\};\n@$1$X};@gs')
test "$S" && defaults write com.apple.Terminal "Window Settings" "$S"
If you don't get any error messages, exit from the Terminal application (closing the individual terminal windows is not enough) and start it again.

Alternatively, you can change the individual key bindings in Terminal / Preferences / Startup / Basic / Keyboard.

The allow non-ASCII characters (such as accented letters) in the bash command line, run
cat >>~/.inputrc <<'END'
set meta-flag On
set convert-meta Off
set output-meta On
END
bind -f ~/.inputrc
To make the PageUp and PageDown keys search the shell history forward and backward for a command with a prefix before the cursor, run
cat >>~/.inputrc <<'END'
"\e[5~": history-search-backward
"\e[6~": history-search-forward
END
bind -f ~/.inputrc

2009-08-05

How to set up static DHCP with OpenWRT

This blog post explains how to set up the DHCP server running within OpenWRT so it always assigns the same IP address to some of the hosts.

In the LuCI web interface, select Administration, then Network / DHCP / Leases. Add your (MAC address, IP address) entries to the Static leases list. Click on Save & Apply. Ask for a new IP address at your computer. (On Debian and Ubuntu, do an ifdown eth0; ifup eth0; on Windows, do an ipconfig /renew /all, or use the Control Panel to bring the network interface down and then up again.)

Should you not get the static IP address you've set up, double check that you have specified the MAC address correctly. If you still don't get it, SSH onto the router, and run the following commands:
root@OpenWrt:~# /etc/init.d/dnsmasq stop
root@OpenWrt:~# true >/var/dhcp.leases
root@OpenWrt:~# /etc/init.d/dnsmasq start
Once this is done, ask for a new IP address at your computer.

2009-07-29

BibTeX cheat sheet and entry templates

See the most up-to-date version of the cheat sheet at http://www.inf.bme.hu/~pts/bibtex-cheat-sheet.txt . A quick copy:
* Based on information form http://en.wikipedia.org/wiki/BibTeX
* See also: http://amath.colorado.edu/documentation/LaTeX/reference/faq/bibstyles.html
* Nonstandard entries: url=, annote=, crossref=,
* No url= field in standard types, should be put to note={URL \url{...}}.
* There are no comments in BibTeX .bib files, not even %
* ?NAME= describes an optional field.
* BibTeX silently ignores ignores a field whose name it doesn't know.

.bib file entry templates
"""""""""""""""""""""""""
@article{NAME,
author={},
title={},
journal={},
year=,
?volume={},
?number={},
?pages={},
?month=,
?note={},
?key={},
}

@book{NAME,
author/editor={},
title={},
publisher={},
year=,
?volume={},
?series={},
?address={},
?edition={},
?month=,
?note={},
?key={},
?pages={},
}

@booklet{NAME,
title={},
?author={},
?howpublished={},
?address={},
?month=,
?year=,
?note={},
?key={},
}

@conference{NAME,
author={},
title={},
booktitle={},
year=,
?editor={},
?pages={},
?organization={},
?publisher={},
?address={},
?month=,
?note={},
?key={},
}

@inbook{NAME,
author/editor={},
title={},
chapter/pages={},
publisher={},
year=,
?volume={},
?series={},
?address={},
?edition={},
?month=,
?note={},
?key={},
}

@incollection{NAME,
author={},
title={},
booktitle={},
year=,
?editor={},
?pages={},
?organization={},
?publisher={},
?address={},
?month=,
?note={},
?key={},
}

@inproceedings{NAME,
author={},
title={},
booktitle={},
year=,
?editor={},
?pages={},
?organization={},
?publisher={},
?address={},
?month=,
?note={},
?key={},
}

@manual{NAME,
title={},
?author={},
?organization={},
?address={},
?edition={},
?month=,
?year=,
?note={},
?key={},
}

@mastersthesis{NAME,
author={},
title={},
school={},
year=,
?address={},
?month=,
?note={},
?key={},
}

@misc{NAME,
?author={},
?title={},
?howpublished={},
?month=,
?year=,
?note={},
?key={},
}

@phdthesis{NAME,
author={},
title={},
school={},
year=,
?address={},
?month=,
?note={},
?key={},
}

@proceedings{NAME,
title={},
year=,
?editor={},
?publisher={},
?organization={},
?address={},
?month=,
?note={},
?key={},
}

@techreport{NAME,
author={},
title={},
institution={},
year=={},
?type={},
?number={},
?address={},
?month=,
?note={},
?key={},
}

@unpublished{NAME,
author={},
title={},
note={},
?month=,
?year=,
?key={},
}

Description of entries and fields
"""""""""""""""""""""""""""""""""
* @article: An article from a journal or magazine.
* @book: A book with an explicit publisher.
* @booklet: A work that is printed and bound, but without a named publisher or
sponsoring institution.
* @conference: The same as inproceedings, included for Scribe compatibility.
* @inbook: A part of a book, usually untitled. May be a chapter (or section or
whatever) and/or a range of pages.
* @incollection: A part of a book having its own title.
* @inproceedings: An article in a conference proceedings.
* @manual: Technical documentation.
* @mastersthesis: A Master's thesis.
* @misc: For use when nothing else fits.
* @phdthesis: A Ph.D. thesis.
* @proceedings: The proceedings of a conference.
* @techreport: A report published by a school or other institution, usually
numbered within a series.
* @unpublished: A document having an author and title, but not formally
published.
* address=: Publisher's address (usually just the city, but can be the full
address for lesser-known publishers)
* annote=: An annotation for annotated bibliography styles (not typical)
* author=: The name(s) of the author(s) (in the case of more than one author,
separated by and)
* booktitle=: The title of the book, if only part of it is being cited
* chapter=: The chapter number
* crossref=: The key of the cross-referenced entry
* edition=: The edition of a book, long form (such as "first" or "second")
* editor=: The name(s) of the editor(s)
* eprint=: A specification of an electronic publication, often a preprint or a
technical report
* howpublished=: How it was published, if the publishing method is nonstandard
* institution=: The institution that was involved in the publishing, but not
necessarily the publisher
* journal=: The journal or magazine the work was published in
* key=: A hidden field used for specifying or overriding the alphabetical order
of entries (when the "author" and "editor" fields are missing). Note that
this is very different from the key (mentioned just after this list) that is
used to cite or cross-reference the entry.
* month=: The month of publication (or, if unpublished, the month of creation).
Example 1=: month=jan. Example 2=: month="17~" # feb.
* note=: Miscellaneous extra information
* number=: The "number" of a journal, magazine, or tech-report, if applicable.
(Most publications have a "volume", but no "number" field.)
* organization=: The conference sponsor
* pages=: Page numbers, separated either by commas or double-hyphens. For books,
the total number of pages.
* publisher=: The publisher's name
* school=: The school where the thesis was written
* series=: The series of books the book was published in (e.g. "The Hardy Boys"
or "Lecture Notes in Computer Science")
* title=: The title of the work
* type=: The type of tech-report, for example, "Research Note"
* url=: The WWW address
* volume=: The volume of a journal or multi-volume book
* year=: The year of publication (or, if unpublished, the year of creation)

2009-07-11

How to create a bootable CD running GRUB4DOS on Linux (Ubuntu Hardy)

GRUB4DOS is a flexible, feature-extended version of the GRUB boot manager. This blog post explains how to create a bootable CD which boots GRUB4DOS, from which you can boot almost anything. To create a bootable floppy instead, see http://ptspts.blogspot.com/2009/07/how-to-create-bootable-floppy-running.html.

Run this shell script to create the CD image grldr.iso:
#! /bin/bash
# by pts@fazekas.hu at Sat Jul 11 16:24:31 CEST 2009
GRUB4DOS_ZIP=grub4dos-0.4.4-2009-06-20.zip
GRUB4DOS_DIR=grub4dos-0.4.4 # as extracted from $GRUB4DOS_ZIP
set -ex
TO_INSTALL=''
type -p mkisofs || TO_INSTALL="$TO_INSTALL mkisofs"
type -p wget || TO_INSTALL="$TO_INSTALL wget"
type -p unzip || TO_INSTALL="$TO_INSTALL unzip"
test "$TO_INSTALL" && sudo apt-get install $TO_INSTALL
wget -O "$GRUB4DOS_ZIP" http://download.gna.org/grub4dos/"$GRUB4DOS_ZIP"
unzip -o "$GRUB4DOS_ZIP"
test "$GRUB4DOS_DIR"/grldr
rm -rf grldr.iso.dir
mkdir grldr.iso.dir
cp "$GRUB4DOS_DIR"/{grldr,menu.lst} grldr.iso.dir
mkisofs -R -b grldr -no-emul-boot -boot-load-size 4 -o grldr.iso grldr.iso.dir
: All OK, CD image grldr.iso created.
Please note that you may want to adjust the GRUB4DOS_ZIP variable above in order to download a more recent version of GRUB4DOS, when its available. you can get the list of versions from http://download.gna.org/grub4dos/.

You may use the CD image grldr.iso in your virtualization software (such as VirtualBox), or you may burn it to a CD with:
$ sudo apt-get install growisofs
$ growisofs -dvd-compat -Z /dev/cdrom=grldr.iso

How to create a bootable floppy running GRUB4DOS on Linux (Ubuntu Hardy)

GRUB4DOS is a flexible, feature-extended version of the GRUB boot manager. This blog post explains how to create a bootable floppy which boots GRUB4DOS, from which you can boot almost anything. To create bootable CD instead, see http://ptspts.blogspot.com/2009/07/how-to-create-bootable-cd-running.html.

Run this shell script to create the 1.44MB floppy image grldr.img:
#! /bin/bash
# by pts@fazekas.hu at Sat Jul 11 11:02:14 CEST 2009
GRUB4DOS_ZIP=grub4dos-0.4.4-2009-06-20.zip
GRUB4DOS_DIR=grub4dos-0.4.4 # as extracted from $GRUB4DOS_ZIP
set -ex
TO_INSTALL=''
type -p wget || TO_INSTALL="$TO_INSTALL wget"
type -p mformat || TO_INSTALL="$TO_INSTALL mtools"
type -p unzip || TO_INSTALL="$TO_INSTALL unzip"
test "$TO_INSTALL" && sudo apt-get install $TO_INSTALL
wget -O "$GRUB4DOS_ZIP" http://download.gna.org/grub4dos/"$GRUB4DOS_ZIP"
unzip -o "$GRUB4DOS_ZIP"
dd if=/dev/zero of=grldr.img bs=1474560 count=1
mformat -i grldr.img -f1440 ::
"$GRUB4DOS_DIR"/bootlace.com --floppy --chs grldr.img
mcopy -i grldr.img "$GRUB4DOS_DIR/grldr" ::GRLDR
mcopy -i grldr.img "$GRUB4DOS_DIR/menu.lst" ::menu.lst
: All OK, 1.44MB floppy image grldr.img created.
Please note that you may want to adjust the GRUB4DOS_ZIP variable above in order to download a more recent version of GRUB4DOS, when its available. you can get the list of versions from http://download.gna.org/grub4dos/.

You may use the floppy image grldr.img in your virtualization software (such as VirtualBox), or you may write it to a floppy disk with:
$ cat grldr.img >/dev/fd0

2009-06-21

How to configure dynamic DNS (dyndns) with OpenWRT and LuCI

This blog post tells you how to configure dynamic DNS (using dyndns.org) on your router running OpenWRT Kamikaze. The instructions havene be tested on a Linksys WRT54GL running OpenWRT Kamikaze (r14417).

Go to the menu System / Software. If you don't see package descriptions, click on the link Update package lists. Make sure you don't have the obsolete packages updatedd or ipupdate or ez-ipupdate. (You'll still find some old, obsolete forum posts recommending them on the net.) Install package luci-app-ddns.

Reboot (power-cycle) the router, or run rm -rf /var/luci-* in the SSH prompt. Without this step, the menu Services / Dynamic DNS doesn't appear.

Go to the menu Services / Dynamic DNS. Adjust your settings. The package supports dyndns.org, changeip.com, zoneedit.com, no-ip.com, and freedns.afraid.org . Please note that if you specify a force update interval less then 28 hours (the default is 72 hours), then dyndns.org will ban you. Click on the button Save & Apply.

Reboot (power-cycle) the router, or run INTERFACE=wan ACTION=ifup sh /etc/hotplug.d/iface/25-ddns in the SSH prompt. Without this step, the updater daemon script (/bin/sh /usr/lib/ddns/dynamic_dns_updater.sh myddns 0) is not started. This script sends the IP address update to the dynamic DNS provider. Wait a few minutes for the DNS update to take effect, then try to ping your new host name.

2009-06-15

How to render SVG with sharp text and manual kerning

If you want to render SVG vector graphics with text (i.e. convert it to a PNG), you might experience some problems: 1. the antialiasing glitch: the edges of the characters (even horizontal and vertical edges) are blurry; 2. the manual kerning glitch: if you specify manual kerning (by pressing Alt-Left or Alt-Right between two characters) in Inkscape, this gets ignored upon rendering. This blog post gives a solution to these problems.

To get rid of the antialiasing glitch, make sure your text is a text object, not a path. To check this, try to edit it with the text tool of Inkscape. If you can edit the text, then it's a text object. (If you have your text as path, you'll get the antialiasing glitch.) Then render the SVG with rsvg or The GIMP. There will be no antialiasing glitch in the output PNG. (If you render by exporting from Inkscape 0.46 or earlier, you'll get the antialiasing glitch. We tested it on Linux and Windows.)

To get rid of the manual kerning glitch, export the SVG to PNG in Inkscape. However, this introduces the antialiasing glitch. To get rid of both, you should use a patched rsvg, i.e. downloading, patching, compiling and installing rsvg for yourself. Get the patch from http://code.google.com/p/pts-mini-gpl/source/browse/#svn/trunk/pts-librsvg-manual-kerning-patch. As of now, the patch is for librsvg-2.26.0, but it is reported to apply cleanly to librsvg-2.22.3 as well. If you use Ubuntu Intrepid on an x86 system, you can get .deb packages from the attachments on https://www.epointsystem.org/trac/vending_machine/wiki/HotSpotLogo.

An example rendered image which avoids both problems is visible on https://www.epointsystem.org/trac/vending_machine/wiki/HotSpotLogo .

2009-06-13

How to make Ghostscript embed a Type 1 font to the PDF in one part, without splitting

I was facing a problem with Ghostscript 8.54 -sDEVICE=pdfwrite. When embedding a Type 1 font to the PDF, Ghostscript splitted the font to two font objects (and the corresponding FontDescriptor and CFF stream objects as well), instead of generating only one font object. This blog post explains how I solved the problem.

Please note that the problem didn't happen with a newer Ghostscript (such as GPL Ghostscript 8.61 (2007-11-21), it always generated a single PDF font object.

In a few hours I was able to reduce the source font to two glyphs, Ghostscript still splitting it to two 1-glyph fonts. Then I had the idea to change the /Encoding array, putting the two glyph names next to each other. It solved the problem, Ghostscript didn't split the font anymore. In fact, it turned out that one of the glyphs was missing from the /Encoding, and once I added it (anywhere), the problem was solved.

See the source PostScript file on http://www.math.bme.hu/~pts/twob.ps.txt . It contains the 2-glyph font, demonstrates the problem and shows the solution as well (when compiled with -dfill_encoding=true).

2009-06-12

Using \romannumeral in TeX to do multiple macro expansions

This blog post demonstrates a TeX macro hack: using \romannumeral to expand many macros in a single expansion. The example goal is to define a macro \stars which (when called properly) expands to the specified number of stars as a single expansion. Using a single expansion only makes the macro work in an \expandafter\def...{...} context. The similar macro given in The TeXbook doesn't work in this context because it needs multiple expansions.
\documentclass{article} 
%** Usage: \romannumeral\stars{NUMSTARS}
%** This expands to a NUMSTARS stars (* tokens) in a single expansion step.
\def\stars#1{%
\expandafter\mtostar\expandafter{\expandafter}\romannumeral\number#1 000z}
\def\firstoftwo#1#2{#1}%
\def\secondoftwo#1#2{#2}%
\def\mtostar#1#2{%
\ifx#2z\expandafter\firstoftwo\else\expandafter\secondoftwo\fi
{0 #1}{\mtostar{#1*}}%
}
\begin{document}
\expandafter\def\expandafter\mystars\expandafter{\romannumeral\stars{4}}%
\texttt{(\meaning\mystars)}
\end{document}

2009-06-09

How to typeset text with bilingual fragments with LaTeX

This post gives a possible solution for typesetting two similar versions of the same LaTeX document differing only in some text fragments.

Here is the code:
\documentclass{article}

\makeatletter
\newif\ifsecondlanguage
\def\@bracedfirst#1#2{{#1}}
\def\@bracedsecond#1#2{{#2}}

\def\do@localized#1[#2]{%
\let\@@localized\@undefined
\newcommand\@@localized[#2]{#1}\@@localized}%

%** Typeset different text depending on the current value of \ifsecondlanguage.
%** Usage: \bilingual{LANG1PAT}{LANG2PAT}[ARGCOUNT]{ARG1}{ARG2}...
%** If \ifsecondlanguage is true, pattern LANG2PAT is used, else LANG1PAT is
%** used. Arguments #1, #2 etc. in the pattern are substituted with
%** ARG1, ARG2 etc.
\def\bilingual{%
\expandafter\expandafter\expandafter\do@localized\csname
@braced\ifsecondlanguage second\else first\fi\endcsname
}

\begin{document}

\secondlanguagetrue
\bilingual
{This is addition: #1; and this is multiplication: #2.}
{Translation to another language: #1 and another translation: #2.}
[2]{$x+y$}{$x*y$}

\secondlanguagefalse
\bilingual
{These are additions: #1, #2; and this is multiplication: #3.}
{Translation to another language: #1, #2 and another translation: #3.}
[3]{$x+y$}{$x\oplus y$}{$x*y$}

\end{document}
Select the language to use with \secondlanguagetrue or \secondlanguagefalse. Whenever you have a text fragment which needs to be typeset differently depending on the languagem, use the \bilingual command, as demonstrated above.

2009-06-01

How to migrate or merge a CVS or SVN repository to a remote SVN repository

Let's suppose you have one or more local CVS and/or SVN repositories, and you want to merge them (with their change history) to new directories of an existing, possibly remote target SVN repository. This blog post explains how to do this using Unix tools.

The following tools will be used:Please note that we won't use svnsync, because it requires the target repository to be empty. We won't use svn-merge-repos.pl much either, because it requires the target repository to be local. We won't use svnadmin load much either, because it requires the target repository to be local.

Access remote repositories for the first time

For some repositories, you have to specify your username (usually in the command line) and password (usually answering to an interactive prompt) in order to be able to connect. For each machine, you have to do it once, because SVN records your username and password to files under $HOME/.subversion/auth. To avoid problems connecting later, make sure you access the the remote repositories on each machine you'll be working on, so your credentials get saved. The easiest way to do it is to run
svn ls URL://TO/SVN/REPOSITORY --username MYUSER

Install svn-pusher

svn-pusher can add any SVN repository to another SVN repository (as a subdirectory) no matter local or remote, keeping the commit history of the specified revision interval. You can skip this installation step now and come back only if you are asked to use svn-pusher in some of the steps below.

svn-pusher is implemented as a Perl script using SVN's Perl bindings (SVN::Core). The easiest way to install svn-pusher is with root access on a Unix system. For example, on Debian or Ubuntu, run this as root:
# apt-get update
# apt-get install libsvn-core-perl
# echo no | cpan -i SVN::Pusher # this may take about a minute
# type -p svn-pusher
/usr/local/bin/svn-pusher
# svn-pusher help
For the sake of completeness we mention (but we recommend against using) svn-push as an alternative of svn-pusher. svn-push is a tool written in C, available in the SVN contrib directory, and it does something like svn-pusher, but it's dumber: it can commit only a single revision at once, and it needs the head revision number. Here is how to compile it:
$ wget http://svn.collab.net/repos/svn/trunk/contrib/client-side/svn-push/svn-push.c
$ sudo apt-get install libsvn-dev
$ gcc -W -Wall -I/usr/include/subversion-1 -I/usr/include/apr-1.0 \
-Doff64_t='unsigned long long' -o svn-push svn-push.c -lsvn_client-1
$ ./svn-push
Usage : svn-push -r N:M SRC_URL DEST_URL
For the sake of completeness, we also mention the SVN::Push Perl module, which provides the svnpush command. SVN::Pusher seems to be more up-to-date.

Convert the CVS repository to an SVN repository dump

You can skip this step if your source repository is not a CVS repository.

A CVS repository is a directory containing *,v files (possibly in subdirectories), and containing a directory named CVSROOT (or one of its parents containing the CVSROOT).

Download and install cvs2svn from here. It's a Python script, so install Python as well (2.4 or 2.5 should be OK). You don't need root access to run cvs2svn; in fact, it can be run as extracted from the tarball. Example:
$ wget http://cvs2svn.tigris.org/files/documents/1462/44372/cvs2svn-2.2.0.tar.gz
$ tar xzvf cvs2svn-2.2.0.tar.gz
$ cvs2svn-2.2.0/cvs2svn --help
You don't need Subversion itself for this step – cvs2svn (if run with --dumpfile=) needs only Python and the standard Unix sort utility.

Make sure you have your CVS repository on the same machine as cvs2svn. (Copy with scp -r or rsync if necessary.) It is a good and safe idea to make a copy and to use it for the purpose of the conversion. Make sure you have a neighbor or parent directory named CVSROOT next to the repository. The CVSROOT directory can be empty. Make sure that all files in your CVS repository directory are named *,v (i.e. their name ends with ,v). If you don't need all files or all directories, feel free to remove them now. The effect would be as if those files and/or directories have never been added to the CVS repository.

Run cvs2svn --dumpfile=PROJECT.dump PATH/TO/CVS/REPOSITORY . This creates the file PROJECT.dump, which contains all files in the CVS repository, with their full commit history.

Convert the SVN repository to an SVN repository dump

You can skip this step if your source repository is not a SVN repository.

If your source repository is local and you have read access to it, just run
svnadmin dump PATH/TO/SVN/REPOSITORY >PROJECT.dump
Otherwise use svn-pusher like this:
$ svnadmin create PROJECT.copy
$ svn-pusher push URL://OF/SVN/REPOSITORY PROJECT.copy
$ svnadmin dump PROJECT.copy >PROJECT.dump # this may take some time
$ rm -rf PROJECT.copy
As an alternative to svn-pusher, you can use svnsync as well (part of the standard SVN installation), see blog post Dump a SVN repository from a URL how to do it. Please note that both svn-pusher and svnsync are quite slow (as compared to svnadmin dump), and svnsync is better supported and documented since it is part of standard SVN.

Once you have your PROJECT.dump file, use svndumpfilter (part of standard SVN) to get rid of the unnecessary files and directories. You may also edit the file in a text editor to do some other modifications (such as renaming files). The file format should be self-explanatory.

If you expect a file name conflict between the repositories (between source1 and source2 or source1 and target), e.g. multiple repositories have trunk/version.h, it is safest to move/rename all the source repositories to their own directories, and once the merge is done, do a careful and safe svn mv. Here is how to rename everything (e.g. from DIR/TO/FILE to PROJECT.merge/DIR/TO/FILE) in a *.dump file:
$ perl -pi -e's@^(Node-path: )@${1}PROJECT.merge/@' PROJECT.dump
After that, please make sure you add the directory creation into revision 1 of PROJECT.dump. Use your text editor to insert the following lines just below the first PROPS-END line:
Node-path: PROJECT.merge
Node-kind: dir
Node-action: add

Merge an SVN repository dump to a local target SVN repository

You can skip this step if the target repository is not local.
The contents of the dump file PROJECT.dump can be added to an existing local target SVN repository using svnadmin load PATH/TO/TARGET/SVN/REPOSITROY <PROJECT.dump . An example for creating a new target SVN repostiory, and adding multiple projects to it:
$ svnadmin create myprojects
$ cvs2svn --dumpfile=myproject1.dump cvsrepo/dir/myproject1
$ svnadmin load myprojects <myproject1.dump
$ cvs2svn --dumpfile=myproject2.dump cvsrepo/dir/myproject2
$ # (edit myproject2.dump, see below)
$ svnadmin load myprojects <myproject2.dump
$ svn ls -R file://$PWD/myprojects
Please note that cvs2svn adds the creation of the directories trunk, tags and branches to the *.dump file. This will be a problem in svnadmin load myprojects <myproject2.dump, because this tries to add directory trunk, which already exists in repository mpyrojects, so the operation will fail. The solution is to edit the file myproject2.dump, and remove the following lines from near the beginning:
Node-path: trunk
Node-kind: dir
Node-action: add


Node-path: branches
Node-kind: dir
Node-action: add


Node-path: tags
Node-kind: dir
Node-action: add

Merge an SVN repository dump to a target SVN repository

This steps works for both local and remote target SVN repositories, but it's a lot slower than the svnadmin load method described above (which works only if the target SVN repository is local). Install svn-pusher (see the step for it above). It doesn't matter which machine you install svn-pusher to as long as it can connect to the target repository. Copy PROJECT.dump created above to the machine you've installed svn-pusher to. Then run
$ svnadmin create PROJECT.copy
$ svnadmin load PROJECT <PROJECT.dump
$ svn-pusher push file://$PWD/PROJECT.copy URL://TO/TARGET/SVN/REPOSITORY # slow
$ rm -rf PROJECT.copy
Please note that revision numbers in messages reported by svn-pusher are usually off by one, e.g. Committed revision 1 from revision 0. This is normal, the revision numbers will match perfectly in the target repository.

You can use svn-pusher multiple times on the same target repository to merge multiple source repositories. If the target repository is not empty by the time you start svn-pusher, it is safest to dump it first (to have a backup), and then please pay attention to the following facts. svn-pusher (unlike svnadmin load) reports a warning if a directory (e.g. trunk) has already been added. This warning is usally harmless. If svn-pusher wants to add a file which already exists, it will skip merging that source revision, but it will proceed merging subsequent source revisions. This is not always what you want, because you may want to fix the conflict first by renaming files, and only then proceed with subsequent revisions. Should this happen, you may have to rebuild the target repository from scratch using the backup.

2009-05-31

Syntax highlighted Python

Example syntax highlighted Python code: (please ignore)
def Hello(self, x, y): 
return x * y + "foo" # bar
The syntax highlighting was done in the borland style on http://pygments.org/demo/.

2009-05-21

Ruby on Rails 2.3 unit tests

The blog post Ruby on Rails Unit Tests explains why it is a bad idea to give the unit tests access to the database, and how to set up your Rails project so that you get an exception if you try to access the database with ActiveRecord model objects from a unit test. Unfortunately, that blog post doesn't work with the latest Rails. This blog post explains how to set up a Ruby on Rails 2.3 project to disallow database accees in the unit tests. The instructions were tested with Rails 2.3.2.

Step 1. Create file lib/tasks/unit_tests.rake with the following contents:
Rake::Task[:'test:units'].prerequisites.delete('db:test:prepare')
Step 2. Create file test/unit_test_helper.rb with the following contents:
# similar to autogenerated test/test_helper.rb
#
fail "some of the unit tests has loaded test_helper.rb. Please change " +
"(require 'test_helper') to (require 'unit_test_helper') in " +
"tests/unit/**/*.rb" if $".include?('test_helper.rb')
fail "some of the unit tests has loaded test_help.rb. Please make sure that " +
"the first line is (require 'unit_test_helper') in tests/unit/**/*.rb" if
$".include?('test_help.rb')
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
HIDE_ActiveRecord = self.class.send(:remove_const, :ActiveRecord)
require 'test_help' # rails 2.3.2 standard module
ActiveRecord = self.class.send(:remove_const, :HIDE_ActiveRecord)

class FakeConnection
class InvalidActionError < StandardError
end
COLUMNS = {}
def self.columns(table_name, name=nil)
if COLUMNS.has_key?(table_name)
COLUMNS[table_name]
else
raise InvalidActionError, "please create something like this first: " +
"FakeConnection::COLUMNS[#{table_name.inspect}] = [ " +
"ActiveRecord::ConnectionAdapters::Column.new(name=..., " +
"default=nil, sql_type=\"text\", null=false), ...]"
end
end
DB_ERROR_MSG = 'You cannot access the database from a unit test'
def self.quote_table_name(*args) # called from ActiveRecord::Base.find
raise InvalidActionError, DB_ERROR_MSG, caller
end
def self.quote_column_name(*args) # called from ActiveRecord::Base.delete
raise InvalidActionError, DB_ERROR_MSG, caller
end
def self.select_all(*args) # called from ActiveRecord::Base.find_by_sql
raise InvalidActionError, DB_ERROR_MSG, caller
end
def self.transaction(*args) # called from ActiveRecord::Base.save
raise InvalidActionError, DB_ERROR_MSG, caller
end
end
class << ActiveRecord::Base
def connection
FakeConnection
end
end
Step 3. Make sure all your unit tests (i.e. =*.rb= files in =test/unit=, recursively) start with the line require 'unit_test_helper' instead of require 'test_helper'. Don't forget any of the =*.rb= files, otherwise you'll get an error message at test file load time.

Step 4. If you need access to your model objects, add the necessary column declaration to your test file. For example, for model class Foo:
FakeConnection::COLUMNS['foos'] = [
ActiveRecord::ConnectionAdapters::Column.new(
name="bar1", default=nil, sql_type="varchar(255)", null=false),
ActiveRecord::ConnectionAdapters::Column.new(
name="bar2", default=nil, sql_type="integer", null=false),
]
After this, you can do a Foo.new in your tests – but you won't be able to save the object, and Foo.find, Foo.find_by_sql and Foo.delete won't work either. If you try any of those, a FakeConnection::InvalidAction gets raised. To test such a functionality, use an integration test instead of a unit test. To do so, create your test file as test/integration/*.rb instead of test/unit/*.rb.

Once all steps are done, run your unit tests with rake test:units. This will run all tests in all *.rb files in the test/units directory (recursively). If you get an exception, and you need the full backtrace, rerun with BACKTRACE=1 rake test:units. For an even longer backtrace, run rake --trace test:units.

2009-05-14

Java .class file converter which makes method and fields public available

ClassPublic.Java is a Java .class file converter which makes a Java class public and non-final, its fields public and its methods public and non-final. ClassPublic.java has a compact implementation, depending only on a J2SE 1.5.0 or newer. ClassPublic.java can be used to increase interoperability and code reusability of a .class file whose .java source is not available or it is not feasible to recompile.

Download classpublic-latest.zip.

2009-05-13

Java class disassembler with offset display available

jdisasm.py is a Java class file disassembler implemented as a Python script. jdisasm.py displays a java .class file in a human readable form, showing the class name, the field names and types, the method names, types and codes (including instruction mnemonics). For each item shown, the file offset is prepended. (Neither javap or jad can display the file offset.)

Download jdisasm-latest.tar.gz.

2009-05-05

How to deploy a Ruby on Rails application using Phusion Passenger and Rails Enterprise Edition on Debian Etch or Ubuntu Hardy

This post gives step-by-step instructions on deploying a Ruby on Rails application on a Linux-based server using Phusion Passenger and Ruby Enterprise Edition. The instructions are for a Debian Etch system, but they should work with minor modifications on any Linux system based on Debian or Ubuntu (including Debian Etch Mini, available as Minimal Gnome Desktop from http://www.visoracle.com/download/debian/.

It doesn't matter if you have Ruby installed on the server, because you'll compile and install Ruby Enterprise Edition (REE) 1.8.6 into its own directory, and run the deployed Rails application using REE. REE is Ruby with some garbage collection improvements to allow memory page reuse across fork()ed subprocesses.

So the Ruby interpreter, Ruby packages and Ruby gems already installed to the system won't be used or needed. But some libraries (such as SQLite client and MySQL client) will be used. You have to install those libraries including the development packages using the system's package manager. Example:
# apt-get install libsqlite3-dev
# apt-get install libpq-dev # optional, needed if Rails app connects to PostgreSQL
# apt-get install libmysqlclient15-dev # needed if app connects to MySQL
You also have to install some development packages in order to be able to compile and install REE. Do this:
# apt-get install wget gcc g++ make libc6-dev libreadline5-dev zlib1g-dev libssl-dev
Should other packages be missing, the installer script run below will tell you the apt-get install command to run.

You can get the REE installer tarball from http://www.rubyenterpriseedition.com/. Feel free to substitute any newer ruby-enterprise-*.tar.gz. filename to the command below. To download and install REE (including a bundled Ruby, rubygems, Rake, Rails and Ruby client modules for SQLite 3, MySQL and PostgreSQL), run this:
# cd /usr/src
# wget http://rubyforge.org/frs/download.php/55511/\
ruby-enterprise-1.8.6-20090421.tar.gz
# tar xzvf ruby-enterprise-1.8.6-20090421.tar.gz
# ruby-enterprise-1.8.6-20090421/installer \
-c--enable-pthread -a/usr/local/ruby-enterprise-1.8.6
# strip /usr/local/ruby-enterprise-1.8.6/bin/ruby
The installer is an interactive script, and it would have asked questions and waited for you to press Enter occasionally if you hadn't called it with the -a flag. Nevertheless, it show you some nice, colorful messages indicating progress. In a few minutes, it finishes compilation and installation to /usr/local/ruby-enterprise-1.8.6. Please note that the installer runs /usr/local/ruby-enterprise-1.8.6/gem install to download, compile and install some gems (Ruby modules). Please note that -c--enable-pthread is necessary to work around a bug in ruby-enterprise-1.8.6-20090421.tar.gz, which causes fork()ed subprocesses to exit early with SIGVTALRM.

Scroll up and check that the MySQL and PostgreSQL Ruby client modules were installed properly. If not (and you need those), then apt-get install the necessary libraries, and run the installer or gem install again. Example command lines (optional, needed only if you need MySQL or PostgreSQL and the installer failed the compile the relavant Ruby modules):
# apt-get install libmysqlclient15-dev
# /usr/local/ruby-enterprise-1.8.6/bin/gem install --no-ri --no-rdoc mysql

# apt-get install libpq-dev
# /usr/local/ruby-enterprise-1.8.6/bin/gem install --no-ri --no-rdoc postgres
If you have any other gems needed by your Rails application, install them now (similarly to the postgres gem above). Please note that all gems should be installed as root in the setup described in this tutorial, and gems are shared among Rails applications.

Create a user (non-root) and download your Rails application to the user's home (e.g. /home/myrails/myapp). The best way to download the application is the checkout function of a version control system (such as Subversion), because using that you can easily re-deploy the application later. (Please note that Capistrano can be used to fully automate Rails application deployment, but this is beyond the scope of this tutorial.) If you have multiple Rails applications, you can create as many users as you want, and map applications to users freely. The application would run in production as the UID of that user (myrails). As myrails, add these lines to /home/myrails/.bash_profile:
export GEM_HOME=/usr/local/ruby-enterprise-1.8.6/lib/ruby/gems/1.8
export PATH="/usr/local/ruby-enterprise-1.8.6/bin:$PATH"
Log in again, and check that type -p ruby gem rake rails prints a filename inside /usr/local/ruby-enterprise-1.8.6. Make sure that all scripts in /home/myrails/myapp/scrit/* start with /usr/bin/env ruby, and they don't have a specific Ruby interpreter (such as /usr/bin/ruby) hardcoded. Create the initial database of your Rails application (by running rake db:migrate etc.). Quickly try your application by starting
/home/myrails/myapp/script/server --environment=production
and visiting http://localhost:3000/ . Try some functionality which accesses the database. As soon as it works fine, stop the server script.

Now it is time to install Apache2 (please note that Apache 1.3 won't suffice, because Phusion Passenger, the Rails--Apache connector this tutorial uses needs Apache 2). Run this:
# apt-get install apache2 apache2-prefork-dev libapr1-dev
# /usr/local/ruby-enterprise-1.8.6/bin/passenger-install-apache2-module -a
The passenger-install script gives you nice, colorful instructions how to configure your Apache. Memorize those instructions, and feel free to use them in place of the instructions given in this tutorial. Configure your Apache2 as usual. (Setting up and populating a DocumentRoot, setting up SSL (https://) and setting up Apache VirtualHost entries is not covered in this tutorial.) Create file /etc/apache2/mods-available/passenger.conf containing the right paths, for example (type it without the line break):
PassengerRoot /usr/local/ruby-enterprise-1.8.6/lib/ruby/gems/1.8/gems
/passenger-2.2.2
PassengerRuby /usr/local/ruby-enterprise-1.8.6/bin/ruby
Crete file /etc/apache2/mods-available/passenger.load containing the right paths, for example (type it without the line break):
LoadModule passenger_module /usr/local/ruby-enterprise-1.8.6/lib/ruby/gems/
1.8/gems/passenger-2.2.2/ext/apache2/mod_passenger.so
Run this:
# ln -s ../mods-available/passenger.load /etc/apache2/mods-enabled/
# ln -s ../mods-available/passenger.conf /etc/apache2/mods-enabled/
Restart apache with
# /etc/init.d/apache2 restart
Wait about 60 seconds, and make sure you don't get any Passenger-related error messages at the end of /var/log/apache2/error.log.

If you want to deploy your Rails application to the root URI of an Apache VirtualHost, all you have to do is specifying
<VirtualHost server.name:80>
ServerName server.name
DocumentRoot /home/myrails/myapp/public
</VirtualHost>
in your /etc/apache2/sites-available/default. Do not specify RailsBaseUrl /, that wouldn't work. Restart Apache, and visit http://server.name/ . The first page download may take a few seconds, because Phusion Passenger starts the Rails application at that time. The latency of further downloads should be negligible. If you get a colorful, but unhelpful error message like The page you were looking for doesn't exist.; You may have mistyped the address or the page may have moved., then visit http://localhost/ instead, on which Phusion Passenger doesn't hide the exception raised by Rails. Examining /var/log/apache2/error.log can also help you diagnose the problem.

If you want to deploy your Rails application to a non-root URI (e.g. http://server.name/myapp), then first make sure that the necessary workarounds are applied. Follow the instructions on http://ptspts.blogspot.com/2009/05/how-to-fix-railsbaseuri-sub-uri-with.html of adding files /home/myrails/myapp/config/initializers/!fix_relative_url_root.rb and /home/myrails/myapp/config/initializers/!relative_url_for.rb. Make sure those files are added to your source code repository as well, if applicable. Then create a symlink to your application's public directory. Assuming that your Apache2 DocumentRoot is /var/www, create the symlink by running
# ln -s /home/myrails/myapp/public /var/www/myapp
Make sure you have these lines in your Apache2 configuration (most probably /etc/apache2/sites-available/default):
DocumentRoot /var/www
<Directory /var/www>
Options +FollowSymlinks
</Directory>
RailsBaseUri /myrails
It's OK to have multiple RailsBaseUri directives, both for multiple or a single application. Restart Apache2 if needed. Visit http://server.name/myrails . If you see an unhelpful error message instead of your application's main page, then visit http://localhost/myrails to get the details. Examining /var/log/apache2/error.log can also help you diagnose the problem.

Please note that if you have mutiple Rails applications (i.e. multiple public directories (after following symlinks)), Phusion Passenger will not
reuse the same Ruby worker process for running multiple applications. Please also note that Ruby worker processes spawned by Phusion Passenger are single-threaded. Phusion Passenger treats two Rails applications the same if they have the same public directory (after following symlinks), so the http:// and https:// versions and aliases of your Rails application may run on the same Ruby worker process.

Rails applications running under Phusion Passenger's control in the production environment don't pick up file changes automatically. So if you change your application's code or config, you have to restart the application: run (as myrails)
# touch /home/myrails/myapp/tmp/restart.txt
and visit the application's URL. The first download should take a few seconds, because Phusion Passenger is restarting your Rails application.

Please refer to Phusion Passenger's documentation for more information about running your Rails application with Phusion Passenger with an Apache2 frontend.