sobota, 19 lutego 2011

Multiseat setup - upgrade to Debian wheezy

Not much to write about... Just works.

However, they say that in wheezy gdm will be completely removed in favor of gdm3. So, an investigation on gdm replacement with gdm3 is needed. I will post some info when I'm done with that.

środa, 9 lutego 2011

Multiseat setup - the final feast

In this post I would like to tell you about the actual implementation of the multiseat setup which I use.

Here is the tree of the installed files (filenames are links):

/etc
 |
 +-/default
 |  |
 |  +-multiseat
 |
 +-/gdm
 |  |
 |  +-gdm.conf
 |
 +-/X11
 |  |
 |  +-xorg.conf
 |
 +-/init.d
 |  |
 |  +-multiseat
 |
 +-/udev
 |  |
 |  +-/rules.d
 |     |
 |     +-00-multiseat.rules
 |
 +-/multiseat
    |
    +-multiseat.conf
    |
    +-/scripts
       |
       +-helper-functions
       |
       +-match-and-name
       |
       +-multiseat-greeter
       |
       +-Xephyr-seat
 

/etc/default/multiseat as of now contains only a path to the multiseat.conf. /etc/gdm/gdm.conf and /etc/X11/xorg.conf have already been explained. /etc/init.d/multiseat contains only one line, and as I experienced it is necessary to trigger the events once more even though they are triggered by the standard startup scripts. In Debian it is possible to issue this command as root:

update-rc.d multiseat start 70 2 .

to install appropriate link to this script. Please bear in mind that this might be old-fashioned as dependency-based init scripts are used quite often nowadays.

/etc/udev/rules.d/00-multiseat.rules has already been explained; its name starts with 00 so that it is the first rules file processed in the rules.d directory.

/etc/multiseat/multiseat.conf deserves a few words of comment. This file needs to follow a few rules. First, it is composed of sections numbered 0..n. Comments are allowed and start with a # in the beginning of the line

The section [0] describes global settings. Right now it only specifies which section describes the primary display, which is not displaced. It is just that: if all Xephyrs are started in the top left corner of the big Xinerama screen, then one of them can stay where it is, and this is considered the primary display.

The remaining sections describe one seat each. In each section the following keys are used:

KEYBOARD-MATCH: a list of newline-separated patterns against which the output of udevadm info --name /dev/input/eventXX will be matched by match-and-name to match and name the keyboard associated with this seat

MOUSE-MATCH: similar to KEYBOARD-MATCH, but refers to the mouse associated with this seat

SIZE: <width>x<height> in pixels of this seat's Xephyr (=> of this seat monitor if you will)

KEYBOARD: used both by match-and-name to name the keyboard associated with this seat, and by Xephyr-seat to tell this seat's Xephyr which keyboard to use

MOUSE: similar to KEYBOARD, but refers to the mouse to use at this seat

LAYOUT: xkb layout of the keyboard at this seat

DISPLACEMENT: dx,dy where to move this Xephyr's seat to (=> position of this Xephyr in the big Xinerama screen)

DPI: the visual resolution of this seat's Xephyr (influences the size of the base font and so on); typically 96

The multiseat.conf file is parsed only by standard tools: bash, awk and sed. I could have used some more fashionable tool like python, but I wanted as little dependencies as possible. However, the limited possibilities of the chosen tools make it a bit awkward to use a sectioned config file, not to mention reading-in the tables. The helper-functions are devoted to ease this task. They use very simple shell scripting and some moderately difficult awk and sed.

The multiseat-greeter script for each seat performs moving the corresponding Xephyr by an offset specified in its DISPLACEMENT. The correct Xephyr is selected by grepping the text "Xephyr on :<number>" where <number> is 1..n (n = number of seats). The comments on xwininfo and wmctrl can be found here.

