User Tools

Site Tools


user:ryoung12:multiseat

Multiseat Configuration on Ubuntu via USB

What is Multiseat?

Multiseat configurations allow multiple people to use one computer at the same time. The way this works, is each user has their own monitor, keyboard, and mouse, which are collectively known as a seat. The multi prefix to seat, comes from their being multiple monitors, keyboards, and mice connected to a single computer. By being able to share the resources of the computer, each user is able to work independently from the other users, much in the same fashion as if each person had their own computer.

Getting Started

The methods for configuring multiseat in this article focus on using an older version of the Ubuntu distribution. Specifically, 9.04 or before. The reason for this is, we will be relying on the use of GDM 2.20 or earler. Ubuntu 9.10 and later, include a newer version of the GDM which no longer supports multiseat configurations directly. However, it should be possible to downgrade your GDM if desired.

To see what version of GDM your machine is running, you can use the following command:

$ gdm -version

In this tutorial, we will be making use of the “USB 2.0 Universal Docking Station” from Plugable.com (Model# UD-165). It is a USB docking station that has a DVI port for the internal DisplayLink USB video adapter for connecting to your monitor, and extra USB ports for connecting your keyboard and mouse. By having all three on the same root USB device, it will greatly simplify the means of mapping the monitors, keyboards, and mice to the proper seat.

The remainder of this tutorial will be tailored to a fresh installation of Ubuntu 9.04 and having applied the latest updates using aptitude update and aptitude safe-upgrade. It is also assumed you are using GDM version 2.20.

While support for the USB DisplayLink FrameBuffer Driver (udlfb) is in the staging tree of Linux Kernels 2.6.32 and later, it is recommended to use the most recent version of the udlfb driver. In order download the source files from their repository, and then compile the driver, the Module Assistant and Git packages will need to be installed.

To install and prepare the Module Assistant and Git packages, you can use the following command:

$ sudo aptitude update && aptitude install -y module-assistant git-core
$ sudo module-assistant prepare

Using the following commands, download, compile, and install the udlfb driver.

$ mkdir ~/git && cd ~/git
$ git clone http://plugable.com/webdav/udlfb/
$ cd udlfb
$ make && sudo make install && make clean
$ sudo depmod -a

When this step is completed, any time you reboot your computer with a monitor connected via a DisplayLink adapter, the screen should be a solid green color. Next, we need to configure an X Server for use with our new driver. To do this, we will need to have the PKG Config and Xorg Development packages installed.

To install the PKG Config and Xorg Development packages, you can use the following command:

$ sudo aptitude install pkg-config xorg-dev

Using the following commands, download, configure, and install the DisplayLink X Server

$ cd ~/git
$ git clone http://git.plugable.com/webdav/xf-video-udlfb/
$ cd xf-video-udlfb
$ ./configure
$ make && sudo make install && make clean

Downloading the Multiseat Config Files

After having created and installed the DisplayLink drivers and an X Server for the DisplayLink adapter, we need to begin configuring the system for multiseat operation. Rather than manually typing or copy/pasting the required shell scripts and other configuration files that make multiseat work, lets save time and effort by downloading them.

To download the files, use the following command:

$ cd ~/git
$ get clone http://git.plugable.com/webdav/misc-udlfb/

The usbseat.rules udev script

We need to copy the file 50-usbseat.rules we just downloaded from misc-udlfb into /lib/udev/rules.d/ and make sure it has the right owner, group, and read permissions. This udev script will run each time a USB device is attached to the system to see if it is one of our USB docking stations.

Using the following commands, copy the file into the directory, and set the owner/group and permissions.

$ cd /lib/udev/rules.d
$ sudo cp ~/git/misc-udlfb/usbseat/50-usbseat.rules .
$ sudo chown root 50-usbseat.rules && sudo chgrp root 50-usbseat.rules
$ sudo chmod 644 50-usbseat.rules

Below is the 50-usbseat.rules file at the time of writing.

50-usbseat.rules
# set all DisplayLink devices to configuration 1
# see http://libdlo.freedesktop.org/wiki/DeviceQuirks for more info
ATTR{idVendor}=="17e9", ATTR{bConfigurationValue}=="2", RUN="/bin/echo 1 > /sys%p/bConfigurationValue"
 
# aliases for display, kbd, mouse attached to specific hubs
 
KERNEL=="fb*",SUBSYSTEMS=="usb",PROGRAM="/bin/cat /sys/%p/../../../devnum",SYMLINK+="usbseat/%c/display",RUN+="usbseat.sh %c"
KERNEL=="mouse*", SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="03", ATTRS{bInterfaceProtocol}=="02", PROGRAM="/bin/cat /sys/%p/../../../../../devnum",SYMLINK+="usbseat/%c/mouse",RUN+="usbseat.sh %c"
KERNEL=="event*", SUBSYSTEM=="input", ATTRS{bInterfaceClass}=="03", ATTRS{bInterfaceProtocol}=="01",PROGRAM="/bin/cat /sys/%p/../../../../../devnum",SYMLINK+="usbseat/%c/keyboard",RUN+="usbseat.sh %c"
KERNEL=="control*", SUBSYSTEM=="sound", SUBSYSTEMS=="usb", PROGRAM="/bin/cat /sys/%p/../../../../../devnum", SYMLINK+="usbseat/%c/sound"
 
# and for when the keyboard and mouse are one more hub downstream. Relying on pnp order to have already set up mouse, keyboard on upstream hub if we're daisy-chaining
KERNEL=="event*", SUBSYSTEM=="input", ATTRS{bInterfaceClass}=="03", ATTRS{bInterfaceProtocol}=="01",PROGRAM="/bin/cat /sys/%p/../../../../../../devnum",SYMLINK+="usbseat/%c/keyboard",RUN+="usbseat.sh %c"
KERNEL=="mouse*", SUBSYSTEMS=="usb", ATTRS{bInterfaceClass}=="03", ATTRS{bInterfaceProtocol}=="02", PROGRAM="/bin/cat /sys/%p/../../../../../../devnum",SYMLINK+="usbseat/%c/mouse",RUN+="usbseat.sh %c"

The usbseat.sh shell script

We also need to copy the file usbseat.sh from our misc-udlfb download into /lib/dev/ and set the owner/group and read permissions. This shell script is called each time the 50-usbseat.rules udev script is run to map the correct monitor, keyboard, and mouse from the docking station to the seat that will be created for it.

Using the following commands, copy the file into the directory, and set the owner/group and permissions.

$ cd /lib/udev/
$ sudo cp ~/git/misc-udlfb/usbseat.sh .
$ sudo chown root usbseat.sh && sudo chgrp root usbseat.sh
$ sudo chmod 755 usbseat.sh

Below is the usbseat.sh file at the time of writing.

usbseat.sh
#!/bin/bash
# takes the "seat number" as parameter $1
# the seat number is the kernel device id of the hub the seat's devices are sitting off of
# called once for every usb device that MIGHT be part of a seat, when they arrive or remove
 
if [[ !(-n `/bin/pidof gdm`) ]]; then
    exit 0
fi
 
seat_running=`/usr/bin/gdmdynamic -l | /bin/sed -n -e "/:$1,/p"`
 
# $ACTION environment variable is set by udev subsystem
case "$ACTION" in
    'remove')
        if [[ -n "{$seat_running}" ]]; then
            /usr/bin/gdmdynamic -v -d $1
        fi
        ;;
    *)
                # A device which might be part of a seat has been added
 
        # if we already have a running seat for this #, exit
        if [[ -n "${seat_running}" ]]; then
            exit 0
        fi
 
        if [[ -e /dev/usbseat/$1/keyboard && -e /dev/usbseat/$1/mouse && -e /dev/usbseat/$1/display ]]; then
 
            # We have a newly complete seat. Start it.
            TMPFILE=`/bin/mktemp` || exit 1
            /bin/sed "s/%ID_SEAT%/$1/g" < /lib/udev/usbseat-xf86.conf.sed > $TMPFILE
            /usr/bin/gdmdynamic -v -t 2 -s 1 -a "$1=/usr/X11R6/bin/X -br :$1 vt07 -audit 0 -config $TMPFILE"
            #/usr/bin/gdmdynamic -v -t 2 -s 1 -a "$1=/usr/X11R6/bin/X -br :$1 vt07 -audit 0 -nolisten tcp -config $TMPFILE"
