Nastavení jednokaretního multiseatu

Text na této stránce popisuje, jak postavit multiseat pro dva uživatele na počítači s (jedinou) dvouhlavou grafickou kartou. Pokud nevíte, co multiseat je, podívejte se, prosím, nejdříve sem. Pokud používáte jiný operační systém než Linux, tak pro vás tento popis asi nebude příliš užitečný (pokud tedy nepoužíváte podobný systém Unixového typu).

Jak to funguje

Řešení, které používám je následující – je spuštěn X server, který je nastaven na používání obou výstupů grafické karty a který vytvoří display přes oba monitory (teď určitě něco popletu – pokud se vám bude zdát, že motám dohromady pojmy screen, monitor a display, tak vás můžu ujistit, že se vám to nezdá). Následně jsou spuštěny dva vnořené X servery, přesněji Xgl servery. Každý pak zabere jeden monitor.

Zasílání událostí z klávesnice a myši je pak řešeno pomocí XevdevServeru, který čte vstupní porty těchto zařízení a pomocí X událostí je pak přeposílá příslušnému serveru (to je nutné, protože v současnosti lze pouze skutečnému X serveru určit, kterých vstupních zařízení si má všímat – Xgl tuto schopnost nemá).

A nad tímhle už běží vlastní přihlašovací manager (v mém případě GDM).

Abych nezapomněl – používám proprietární ovladače od Ati, protože s těmi otevřenými byl výkon o dost horší.

Jak je to stabilní a rychlé?

Krátká odpověď: dobře, nic moc. Čili, se stabilitou rozhodně problémy nemám (za celou tu dobu co tohle řešení používám to ani jednou „nevytuhlo“). Problém je spíše s rychlostí. Zkrátka a dobře, pokud mezi X server a vlastní aplikace „vrazíte“ ještě jednu (vykreslovací) vrstvu, tak to prostě slušně nepoběží.

Důsledek je ten, že např. KDE to prostě neutáhne. Přesněji: naběhne bez problémů, ale při překreslování např. ikon na panelu nástrojů je prostě vidět, jak se jednotlivé ikony překreslují, takže je to použitelné pouze pokud není jiná možnost.

Další populární aplikace a subjektivní pohled na jejich výkon:

  • Firefox: bez problémů (dokud nenarazíte na stránku s nějakým šíleným Flashem; YouTube je ale v pohodě)
  • Thunderbird: bez problémů
  • OpenOffice.org: překreslování ikon je znatelné, ale dá se s ním normálně pracovat
  • GIMP: těžko se pracuje s nástroji typu štětec (kurzor myši je pozadu)
  • MPlayer, Xine: bez problémů

Hry jsou v podstatě vyloučené (buď se hodně trhají nebo se dokonce odmítnou spustit), to mi ale tolik nevadí.

Jenom doufám, že výkon se časem trochu zlepší, protože na vývoji ovladačů a Xgl se (snad) pracuje…

Nastavení

Nastavení X serveru

Nehodlám popisovat nastavení v xorg.conf, protože předpokládám, že ten, kdo chce zkoušet multiseat ho nastavit umí. Výsledkem musí být zprovozněný výstup na dva monitory pomocí jedné grafické karty. Pokud se podaří docílit, aby každý monitor používal jiný display (tj. např. :0.0 a :0.1), máte o jednu starost míň (mně se to totiž nepodařilo, takže musím ten Xgl po startu ještě posunout).

Nezapomeňte ale vypnout jakékoliv snahy X serveru o spánek monitorů a podobné vymoženosti. Důvodem budiž fakt, že XevdevServer odchytá všechny klávesnicové/myší události, takže si X server myslí, že uživatel nic nedělá a v klidu monitory vypne (a při „vhodném“ nastavení je jediným východiskem restart)…

Nastavení vlastností multiseatu

Jak jsem se již zmiňoval, důležitou součástí je program XevdevServer, který čte stisknuté klávesy a pomocí X událostí je přeposílá příslušnému Xgl serveru. Je proto velmi důležité zjistit, který speciální soubor v adresáři /dev/ reprezentuje příslušné vstupní zařízení.