The Xephyr-seat script detects which seat it is called for by inspecting its command line parameters. Even though in gdm.conf there are no explicit parameters, they are added by gdm and include the display number. Once the seat is known the script can get all the necessary parameters from the configuration file and finally run Xephyr.

I have used this setup for about a month now and it seems quite promising. The system is stable. Logging in and out does not disturb the work of the other user. I noticed that keyboard leds do not function properly (in fact they do not function at all on both keyboards). There is a satisfying workaround: install lock-keys-applet or something similar.

One more remark. The Xinerama screen with a 1920x1080 and 1280x1024 monitor (positioned logically one above the other) takes quite a lot of memory. I had to add vmalloc=162M option to the kernel command line so that the nVidia can handle it. I did not experiment much with the parameter value, though. My system has 4GB of RAM and I am running a 32-bit Debian squeeze on a Core2Duo E7300@2.66GHz. The video card is GeForce 8400GS. Some two years ago this was not such a bad setup. Now it is, if at all, ordinary. Especially the graphics. Anyway, it is good enough for 2 people to use the same computer; in fact I've been running a two seat setup for more than 7 years now, and used to do it also on a weaker machine, a 1GHz Athlon with nVidia MX 200 and an S3 ViRGE.

By the way. Actually the reason to look for a multiseat setup was a contention for the computer. This way I made my wife use Linux. And she doesn't mind;)

Multiseat setup - Xephyr-seat

. /etc/multiseat/scripts/helper-functions

trap "" usr1

XEPHYR=/usr/bin/Xephyr
DISPLAY=:0
XAUTHORITY=/var/lib/gdm/:0.Xauth

SEAT=`echo $* | cut -d" " -f1 | cut -d: -f2`

SIZE=`get_conf $SEAT SIZE`
KEYBOARD=`get_conf $SEAT KEYBOARD`
LAYOUT=`get_conf $SEAT LAYOUT`
MOUSE=`get_conf $SEAT MOUSE`
DPI=`get_conf $SEAT DPI`

exec $XEPHYR -br -screen ${SIZE} -keybd evdev,,device=/dev/input/multiseat/${KEYBOARD},xkbrules=xorg,xkbmodel=evdev,xkblayout=${LAYOUT} -mouse evdev,5,device=/dev/input/multiseat/${MOUSE} -dpi ${DPI} $*

Multiseat setup - multiseat-greeter

. /etc/multiseat/scripts/helper-functions

NUM_SEATS=`get_num_seats`

PRIMARY=`get_conf 0 PRIMARY`

for (( i=1; i<=$NUM_SEATS; i++ ));
do
    if [ $i != $PRIMARY ]; then
        XEP=`XAUTHORITY=/var/lib/gdm/:0.Xauth xwininfo -root -children -display :0 | grep "Xephyr on :$i" --max-count=1`;
        XEPHYR_WIN_ID=`echo ${XEP} | cut -d' ' -f1`;
                DISPLACEMENT=`get_conf $i DISPLACEMENT`
        DISPLAY=:0 XAUTHORITY=/var/lib/gdm/:0.Xauth wmctrl 2>&1 -v -i -r ${XEPHYR_WIN_ID} -e 0,${DISPLACEMENT},-1,-1;
    fi
done

/usr/lib/gdm/gdmgreeter

Multiseat setup - match-and-name

. /etc/multiseat/scripts/helper-functions

EV=$1

#
# $1 device type
# $2 match table size
# $3 event interface id
# $4 device name
#
do_device()
{
    TABLE_NAME=$1
    T_SIZE=$2
    EVENT=$3
    NAME=$4
    # if match table empty then no match
    match=0
    for ((j = 0; j < $T_SIZE; j++)) {
            PATTERN=`get_table $i $TABLE_NAME $j`
            match=0
            /sbin/udevadm info --name=/dev/input/$EVENT --attribute-walk | grep 2>&1 "$PATTERN" >/dev/null || break
            match=1
    }
    if [ $match == 1 ];
    then
        echo $NAME
        return 0
    fi
    return 1
}