#           /usr/bin/gdmdynamic -v -t 2 -s 1 -a "$1=/usr/X11R6/bin/X -br :$1 -audit 0 -nolisten tcp -novtswitch -sharevts -config $TMPFILE"
 
            /usr/bin/gdmdynamic -v -r $1
        fi
        ;;
esac
 
exit 0

Xorg Configuration File

<warp indent>Next we need to copy the file usbseat-xf86.conf.sed from our misc-uldfb download into /lib/udev/ and set the owner/group and read permissions. This stream editor script will make the necessary changes or our X.org configuration file (xorg.conf) as seats are added or removed from the computer, instead of having to manually update it ourselves as changes are made.</wrap>

Using the following commands, copy the file into the directory, and set the owner/group and permissions.

$ cd /lib/udev/
$ sudo cp ~/git/misc-udlfb/usbseat-xf86.conf.sed .
$ sudo chown root usbseat-xf86.conf.sed && sudo chgrp root usbseat-xf86.conf.sed
$ sudo chmod 644 usbseat-xf86.conf.sed

Below is the usbseat-xf86.conf.sed file at the time of writing.

usbseat-xf86.conf.sed
Section "ServerFlags"
    Option  "AutoEnableDevices" "false"
    Option  "AutoAddDevices"    "false"
    Option  "DefaultLayout"     "seat"
    Option  "DontZoom"      "true"
    Option  "DontZap"       "true"
    Option  "AllowMouseOpenFail"    "yes"