Pro tyto účely se nejlépe hodí soubory /dev/input/even­tN, kde N je pořadové číslo vstupního zařízení. Tyto čísla se však mohou během startů systému měnit, a proto je výhodnější pracovat přímo s fyzickou adresou zařízení, která se nemění. Tyto adresy je možné zjistit v souboru /proc/bus/input/de­vices, který je tvořen několika podobnými částmi, které vypadají nějak takto:

I: Bus=0011 Vendor=0002 Product=0005 Version=0000
N: Name="ImPS/2 Generic Wheel Mouse"
P: Phys=isa0060/serio1/input0
S: Sysfs=/class/input/input2
U: Uniq=
H: Handlers=mouse1 event2
B: EV=7
B: KEY=70000 0 0 0 0
B: REL=103

Asi pro vás nebude problém identifikovat vaši klávesnici a myš a zjistit příslušné fyzické adresy (řádek Phys=). Kromě klávesnicí a myší se v tomto souboru také pravděpodobně objeví zvlášť tlačítko Power a popř. dálkové ovládání.

Pokud tyto adresy známe, můžeme si vytvořit konfigurační soubor multiseatu, do kterého navíc přidáme ještě informace o rozměrech obrazovek (tyto data pak budou čtena skriptem, o kterém píši o pár odstavců níže).

Konfigurační soubor uložte např. do /etc/mseat.conf.

#seat 0 (left)
seat-0-screen-size=1280x1024
seat-0-screen-position=0,0
seat-0-keyboard=usb-0000:00:1d.1-1/input0
seat-0-mouse=usb-0000:00:1d.1-1/input1

#seat 1 (right)
seat-1-screen-size=1280x1024
seat-1-screen-position=1280,0
seat-1-keyboard=isa0060/serio0/input0
seat-1-mouse=isa0060/serio1/input0

Nastavení GDM

Tady už je nastavování o něco zábavnější (čti složitější). Musíme přidat pro každé místo spuštění příslušného Xgl serveru a také spustit XevdevServer. Takže jsem si vytvořil skript, který provede vše podstatné.

Zde je seznam „úkolů“, které musí skript provést:

  • převést fyzické adresy klávesnice a myši na názvy souborů v adresáři /dev/input/
  • zjistit číslo displaye, na kterém běží X server a na něm spustit Xgl se správnou velikostí
  • posunout Xgl do správné pozice (Xgl totiž nezná parametr -geometry)
  • spustit XevdevServer s potřebnými parametry

Možná bych měl vysvětlit, co míním těmi potřebnými parametry pro XevdevServer. Za prvé a za druhé jsou to cesty k souborům reprezentujícím vstup od klávesnice a myši. Dále je to číslo displaye – těch může být dokonce více, protože XevdevServer mezi nimi umí přepínat, takže lze spustit čtyři přihlašovací managery na jedné obrazovce (a přepínat mezi nimi klávesou Win Num+). Kromě toho, v nové verzi XevdevServeru (2.0.0) je možné přiřadit konfigurační soubor, ve kterém je možné předefinovat mapování kláves a speciální akce pro některé z nich (např. ono přepnutí displayů nebo třeba zabití Xgl serveru).

Navíc, skript musí být poměrně rychlý. Pokud je totiž skript pomalý – a nestačí Xgl spustit včas – tak se GDM domnívá, že se něco zhroutilo a všechny subprocesy od daného displaye sestřelí a zkusí je spustit znovu (a pokud si během toho vytvoříte zámek, tak jste v pěkné bryndě). Pokud se mu to navíc nepodaří vícekrát, tak čeká asi 2 minuty před dalšími pokusy.

Nakonec se mi naštěstí podařilo skript vyladit tak, že běhá celkem rychle (co může, tak odloží na později: velmi rychle spustí Xgl a XevdevServer spouští až později) a GDM už nic neodstřeluje.