SEATS=`get_num_seats`

for ((i = 1; i <= $SEATS; i++)) {
    KEYB_T_SIZE=`get_table $i KEYBOARD_MATCH -size`
    KEYB=`get_conf $i KEYBOARD`
    do_device KEYBOARD_MATCH $KEYB_T_SIZE $EV $KEYB && exit 0

    MOUSE_T_SIZE=`get_table $i MOUSE_MATCH -size`
    MOUSE=`get_conf $i MOUSE`
    do_device MOUSE_MATCH $MOUSE_T_SIZE $EV $MOUSE && exit 0
}

exit 1

Multiseat setup - helper-functions

. /etc/default/multiseat

#
# private implementation of get_sect
#
# get given section's contents from the given config file
#
# $1 filename
# $2 section number
#
__get_sect()
{
    cat $1 | awk -v sect=$2 '
    BEGIN {
        SECT="^\[" sect "\]"
        NEXT_SECT="^\[" (sect + 1) "\]"
    }
    $0 ~ SECT, $0 ~ NEXT_SECT {
        if ($0 ~ SECT)
            next;
        if ($0 ~ NEXT_SECT)
            next;
        if (substr($1, 1, 1) == "#")
            next;
        print
    }
    '
}

#
# private implementation of get_conf
#
# get given key's contents from the given section
# from the given config file
#
# does not work for tables in the config file
#
# $1 filename
# $2 section name
# $3 key name
#
__get_conf()
{
    __get_sect $1 $2 | awk -F"=" -v key=$3 '
    BEGIN {
        KEY=key
    }
    $1==KEY {
        print substr($0, index($0, "=") + 1)
    }
    '
}

#
# private implementation of get_num_seats
#
# get number of seats from the default config file
#
# $1 filename
#
__get_num_seats()
{
    cat $1 | awk '
    BEGIN {
        count=0
    }
    /^\[[0-9]*\]/ {count++}
    END {
        print count - 1
    }
    '
}

#
# get given section's contents from the default config file
#
# $1 section name
#
get_sect()
{
    __get_sect $CONFIG $1
}

#
# private implementation of get_table
#
# $1 filename
# $2 section number
# $3 table name
# $4 size/entry switch [-size => size, <number> for entry]
#
__get_table()
{
    SIZE=0;
    ENTRY=-1

    if [ $4 == "-size" ];
    then
        SIZE=1;
    else
        ENTRY=$4
    fi

    RESULT=`__get_sect $1 $2 | awk -v table=$3 -v get_size=$SIZE -v entry=$ENTRY '
    BEGIN {
        print BEGIn
        TABLE=table "\=" "\("
        GET_SIZE=get_size
        ENTRY=entry
        size=0
    }
    $0 ~ TABLE, /^\)/ {
        if ($0 ~ TABLE)
            next;
        if ($0 ~ /^\)/)
            next;
        if (substr($1, 1, 1) == "#")
            next;
        if (get_size == 0 && size == entry){
                print
                exit
        }
        size++;
    }
    END {
        if (get_size)
            print size;
    }
    '`
    RESULT=`echo $RESULT | sed 's/^[ \t]*//g'`

    echo $RESULT
}

#
# get given key's contents from the given section
# from the default config file
#
# does not work for tables in the config file
#
# $1 section name
# $2 key name
#
get_conf()
{
    __get_conf $CONFIG $1 $2
}

#
# get number of seats from the default config file
#
get_num_seats()
{
    __get_num_seats $CONFIG
}

#
# get size of a given table in the given section of the config file or
# get specified entry of the table
#
# $1 section number
# $2 table name
# $3 size/entry switch [-size => size, <number> for entry]
#
get_table()
{
    __get_table $CONFIG $1 $2 $3
}

Multiseat setup - multiseat.conf

# global configuration
[0]
PRIMARY=2