EndSection
 
Section "Module"
    Load "ddc"
EndSection
 
Section "Files"
    ModulePath      "/usr/lib/xorg/modules"
    ModulePath      "/usr/local/lib/xorg/modules"
EndSection
 
Section "Device"
    Identifier "dl"
    Driver     "displaylink"
#   Option "rotate" "CCW"
    Option "fbdev"  "/dev/usbseat/%ID_SEAT%/display"
EndSection
 
Section "InputDevice"
    Identifier "keyboard"
    Driver  "evdev"
    Option  "CoreKeyboard"
    Option  "Device"    "/dev/usbseat/%ID_SEAT%/keyboard"
    Option  "XkbModel"  "evdev"
    Option  "XkbLayout" "us"
    # Suggested options for removing keyboard input quirks
    Option  "evBits"    "+1"
    Option  "keyBits"   "~1-255 ~352-511"
    Option  "Pass"      "3"
EndSection
 
Section "InputDevice"
    Identifier "mouse"
    Driver  "mouse"
    Option  "CorePointer"
    Option  "Protocol" "ImPS/2"
    Option  "Device"    "/dev/usbseat/%ID_SEAT%/mouse"
    Option  "Buttons" "5"
    Option  "ZAxisMapping" "4 5"
EndSection
 
Section "Monitor"
    Identifier "monitor"
EndSection
 
Section "Screen"
    Identifier "screen"
    Device "dl"
    Monitor "monitor"
EndSection
 
Section "ServerLayout"
    Identifier "seat"
    Screen  0 "screen" 0 0
    InputDevice "keyboard" "CoreKeyboard"
    InputDevice "mouse" "CorePointer"
EndSection

Modifying the User Run Level Scripts

We need to add a few lines to the end of the /etc/rc.local script file, to tell the computer to check for any USB docking stations that are connected before loading the GDM.

Open /etc/rc.local in your text editor of choice, and add the following lines to the end of the file just before the line exit 0, then save and close the file.

oldIFS=$IFS
IFS=/
for seat in /dev/usbseat/*; do
	set $seat
	/lib/udev/usbseat.sh $4
done
IFS=$oldIFS

Below is the a sample of the rc.local file, after inserting the above referenced lines of code.

rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
oldIFS=$IFS
IFS=/
for seat in /dev/usbseat/*; do
    set $seat
    /lib/udev/usbseat.sh $4
done
IFS=$oldIFS
 
exit 0

Patching the GDM

We need to tell the GDM, and Xorg that we will be running more than one X Server when one or more docking stations are attached It would also be nice to keep the ability of the system to function without any docking stations attached. By adding a few lines to the file gdm in /etc/init.d/, we add this capability.

Open up /etc/init.d/gdm in your text editor of choice, and look for a section, “Allow ccd to override the config” (about lines 30-35). After the closing fi for that section, add the following lines, then save and close the file.

# Allow usbseat to override the config
if [ -f /etc/gdm/gdm-usbseat.conf ]; then
	for usbseat in /dev/usbseat/*; do
		seatid=${usbseat##*/}
		if [ -e "/dev/usbseat/$seatid/keyboard" -a -e "/dev/usbseat/$seatid/mouse" -a -e "/dev/usbseat/$seatid/display" ]; then
			CONFIG_FILE="--config=/etc/gdm/gdm-usbseat.conf"
		fi
	done
fi

Below is a sample /etc/init.d/gdm file after adding the lines of code above.