Protože veškeré nastavení je načítáno z konfiguračního souboru mseat.conf, stačí skriptu předat jediný parametr – identifikátor místa (sedadla). Toto je část mého /etc/gdm/custom­.conf:

[server-Std]
name=Standard
handled=false
command=/usr/bin/X -ac
flexible=false

[server-Seat0]
name=Seat Alpha
command=/usr/local/sbin/mseat-Xstart -seat 0
flexible=false

[server-Seat1]
name=Seat Bravo
command=/usr/local/sbin/mseat-Xstart -seat 1
flexible=false

Všimněte si zakázu spouštení vlastního správce na hlavním X serveru (handled=false).

To je – pokud se týče nastavení přihlašovacího manageru – vše. Z praktických důvodů se ale vyplácí provést ještě další nastavení (např. nastavit šetřič, odstranit položku vypnout atd.).

Skript pro spuštění Xgl a XevdevServeru

#!/bin/sh


myDisplay="$1"; shift

lockDir="/var/lock/mseat";

mkdir /tmp/mseat 2>/dev/null
date >>/tmp/mseat/log.$mseatUniqueId
logmsg() {
  dt=`date '+%m-%d %H:%M:%S'`;
  echo "[mseat $dt]: " "$@" >>/tmp/mseat/log.$mseatUniqueId
}

usage() {
 echo "Usage:" >/dev/stderr
 echo "  $0 -seat # [-other-parameters-for-Xgl]" >/dev/stderr
 exit 1;
}

getConfig() {
 sed -n 's/^seat-'"$1-$2"'=\(.*\)/\1/p' </etc/mseat.conf | tail -n 1
}

logmsg "$@";
logmsg "Running as $USER";

backgroundServer=/usr/bin/X
xevdevServer=/usr/sbin/xevdevserver