# PHILIPS 107T + PS/2 KBD + PS/2 M
[1]
KEYBOARD_MATCH=(
 DRIVERS=="atkbd"
)
MOUSE_MATCH=(
 DRIVERS=="psmouse"
)
SIZE=1280x1024
KEYBOARD=PS2_keyboard
MOUSE=PS2_mouse
LAYOUT=pl
DISPLACEMENT=0,1080
DPI=96

# SAMSUNG SM EX2220 + USB KBD + USB M
[2]
KEYBOARD_MATCH=(
 ATTRS{product}=="USB Keyboard"
# this keyboard actually has 2 USB interfaces;
# the "00" one is of intrest
 ATTRS{bInterfaceNumber}=="00"
)
MOUSE_MATCH=(
 ATTRS{product}=="USB-PS/2 Optical Mouse"
)
SIZE=1920x1080
KEYBOARD=USB_keyboard
MOUSE=USB_mouse
LAYOUT=pl
DISPLACEMENT=0,0
DPI=96


Multiseat setup - 00-multiseat.rules

KERNEL=="event[0-9]*" SUBSYSTEM=="input" PROGRAM="/etc/multiseat/scripts/match-and-name %k" SYMLINK+="input/multiseat/%c"
 

Multiseat setup - init.d/multiseat

/sbin/udevadm trigger
 

Multiseat setup - default/multiseat

CONFIG=/etc/multiseat/multiseat.conf
 

Multiseat setup - xorg.conf

Section "ServerLayout"
    Identifier     "Layout0"
    Screen      0  "Screen0" 0 0
    Option         "Xinerama" "0"
EndSection

Section "Files"
EndSection

Section "Module"
    Load           "dbe"
    Load           "extmod"
    Load           "type1"
    Load           "freetype"
    Load           "glx"
EndSection

Section "ServerFlags"
    # start even without the mouse
    Option "AllowMouseOpenFail" "yes"
    # disable VT switching
    #Option "DontVTSwitch" "yes"
    # Ctrl+Alt+Backspace disable
    #Option "DontZap" "yes"
EndSection

Section "Monitor"
    # HorizSync source: edid, VertRefresh source: edid
    Identifier     "Monitor0"
    VendorName     "Unknown"
    ModelName      "Philips 170B4"
    HorizSync       30.0 - 82.0
    VertRefresh     56.0 - 76.0
    Option         "DPMS"
EndSection

Section "Device"
    Identifier     "Device0"
    Driver         "nvidia"
    VendorName     "NVIDIA Corporation"
    BoardName      "GeForce 8400 GS"
EndSection

Section "Screen"
    Identifier     "Screen0"
    Device         "Device0"
    Monitor        "Monitor0"
    DefaultDepth    24
    Option         "TwinView" "1"
    Option         "TwinViewXineramaInfoOrder" "DFP-0"
    Option         "metamodes" "CRT: nvidia-auto-select +0+1080, DFP: nvidia-auto-select +0+0"
    SubSection     "Display"
        Depth       24
    EndSubSection
EndSection

Multiseat setup - gdm.conf

 [daemon]
Greeter=/etc/multiseat/scripts/multiseat-greeter

[security]
AllowRoot=true

[xdmcp]

[gui]

[greeter]
Browser=false

[chooser]

[debug]

[servers]

0=Xinerama
1=Xephyr
2=Xephyr

[server-Xinerama]
name=Xinerama
command=/usr/bin/X -br -dpms -s 0
handled=false
flexible=false

[server-Xephyr]
name=Xephyr
command=/etc/multiseat/scripts/Xephyr-seat
handled=true
flexible=false

wtorek, 8 lutego 2011

Multiseat setup - the ingredients IV

In this post I would like to talk about how udev can be useful in a multiseat setup.