gdm
#! /bin/sh
#
# Originally based on:
# Version:  @(#)skeleton  1.8  03-Mar-1998  miquels@cistron.nl
#
# Modified for gdm, Steve Haslam <steve@arise.dmeon.co.uk> 14mar99
# modified to remove --exec, as it does not work on upgrades. 18jan2000
# modified to use --name, to detect stale PID files 18mar2000
# sleep until gdm dies, then restart it 16jul2000
# get along with other display managers (Branden Robinson, Ryan Murray) 05sep2001
 
set -e
 
# To start gdm even if it is not the default display manager, change
# HEED_DEFAULT_DISPLAY_MANAGER to "false."
HEED_DEFAULT_DISPLAY_MANAGER=true
DEFAULT_DISPLAY_MANAGER_FILE=/etc/X11/default-display-manager
PATH=/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/gdm
PIDFILE=/var/run/gdm.pid
UPGRADEFILE=/var/run/gdm.upgrade
 
if [ -e $UPGRADEFILE -a "$1" != "restart" -a "$1" != "force-reload" ]; then
    SSD_ARG="--startas $DAEMON"
    rm -f $UPGRADEFILE
else
    SSD_ARG="--exec $DAEMON"
fi
 
# Allow cdd to override the config
if [ -f /etc/gdm/gdm-cdd.conf ]; then
    CONFIG_FILE="--config=/etc/gdm/gdm-cdd.conf"
fi
 
# Allow usbseat to override the config
if [ -f /etc/gdm/gdm-usbseat.conf ]; then
    for usbseat in /dev/usbseat/*; do
        seatid=${usbseat##*/}
        if [ -e "/dev/usbseat/$seatid/keyboard" -a -e "/dev/usbseat/$seatid/mouse" -a -e "/dev/usbseat/$seatid/display" ]; then
            CONFIG_FILE="--config=/etc/gdm/gdm-usbseat.conf"
        fi
    done
fi
 
test -x $DAEMON || exit 0
 
if [ -r /etc/default/locale ]; then
  . /etc/default/locale
  export LANG LANGUAGE
elif [ -r /etc/environment ]; then
  . /etc/environment
  export LANG LANGUAGE
fi
 
. /lib/lsb/init-functions
 
case "$1" in
  start)
        if grep -wqs text /proc/cmdline; then
        log_warning_msg "Not starting GNOME Display Manager (gdm); found 'text' in kernel commandline."
    elif [ -e "$DEFAULT_DISPLAY_MANAGER_FILE" -a "$HEED_DEFAULT_DISPLAY_MANAGER" = "true" -a "$(cat $DEFAULT_DISPLAY_MANAGER_FILE 2>/dev/null)"
        log_warning_msg "Not starting GNOME Display Manager (gdm); it is not the default display manager."
    else
        if [ -z "$SPLASH_ORIG_CONSOLE" ]; then
            log_begin_msg "Starting GNOME Display Manager..."
        fi
        # if usplash is running, make sure to stop it now, yes "start" kills it.
        if [ "$SPLASH_ORIG_CONSOLE" ]; then
            # usplash was already shut down earlier, so don't
            # log success as it will look weird on the console.
            log_end_msg=:
            elif pidof usplash > /dev/null; then
            SPLASH_ORIG_CONSOLE="$(fgconsole)"
            DO_NOT_SWITCH_VT=yes /etc/init.d/usplash start
            # We've just shut down usplash, so don't log
            # success as it will look weird on the console.
            log_end_msg=:
        else
            log_end_msg=log_end_msg
        fi
        start-stop-daemon --start --quiet --oknodo --pidfile $PIDFILE --name gdm $SSD_ARG -- $CONFIG_FILE >/dev/null 2>&1 || log_end_msg 1
        $log_end_msg 0
 
        if [ "$SPLASH_ORIG_CONSOLE" ] && \
           [ "$SPLASH_ORIG_CONSOLE" != serial ]; then
            # Wait a short while for the active console to
            # change, to try to avoid visible console noise from
            # later init scripts.
            i=0
            while [ "$(fgconsole)" = "$SPLASH_ORIG_CONSOLE" ]; do
                i="$(($i + 1))"
                if [ "$i" -gt 50 ]; then
                    break
                fi
                sleep 0.1
            done
        fi
    fi
  ;;
  stop)
    log_begin_msg "Stopping GNOME Display Manager..."
    start-stop-daemon --stop  --quiet --oknodo --pidfile $PIDFILE --name gdm $SSD_ARG --retry 30 >/dev/null 2>&1
    log_end_msg 0
  ;;
  reload)
    log_begin_msg "Reloading GNOME Display Manager configuration..."
    log_warning_msg "Changes will take effect when all current X sessions have ended."
    start-stop-daemon --stop --signal USR1 --quiet --pidfile \
        $PIDFILE --name gdm $SSD_ARG >/dev/null 2>&1
    log_end_msg 0
  ;;
  restart|force-reload)
    $0 stop || true
    $0 start
  ;;
  status)
    status_of_proc -p "$PIDFILE" "$DAEMON" gdm && exit 0 || exit $?
  ;;
  *)
    log_success_msg "Usage: /etc/init.d/gdm {start|stop|restart|reload|force-reload|status}"
    exit 1
  ;;
