Seminar GUI-Tools

WS 1994/95



8½ von Plan 9



Elmar Ludwig






8½ - Das Fenstersystem von Plan 9


Plan 9:

neues Betriebssystem der Bell-Labs von AT&T

­  sieht in vielen Bereichen UNIX ähnlich aus
    (z.B. Shell, viele Tools; aber kein vi)
   
­  aber neue Konzepte


Ziele bei der Entwicklung:

   *  Verteiltes System für sehr große Netze
   *  hardware-unabhängig  (viele Rechnertypen)
   *  sehr sicheres System:

        ­  kein Super-User mehr
        ­  Messagecodierung



Was gibt's Neues?

z.B.:
   *  Namensraum: Alle unter ``/'' sichtbaren Namen (z.B. für ls)
  
       ­  ist Prozeßeigenschaft und kann vererbt werden
       ­  ``virtuelle'' Dateien / Kataloge
  
           Beispiel:   Namensraum im Kern
     
         ``#b/...''      Bit-Device
         ``#c/...''      cons-Device
         ``#e/...''      Environment      usw...
  
   *  Umbau im Namensraum mit bind(1)
  
       Quelle und Ziel sind existente Dateien / Kataloge
       Möglichkeiten:   Quelle verdeckt Ziel
      Quelle vor Ziel einfügen
      Quelle an Ziel anhängen
  
  



   *  Erweiterung mit mount(1)
  
       Quelle ist ein File-Deskriptor,
       Zugriffe werden auf Messages abgebildet
       Möglichkeiten wie bei bind
      
       Beispiele:   8½(1), ramfs(1), ftpfs(1)
  
  
   *  rfork (flags)
  
       erweitertes fork(2) mit Ressourcen-Kontrolle
      
       Flags erlauben:
  
      ­  neuen Prozeß erstellen / alten verändern
      ­  keine Waitmsg hinterlassen
      ­  neue Prozeßgruppe beginnen
      ­  Namensraum kopieren / löschen / teilen
      ­  Environment kopieren / löschen / teilen
      ­  Filedeskriptoren kopieren / löschen / teilen
      ­  Speicher gemeinsam nutzen



Grafik unter Plan 9:

Das Bit-Device ``#b'' verwaltet Bitmap-Terminal
Es übernimmt ungefähr die Aufgaben eines X-Servers:
   *  verwaltet das Display    (Grafik, Maus)
   *  verwaltet Datenstrukturen   (als IDs sichtbar)
       z.B.   Bitmaps, Fonts, Subfonts
   *  liefert Maus-Events an den Clienten
   *  enthält den maschinenspezifischen Code

8½ verwendet 16-Bit Zeichensatz.
Repräsentierung als:

        ­ Runes (16 Bit) oder
        ­ UTF-String (1-3 Byte)
           (UTF = Universal Transformation Format)

           Vorteil:      ASCII-Zeichen stellen sich selbst dar,
      damit Kompatibilität



Kommunikation:

Beim Anmelden werden ``#b'' und ``#c'' auf ``/dev/'' gebunden;
anschließend Kommunikation durch Lese- und Schreibzugriffe
auf die Dateien:

write in ``/dev/bitblt'' verschickt Request und löst damit Aktion aus.
read von ``/dev/bitblt'' liefert (falls vorgesehen) ein Resultat.
read von ``/dev/mouse'' wartet, bis Maus-Event verfügbar ist
   und liefert diesen zurück.

z.B.:   Punkt setzen      ``p id[2] pt[8] value[1] code[2]''
	  void point (Bitmap *map, Point pt, int val, Fcode code)
	  {
	      uchar *buf = bneed(14);
	  
	      buf[0] = 'p';
	      BPSHORT(buf + 1, map->id);
	      BPLONG(buf + 3, pt.x), BPLONG(buf + 7, pt.y);
	      buf[11] = val;
	      BPSHORT(buf + 12, code);
	  }
   Linie zeichnen      ``l id[2] pt1[8] pt2[8] value[1] code[2]''
   Maus-Event (read)      ``m buttons[1] x[4] y[4] msec[4]''

analog für das cons-Device:

read von ``/dev/cons'' blockiert, bis Zeichen verfügbar sind.
write in ``/dev/cons'' produziert Ausgabe auf der Console.
write in ``/dev/consctl'' schaltet raw-mode an/aus



8½ - Das Fenstersystem

8½ verwaltet viele Fenster auf einem Display und übernimmt
die Funktionen des Windowmanagers und eines einfachen
Terminalemulators.
8½ selbst enthält keinen maschinenspezifischen Code mehr,
die meisten Requests werden nur weitergereicht:



Über Menüpunkte kann man:
    *  neue Fenster öffnen
    *  existente verschieben / vergrößern / verkleinern
    *  Fenster löschen (hangup note an die Prozeßgruppe im Fenster)
    *  Fenster verstecken

Die Prozesse im Fenster haben ihr Fenster als lokales Display.
Voreinstellung ist ein einfaches Terminal,
wobei der Text im Fenster editierbar ist (!).



Fenster werden über IDs verwaltet:
Für jedes Fenster existiert ein Katalog ``/dev/windows/[ID]''
mit Kontrolldateien.  -> Hardcopy mit lpr(1) möglich
Außerdem sind im Namensraum der Prozesse im Fenster
folgende Dateien sichtbar:

      /dev/bitblt   lokale Version des Bit-Devices (geclipped)
      /dev/mouse   analog, Erweiterung durch 8½:
         Abschaltung des Editierens beim Öffnen
   ­  Reshape-Events
   ­  keine Events, wenn der Mauszeiger
       außerhalb des Fensters ist
      /dev/nbmouse   aktueller Mauszustand
      /dev/cons   lokale Version des cons-Devices
      /dev/consctl   analog, 8½ ergänzt hier hold-mode
      /dev/label   Fenstertitel (lesen/setzen mit read/write)
      /dev/select   Selektion im Fenster (nur lesen)
      /dev/snarf   Text im snarf-buffer (lesen/setzen)
      /dev/text   gesamter Text im Fenster
      /dev/winid   eindeutige ID des Fensters
      /dev/window   Bitmap des Fensters



Eigene Programme mit Grafik

Notwendige Schritte:      open ("/dev/bitblt", ...)
(fertig in der Bibliothek)      open ("/dev/mouse", ...)
      open ("/dev/cons", ...)
      open ("/dev/consctl", ...)
      write (consctl, "rawon", ...)

Anschließend Entritt in eine Eventschleife,
alles Weitere geschieht Ereignis-gesteuert.

Programmierung in C:

Es gibt Bibliotheksfuntionen für:
  ­  Initialisierung u.a.:
binit, bclose, bscreenrect, bflush usw.
  ­  Bitmaps:
balloc, bfree, readbitmapfile, usw.
  ­  Grafikausgabe:
bitblt, point, segment, circle, disc, arc,
ellipse, texture, border, string usw.
  ­  Manipulation des Mauszeigers:
cursorset, cursorswitch



  ­  Events:
   einit, estart   - Initialisierung
   event, emouse, ekbd, eread   - Events lesen
   ecanmouse, ecankbd, ecanread   - Test auf Verfügbarkeit

Die Eventstruktur in C:

typedef struct Event
{
	int kdbc;		/* gelesenes Zeichen (als Rune) */
	Mouse mouse;		/* enthält Buttons, Position, Zeit */
	int n;
	uchar data [...];	/* für asynchrone Leseoperationen */
} Event;

Events werden aus ``dev/mouse'' und ``/dev/cons'' gelesen.

Ausgabefunktionen schreiben gepuffert in ``/dev/bitblt'',
explizites Schreiben des Puffers ist mit bflush() möglich.

Beispiel:  Events lesen

void main (void)
{
    Event event;
    ulong key;

    binit(NULL, NULL, "Event Tester");
    einit(Emouse|Ekeyboard);
    while (key = event(&event))
    {
	switch (key)
	{
	    case Emouse    : ....	/* Maus-Event */
			      break;
	    case Ekeyboard : ...	/* Keyboard-Event */
			      break;
	}
    }
    exits(NULL);
}



Eine Alternative:  ALEF

Für viele Toplevel-Fenster werden viele Prozesse benötigt,
dafür ist C nicht gut geeignet.
ALEF (von Phil Winterbottom) ist eine Compilersprache,
die dieses Problem löst.

ALEF verwendet eine C ähnliche Syntax (mit Einschränkungen).

neu ist:       ­  Abstrakte Datentypen mit privaten Variablen
       ­  Exception-Behandlung
       ­  Multi-Thread Programmierung
           Es gibt Prozesse   :  *  viele parallele Programmzähler
         *  laufen unabhängig voneinander
         *  nur Zugriff auf lokalen Speicher
                     und Tasks   :  *  Coroutine in einem Prozeß
           *  nicht von anderen Tasks
            unterbrechbar
         *  Zugriff auf gemeinsamen Speicher
       ­  Kommunikation über Channels
           Informationsaustausch zwischen Tasks über
           gemeinsame Variablen oder Message-Übergabe;
           bei Prozessen immer über Message-Übergabe
       ­  Synchronisation über Locks