Since the very beginning all the methods to make a multiseat Linux, perhaps with the exception of Backstreet Ruby (which I had never been able to run successfully) rely on having X servers read the so-called event interface rather than the traditional kernel interface to read keyboards and mice (which, I think, that by design are assumed to be single instance).

This event interface is exposed to user space in /dev/input/event<number>. The important thing here is to note that each mouse or keyboard has at least one corresponding node in /dev/input - my USB keyboard for example has two, but any node corresponds to exactly one device. So while running the multiple X servers (Xephyrs in this approach) one must specify which event interface to use as input for mouse and which for keyboard. Until fairly recently only the regular X had had an option of specifying the mouse/keyboard event interface. Things have changed and Xephyr now also supports this. It would seem that that is all we need. However, things are a little bit more complicated.

The envent interface node names (/dev/input/eventXX) might change across system reboot. They do tend to be the same, but there is no absolute guarantee. First, the USB devices might be plugged to different USB ports each time, and second you could boot your computer with or without your 5 pendrives plugged in. Both of the above can cause the USB enumeration to proceed differently and so the different names. It is not just theory. Until I learned how to cope with this situation I did sometimes experience this kind of problem.

On my Debian system, in the /dev/input directory there are 2 subdirectories: by-id and by-path. They contain symbolic links to actual /dev/input/eventXX nodes. The former does not include the PS/2 mouse/keyboard. The latter contains all the devices, but the node names are related to the actual ports the devices are connected to. Probably one could specify PS/2 mouse/keyboard using the respective by-path entries and USB mice/keyboards using the respective by-id entries. In fact this is not so bad. However, I thought that the by-id and by-path can be considered some special views on the event interface. And that multiseat setup deserves a view on its own. And thanks to such a view all the links would be gathered in one directory nicely named "/dev/input/multiseat".

Here you can find a tutorial on udev. I am not going to explain it. Rather I will concentrate on explaining just one rule:

KERNEL=="event[0-9]*" SUBSYSTEM=="input" PROGRAM="/etc/multiseat/scripts/match-and-name %k" SYMLINK+="input/multiseat/%c"

The KERNEL and SUBSYSTEM are clear. More interesting here is the PROGRAM. It specifies a program which is run every time the rule is tried. The %k parameter is automatically replaced with the actual event interface node name (e.g. /dev/input/event3) which is currently processed. If the program exits with 0, then the rule matches, and if not then the rule does not match. If the rule matches, then the SYMLINK action is taken, and what the program prints on its standard output is available in the %c. The symlink action creates a symbolic link relative to the /dev/input.

Armed with this tool we can design the PROGRAM in such a way that it matches the event interface node name passed as argument with the contents of some configuration file. If the match is successful, then print on standard output the configured name for the associated device. In this solution, the said configuration file is common to this udev rule and to the Xephyr-seat and multiseat-greeter scripts used to start Xephyrs and login screens.

This post is long enough ;) But prepare for the great feast. Stay hungry....that is... tuned.

poniedziałek, 7 lutego 2011

Multiseat setup - the ingredients III

In this post I would like to talk about placing the Xephyrs in desired positions. The idea is explained here and rather than repeating it I would like to make some comments on it.

Now let's have a look on the relevant part of gdm.conf:

[daemon]
Greeter=/etc/multiseat/scripts/multiseat-greeter

[server-Xinerama]
name=Xinerama
command=/usr/bin/X -br -dpms -s 0
handled=false
flexible=false

[server-Xephyr]
name=Xephyr
command=/etc/multiseat/scripts/Xephyr-seat
handled=true
flexible=false

The Greeter is the program responsible for starting the login screen inside X. Instead of the default we will use a wrapper script. But we have n+1 Xes in a n-seat setup! The greeter will be run by gdm only on those servers, which are "handled=true", and those are only the Xephyrs. The purpose of the wrapper script is to position the Xephyrs on their corresponding monitors.