esac
 
exit 0

Creating the GDM Configuration File

Our GDM patch included a reference to a file called gdm-usbseat.conf in /etc/gdm/ which needs to be created by us. This file describes the alternate configuration that the system will use when the docking stations are present. Using the following commands, copy the file into the directory, and set the owner/group and permissions.

$ cd /lib/udev/
$ sudo cp ~/git/misc-udlfb/usbseat-xf86.conf.sed .
$ sudo chown root usbseat-xf86.conf.sed && sudo chgrp root usbseat-xf86.conf.sed
$ sudo chmod 644 usbseat-xf86.conf.sed

Below is the gdm-usbseat.conf file at the time of writing.

gdm-usbseat.conf
[daemon]
DynamicXServers=true
FlexibleXServers=0
Greeter=/usr/lib/gdm/gdmgreeter
 
[security]
 
[xdmcp]
 
[gui]
 
[greeter]
 
[chooser]
 
[debug]
 
[servers]
0=inactive

Enabling Multiseat Audio

If you were to reboot the machine now, you ought to get separate graphical login windows for each docking station connected to the computer, and they would have a usable desktop for most computing functions – except their would be no audio for their seat. To enable the ability to output audio to the line out jack on the front of the docking station, you need to make some changes to the way the system creates a Xorg session. This is done by adding the file 50usbseat to /etc/X11/Xsession.d and making sure it has the right owner/group and read permissions. This file can be found in the misc-udlfb folder we previously downloaded from plugable.com via git.

Using the following commands, copy the file into the directory, and set the owner/group and permissions.

$ cd /etc/X11/Xsession.d
$ sudo cp ~/git/misc-udlfb/50usbseat .
$ sudo chown root 50usbseat && sudo chgrp root 50usbseat
$ sudo chmod 644 50usbseat

Below is the 50usbseat file at the time of writing.

50usbseat
oldIFS=$IFS
IFS=:
set $DISPLAY
IFS=.
set $2
SEAT_ID=$1
LN=`ls -al /dev/usbseat/$SEAT_ID/sound`
IFS=C
set $LN
CARD_ID=$2
export ALSA_CARD=$2
export ALSA_PCM_CARD=$2
IFS=$oldIFS

Finishing Up

At this point, everything should be good to go. Shutdown the computer, plug in the docking stations and turn the system on. Each of the USB seats should boot to the graphical login window. Please note, that the directly attached monitor will now show a text console as opposed to the graphical login window. If you do not have an immediate use for the console on the primary display, you can disconnect the monitor, keyboard, and mouse that are directly connected to the computer, and run it in a headless mode using SSH to access the system from across the network.

Known Problems

Zombie User Sessions

If docking station gets disconnected from the computer while the user is logged in, the user does not get logged out. Furthermore, all of their applications keep on running is if nothing happened. Also, if upon reconnecting the docking station, the user is not returned to their X session. Given that each user can only be logged in once, users who do not logout before their docking station is disconnected, will be unable to log back in until their current session is killed.

To kill a user's session, use the following steps, you first need to find their session instance using the command:

$ gdmdynamic -l

Once you have located the zombie user session with the -l option, kill it using:

$ gdmdynamic -d $seat

Input Duplication

If you take notice of the way things are configured via the usbseat.sh script, all of the seats will be mapped to VT07. If you attempt to configure the system so each seat gets mapped to it's own VT, all of the input from each seat will be duplicated to the primary console.

Flash Drives

Currently, if you plug a flash drive into one of the front USB ports on the the docking station, the system will recognize the drive as flash drive, you will not be able to mount it and access any files stored on the drive. This is in large part due to a permissions issue.

user/ryoung12/multiseat.txt · Last modified: 2010/12/18 21:44 by ryoung12