Syntax

  *  Kommunikation über Channels:
 
	chan (type) [buffers]opt name;
	alloc name;
     muß dynamisch mit alloc erzeugt werden (Freigabe mit unalloc)
     Channel ohne Puffer:  synchroner Kommunikationskanal
        Der Leser / Schreiber wird blockiert, bis ein Partner da ist;
   dann wird der Wert übergeben.
     Channel mit Puffer:  asynchroner Kommunikationskanal
        Der Leser / Schreiber wird nur blockiert, wenn alle
   Puffer leer (beim Lesen) bzw. belegt (beim Schreiben) sind.
  
  
  
     Senden:        channel <-= value
              (Test mit channel?, ob Schreiber blockiert wird)
     Empfangen:  var = <- channel
              (Test mit ?channel, ob ein Wert verfügbar ist)




     Es geht auch parallel mit vielen Channels:
	alt
	{
		case expr1: ...	expr1...exprn müssen send oder
		case exprn: ...	receive Operation enthalten
	}
     Dabei können die Ausdrücke bei case mehrfach bewertet werden!

  *  Erzeugen von Prozessen / Tasks:
     proc function (parameters);
     task function (parameters);
     Prozeß / Task führt die Funktion aus und endet dann.
     par  { statements }
     Für jedes Statement im Block wird ein neuer Prozeß erzeugt.
     Der Prozeß nach par {...}  ist derselbe wie vorher.
    
  *  Synchronisation:
 
     QLock lock;       Lock anlegen
     lock.lock();       blockiert, bis lock belegt werden kann
     lock.unlock();       wieder freigeben



ALEF und Grafik

Es gibt wie in C fertige Funktionen für Initialisierung, Bitmaps
und Grafikausgabe, aber nicht für die Event-Bearbeitung.

Idee:   *  je einen Prozeß erzeugen, der Maus- bzw. Keyboard-
      Events liest und über einen Channel an die
      Eventschleife liefert
   *  Eventschleife mit alt {...}

Beispiel:  Events lesen

void keybd (chan (Mesg) req)
{
    Mesg msg;
    char buf[128], *ptr;
    int fd, fdc, n, w, kbdcnt;

    if ((fd = open("/dev/cons", OREAD)) < 0) error(...);
    if ((fdc = open("/dev/consctl", OWRITE)) < 0) error(...);
    write(fdc, "rawon", 5);

    ...
    for (;;)
    {
	if ((n = read(fd, ptr, sizeof buf - kbdcnt)) < 0) break;
	...
	while (kbdcnt && fullrune(buf, kbdcnt))
	{
	    ...
	    req <-= msg;
	}
    }
}



void mouse (chan (Mesg) req)
{
    Mesg msg;
    int fd;
    char buf[128];

    if ((fd = open("/dev/mouse", OREAD)) < 0) error(...);
    ...
    for (;;)
    {
	if (read(fd, buf, sizeof buf) < 0) break;
	...
	req <-= msg;
    }
}

void main (void)
{
    alloc mreq, kreq;

    rfork(RFNOTEG);		/* neue Prozessgruppe */
    proc mouse(mreq);		/* Prozesse erzeugen */
    proc keybd(kreq);
    binit(error, nil, "Event Tester");

    for (;;)
    {
	Mesg event;

	alt
	{
	    case event = <-mreq : ...		/* Maus-Event */
				    break;
	    case event = <-kreq : ...		/* Keyboard-Event */
				    break;
	}
    }
}

Grafikausgabe funktioniert genau wie in C.



Was ist mit Netzwerktransparenz?

cpu(1) stellt Verbindung zu anderem Rechner her,
dabei ist der lokale Namensraum unter ``/mnt/term''
verfügbar.
Im profile wird ``bind -b /mnt/term/mnt/8½ /dev'' ausgeführt,
damit führen die Dateien in ``/dev'' wieder zum lokalen
Terminal.

In der Praxis funktioniert das leider nur bedingt.



Zusammenfassung:

+  viele neue Konzepte ­ UNIX Altlasten wurden eliminiert
­  bis jetzt wenig benutzerfreundlich, wenig Software
    und sehr sparsame Dokumentation
+  netzwerktransparent
­  es wird keine Anpassung durch den Benutzer unterstützt
    (z.B. Sprache, Ressourcedatenbank), daher fest codiert
±  kein Tastatur-Mapping
    damit kann kein Client die Tastatur verstellen,
    aber: alle Tasten liefern hardware-spezifischen Code,
         daher keine Cursortasten, keine Zehnertastatur
­  keine Möglichkeit, die Fensterposition oder -größe
    vom Programm aus zu ändern
+  sehr sicheres System