This is achieved with the help of 2 programs: xwininfo and wmctrl. The wmctrl, among other things, can position a window in an X server. The option -v tells it to be verbose. The option -i tells it that the window on which the action is to be taken will be specified by a numeric ID (hexadecimal). The option -e tells the window to resize and/or move to a specified place, this option's arguments are: window gravity (0), x, y of the new position, w, h of the new window dimensions (-1 to keep old values). As we can see to make wmctrl do the job for us we need to know two pieces of information: 1) which window to operate on and 2) where to move the window to. The 2) will be obtained from the config file. We will come back to it later when we talk about integration with udev. And to get 1) we will use xwininfo.

Remember the Xauthority? It comes in handy again. Here is the story. xwininfo can query an X server, for example to get a list of the root window's children. The children of our main [server-Xinerama] X happen to be the Xephyrs. But the X server will not tell its secrets to anyone, but only to those who authenticate themselves properly - our friend Xauthority will handle this. The option -root tells xwininfo to use the X root window as the target window. The option -children tells it to list the target window children. The option -display specifies the DISPLAY to use. As a result something on the lines of the below is returned:

xwininfo: Window id: 0x1fc (the root window) (has no name)

  Root window id: 0x1fc (the root window) (has no name)
  Parent window id: 0x0 (none)
     2 children:
     0x400002 "Xephyr on :2.0 (ctrl+shift grabs mouse and keyboard)": ("Xephyr" "Xephyr")  1920x1080+0+0  +0+0
     0x200002 "Xephyr on :1.0 (ctrl+shift grabs mouse and keyboard)": ("Xephyr" "Xephyr")  1280x1024+0+1080  +0+1080

We use grep to extract the line corresponding to the Xephyr we are moving and with cut (or awk) we extract the numerical window ID. Armed with this information we can use wmctrl. Again, it can operate on windows in a specified X, so we need to tell it which, and we do it by specifying the DISPLAY 0 and, of course, Xauthority of the [server-Xinerama]. After wmctrl has moved the Xephyr we can start the greeter program inside it. In GNOME this is /usr/lib/gdm/gdmgreeter.

Now the time has come to talk about the promised single configuration file common for relevant udev parts and for the multiseat setup. Stay tuned.

niedziela, 6 lutego 2011

Multiseat setup - the ingredients II

In this post I would like to talk about a number of Xephyrs running inside one X server.

Xephyr itself is just an X application. The fact that it actually is also an X server is not relevant as far as running a number of Xephyrs in one X server is concerned.

Let's start from the gdm.conf, which is responsible for starting a number of Xephyrs. Each Xephyr is not started directly, but with a wrapper script, Xephyr-seat:

[servers]

0=Xinerama
1=Xephyr
2=Xephyr


[server-Xephyr]
name=Xephyr
command=/etc/multiseat/scripts/Xephyr-seat
handled=true
flexible=false

In my setup there is no separate section for each seat's Xephyr, instead the servers 1 and 2 use the same config section [server-Xephyr]. Other difference to netpatia is that no command line parameters are passed to the Xephyr-seat script - they are obtained inside the said script (we will come back to it later when talking about configuration integration with udev). And here comes the Xauthority I talked about last time - inside the Xephyr-seat script two env variables are defined: DISPLAY=0 and XAUTHORITY=/var/lib/gdm/:0.Xauth, which are interpreted by Xephyr. Remember, that we want to put a number (e.g. 2) of Xephyrs inside a running X.  To display an application in a running X we need to authenticate to it and we do it by telling the X on which display we want to have the applications and where is the Xauthority.

What the Xephyr-seat wrapper effectively does is just call the Xephyr binary with appropriate parameters. The Xephyr binary shows just an empty window and it is the gdm that actually starts a login screen (e.g. gdmgreeter) inside each server specified in the [servers] section of gdm.conf.