while [ $# -gt 0 ]; do
    if [ "$1" = "-seat" ]; then
        seat="$2";
        shift;
    elif [ "$1" = "-displayScreen" ]; then
        DISPLAY="${DISPLAY}.$2";
        elif [ "$1" = "-usingserver" ]; then
                backgroundServer="$2";
                shift;
    else
        if ! echo "$1" | grep '^vt' >/dev/null; then
            args="$args '$1'";
        fi
     fi
    shift
 done
eval set -- $args
logmsg "$@"

[ -z "$seat" ] && { echo "Seat number missing" >/dev/stderr; usage; }


# gonna find display number to connect to
DISPLAY=:`ps -ef | sed -n "s:.*$backgroundServer \:\([^ ]*\).*:\1:p" | head -n 1`;
[ "$DISPLAY" = ":" ] && DISPLAY=:0

export MSEATID=$seat

grep "^seat-$seat" /etc/mseat.conf >/dev/null || { echo "Unknown seat number ($seat)" >/dev/stderr; exit; };

screenSize=`getConfig $seat "screen-size"`;

XServerPid=$$

logmsg "Screen is $screenSize big, Xgl will have PID $XServerPid"

# lock-begin
#i=0;
#until mkdir "$lockDir"; do
#    sleep $(( $RANDOM % 5 + 1 ));
#    i=$(( $i + 1 ));
#    if [ $i -gt 20 ]; then
#       exit 2;
#     fi
#  done

#xwininfo -root -tree | grep Xgl | sed 's#^\([ \t]*\)\([0-9xA-Fa-f]*\).*#\2#' | sort >/tmp/mseat.before

logmsg "Starting background jobs to take action later..."


# __  __     _                   _ _   _
# \ \/ /__ _| |  _ __   ___  ___(_) |_(_) ___  _ __
#  \  // _` | | | '_ \ / _ \/ __| | __| |/ _ \| '_ \
#  /  \ (_| | | | |_) | (_) \__ \ | |_| | (_) | | | |
# /_/\_\__, |_| | .__/ \___/|___/_|\__|_|\___/|_| |_|
#      |___/    |_|
#
(
    logmsg "Neste X server positioning"
    #kill -s CONT $XServerPid || { rmdir "$lockDir"; exit; }
    export DISPLAY
    winid='';
    tries=0;
    while [ -z "$winid" ]; do
        sleep 1;
        winid=`xwininfo -root -tree | grep "Xephyr on $myDisplay" | sed 's#^\([ \t]*\)\([0-9xA-Fa-f]*\).*#\2#'`;
        tries=$(( $tries + 1 ));
        [ "$tries" -gt 60 ] && break;
    done


    screenPosition=`getConfig $seat "screen-position"`;
    screenSize=`echo $screenSize | tr 'x' ','`;

    logmsg "Shifting to $screenPosition...";
    wmctrl -i -r "$winid" -e "0,$screenPosition,$screenSize"
) &




#                    _
# __  _______   ____| | _____   _____  ___ _ ____   _____ _ __
# \ \/ / _ \ \ / / _` |/ _ \ \ / / __|/ _ \ '__\ \ / / _ \ '__|
#  >  <  __/\ V / (_| |  __/\ V /\__ \  __/ |   \ V /  __/ |
# /_/\_\___| \_/ \__,_|\___| \_/ |___/\___|_|    \_/ \___|_|
#
(
    logmsg "xevdevserver start-up"

    getInputEventNumber() {
        if echo "$1" | grep '^/dev/' >/dev/null; then
            ev="$1";
          else
            phys=`echo "$1" | sed 's#/#\\\\/#g'`;
            ev=`sed -n '/Phys='"$phys"'/,/^$/{s#.*Handlers.*\(event[0-9]*\).*#\1#p}' </proc/bus/input/devices`;
            ev="/dev/input/$ev";
        fi
        echo "$ev";
    }

    keyboard=`getConfig $seat "keyboard"`;
    mouse=`getConfig $seat "mouse"`;

        # no keyboard or mouse, probably will be used remotely
        [ -z "$keyboard$mouse" ] && exit;



    keyboard=`getInputEventNumber "$keyboard"`;
    mouse=`getInputEventNumber "$mouse"`;

    logmsg "Devices are $mouse (mouse) and $keyboard (keyboard)"

    i=0;
    while true; do
        #nohup /usr/local/sbin/xevdevserver -d "$newDisplay" -m $mouse -k $keyboard -s $XServerPid >/dev/null
        #/usr/local/sbin/startsched 10 /usr/local/sbin/xevdevserver -d "$newDisplay" -m $mouse -k $keyboard -s $XServerPid
        /usr/local/sbin/startsched 10 $xevdevServer -d "$myDisplay" -m $mouse -k $keyboard -c /etc/xevdevserver.cfg
        #-c /home/vojta/bin/xevdev.cfg
        #/usr/local/sbin/xevdevserver -d "$newDisplay" -m $mouse -k $keyboard -s $XServerPid
        sleep 1;
        kill -s CONT $XServerPid || exit;
        sleep 1;
        i=$(( $i + 1 ));
        logmsg "Failed to start xevdevserver, having another try ($i)";
    done

)&

logmsg "Launching Xephyr NOW!"
export DISPLAY
export MSEATNUMBER=$seat
#export LD_PRELOAD=/usr/lib/libGL.so
#exec /usr/local/sbin/startsched 1 /usr/bin/Xgl "$newDisplay" +extension XTEST -arinterval 10 -accel glx -accel xv -softcursor -screen "$screenSize" -s 5 v "$@"
exec Xephyr "$myDisplay" +extension XTEST +extension GLX +extension DAMAGE +extension DOUBLE-BUFFER +extension Composite -arinterval 10 -softCursor -zap -screen "$screenSize" -s 5 v "$@"

#exec /usr/bin/Xgl "$newDisplay" +extension XTEST -arinterval 10 -ac -accel glx -accel xv -softcursor -screen "$screenSize" "$@"

logmsg "Exec failed, who knows why :-("

Stránka byla naposledy změněna 31. srpna 2009 v 13.52

Vytvořil Vojtěch HorkýXHTML & CSS platné •