Here is a nice picture of 4 Xephyrs with an own instance of gdmgreeter each, run on one wide monitor. As you can see there is no Xinerama - but as far as multiple Xephyrs inside one X are concerned, how many monitors are used is of no concern. The beauty of the Xephyr multiseat solution is that covering multiple screens is purely orthogonal to running multiple Xephyrs.



As it turns out, Xephyr does not have any command line parameters suitable to placing it in a specific position inside its surrounding X. So to make them occupy one monitor each we need to resort to some tricks. Again, recall the Xauthority. Stay tuned.

sobota, 5 lutego 2011

Multiseat setup - the ingredients

The idea is simple: run one X server covering all the monitors and inside it run Xephyr instances covering one monitor each.

In this post I would like to talk about one X server covering all the monitors.

One X server covering all the monitors can be done with Xinerama. I'm not going to talk about configuring Xinerama here. For my nVidia the relevant xorg.conf looks like this.


The important thing here is the  ServerLayout section and the Screen section. In the ServerFlags section the AllowMouseOpenFail is important. As you can see no input devices are specified: recent X versions will use the defaults (which are not useful for us anyway).

Now a few words about the X covering all the monitors. I would like to comment on what is written here. In gdm's configuration file (/etc/gdm/gdm.conf) there can be specified a number of servers to start in the [servers] section: <display number>=<server config section name>. For later let's remember that gdm can start a number of X servers described by just one server config section. This is the relevant part of the gdm.conf in my setup:

[servers]

0=Xinerama
1=Xephyr
2=Xephyr

[server-Xinerama]
name=Xinerama
command=/usr/bin/X -br -dpms -s 0
handled=false
flexible=false

[server-Xephyr]
name=Xephyr
command=/etc/multiseat/scripts/Xephyr-seat
handled=true
flexible=false

The server "0" is started using the [server-Xinerama] section on DISPLAY 0 with black background (-br), screen saver deactivated (-s 0), dpms on. The gdm creates the Xauthority file for this server in /var/lib/gdm/:0.Xauth. Nice explanation of Xauthority is here. netpatia blog does not say anything about it and this file will be very important while configuring the Xephyrs. Stay tuned.



Multiseat setup

Hi All, Now that I've used it for perhaps 8 years or so,  I would like to say something about multiseat Linux setup.

Back in the days of 2.4 I had the most stable setup, as described by Miguel Freitas: http://cambuca.ldhs.cetuc.puc-rio.br/multiuser/. It required two distinct graphics adapters (one multihead adapter is no good because separate BusIDs are required for each seat) and a patched X server so that keyboards/mice were read using the event interface. It was so stable that I did resist to move to 2.6... With 2.6 Miguel's solution was not so straightforward and required kernel patching, too. Some time later I decided I wanted 2.6 and in fact having a multiseat setup with Debian was possible just out of the box - something very desirable. It was described in numerous places. The main idea still was to use the event interface (then already available with modular X) and to start two X instances with "isolateDevice" options. It did work quite fine, although not as stable as Miguel's solution. There was a problem, though. The multiseat setup worked in certain
hardware configurations, and did not work in others. Worked for me ;) But only until I attempted upgrading to squeeze. Then suddenly things broken. I am almost sure it has to to with the RAC (resource access control) removal from the X: from the architecture point of view this was good, but for my setup it wasn't.

Then I recalled another way to do it: use Xephyr. It's not my invention; you can find nice descriptions for example here; nVidia specific part of the setup here; ATI specific description here. The disadvantage of the Xephyr approach is that we do not use hardware acceleration of graphics (who cares? it's still possible to watch youtube and play DVDs just fine). The advantage is that only one dual-head graphics adapter (for 2 seats) is enough - no need to use ancient PCI cards like S3 ViRGE.

I'm not going to repeat what others have already explained. Rather I would like to comment a little on what can be found on the netpatia blog. Apart from that I would like to talk about how I used udev instead of statically specifying keyboards and mice, and how I integrated relevant configuration of udev and multiseat into one config file. Stay tuned.