Atarium: Drag&Drop zum Dritten

Als ich im Oktober ATARIs Drag&Drop-Protokoll beschrieb [3], habe ich für den folgenden Monat Beispielquelltexte versprochen. Leider machten mir da aber andere Aufgaben einen Strich durch die Rechnung, doch diesen Monat ist es wirklich soweit. Bevor wir uns aber direkt in die C-Sourcen stürzen, will ich noch eine generelle Bemerkung zum Drag&Drop machen:

Zur Zeit ist MultiTOS das einzige Betriebssystem, unter dem Drag&Drop möglich ist. Dies muß natürlich nicht für immer so bleiben: benötigt werden schließlich „nur“ die Möglichkeit, mehrere GEM-Programme gleichzeitig zu benutzen, Pipes sowie präemptives Multitasking. „MagiX!“ zum Beispiel fehlen zur Zeit nur die Pipes, um D&D-fähig zu werden.

Daher sollte man bei der Implementierung eines Drag&Drop-Senders keine allgemeinen MultiTOS-Abfragen machen, sondern gezielt die benötigten Eigenschaften erfragen. Beispiel: Pipes gibt es dann, wenn Laufwerk U: existiert, dort ein Verzeichnis mit dem Namen PIPE vorhanden ist und dort angelegte Dateien nach Erzeugung laut Fxattr() auch wirklich Pipes sind.

Für die Empfängerseite sieht es etwas einfacher aus: kommt die Drag&Drop-Mitteilung an, werden eigentlich nur die speziellen MiNT-Funktionen Psignal() und Fselect() benötigt. Returnwerte von Psignal() kann man getrost ignorieren, und Fselect() ist eben genau dann vorhanden, wenn es nicht den Fehlercode -32 liefert (ungültige Funktionsnummer). Das ist die Theorie, in der Praxis kann man sich natürlich auch genausogut darauf verlassen, daß der Drag&Drop-Sender schon weiß, was er tut und die benötigten Funktionen auch tatsächlich verfügbar sind.

Kommen wir zum Beispielprogramm. Die abgebildeten Routinen basieren auf Quelltexten von ATARI und sind für den Einsatz in „SCSI-Tool“ bearbeitet und erweitert worden. Als Datentyp wird nur ARGS unterstützt. Funktionen für andere Datentypen können allerdings mit geringem Aufwand eingefügt werden.

parse_ARGS() wird benötigt, um die Dateinamen in einer ARGS-Datenzeile zu tokenisieren. Man erinnere sich daran, daß die Version 1.1 des Protokolls auch die Übergabe von Dateinamen mit Leerzeichen erlaubt (vgl. [1] und [2]). Mit „Tokenisieren“ ist gemeint, daß die einzelnen Dateinamen in der Zeichenkette durch Null-Bytes voneinander getrennt werden. Dabei werden auch die Anführungsstriche, die bei Dateinamen mit Leerzeichen benutzt werden müssen, wieder entfernt. Als Ergebnis liefert die Funktion die Anzahl der gefundenen Elemente.

parse() nimmt die eigentliche Verarbeitung der ARGS-Zeile vor. Dazu wird die Zeichenkette zunächst mittels parse_ARGS() in ihre Bestandteile zerlegt. Anschließend wird für jedes Element eine Auswertungsfunktion aufgerufen. In diesem Beispiel wird der Namen einfach nur mittels puts() auf dem Bildschirm ausgegeben. Normalerweise wird man die Dateien als neue Dokumente öffnen bzw. in das Zielfenster einfügen (je nachdem, welche Fensterkennung in der AES-Mitteilungsstruktur angegeben war).

dd_open_fifo() öffnet die Pipe. Dazu werden die beiden Zeichen für die Extension benötigt, die im letzten Element des AES-Mitteilungspuffers übergeben worden sind. Wenn die Pipe geöffnet werden konnte, wird das MiNT-Signal SIGPIPE ausgeschaltet. Damit wird verhindert, daß das Programm abgebrochen wird, falls die andere Seite die Pipe vorzeitig schließt. dd_close() schließt die Pipe wieder und installiert dabei auch noch gleich den vor Beginn des D&D-Vorgangs eingestellten SIGPIPE-Handler.

dd_open() öffnet mittels dd_open_fifo() die Pipe und schreibt bei Erfolg DD_OK sowie die DD_EXTSIZE lange Extension-Liste hinein. Wenn dies schiefgeht, wird die Pipe geschlossen und ein Fehlercode zurückgeliefert, anderenfalls erhält man das Datei-Handle.

Mit dd_getheader() sind wir schon bei der kompliziertesten Funktion: sie liest aus der Pipe einen kompletten D&D-Header aus. Dieser besteht, wie in der Oktober-Ausgabe beschrieben, aus zwei Bytes Längenangabe für den Header (hdrlen), vier Bytes Datentyp (datatype) sowie vier Bytes Angabe der Gesamtlänge (size).

Dann und nur dann, wenn alle diese Werte einwandfrei eingelesen worden sind, wird auch noch der Rest des Headers gelesen. Nach der aktuellen Fassung des Protokolls (1.1, vgl. [ 1 ]) können hier ein Name für die Objekte (nullterminierte Zeichenkette) sowie ein dazugehöriger Dateiname (ebenso) übergeben worden sein. Auch diese Felder werden übernommen und dann ggfs, der Rest (zur Zeit noch nicht definiert) ausgelesen.

dd_reply() ist eine vergleichsweise einfache Funktion: sie schreibt genau ein Zeichen in die D&D-Pipe. Im Fehlerfall wird die Pipe geschlossen; als Ergebnis wird ein Status zurückgeliefert.

Mit dd_receive() sind wir schon am Ende angekommen. Diese Funktion erhält als einzigen Parameter einen Zeiger auf den AES-Mitteilungspuffer und kann damit praktisch direkt aus der Haupt-Ereignis-Abfrage heraus aufgerufen werden. Zunächst wird die D&D-Pipe geöffnet und die eigene Extension-Liste verschickt (dd_open()). Wenn dies klappt, wird solange mit dd_getheader() ein neuer D&D-Header abgerufen, bis der gewünschte Datentyp ARGS auftaucht. Dazu wird mittels dd_reply() der Statuscode DD_EXT verschickt und damit signalisiert, daß der aktuelle Datentyp nicht verstanden wird.

Hat sich dann der Sender dazu durchgerungen, den Datentyp ARGS anzubieten, wird versucht, der Längenangabe entsprechend viele Bytes zu allozieren. Schlägt das fehl, wird dies als Status DDJLEN signalisiert. Anderenfalls wird das Einverständnis zum Senden gegeben (DD_OK), und es werden entsprechend viele Bytes aus der Pipe gelesen. Schließlich wird die Pipe geschlossen und die eingelesenen Daten an parse() zur Weiterverwertung übergeben. Wie man sieht, ist das Protokoll, obwohl es zunächst einen recht komplizierten Eindruck macht, mit vertretbarem Aufwand in den Griff zu bekommen.

MiNT-News

Abschließend will ich wie üblich noch über den aktuellen Stand bei der MiNT-Entwicklung berichten. Eric Smith hat Version 1.09 freigegeben, die allerdings gegenüber der Vorgängerversion nur geringfügige Änderungen enthält (in erster Linie einen Bugfix, durch den der Pexec-Modus 200 wieder korrekt funktioniert). Auf mehr zu hoffen wäre auch überoptimistisch gewesen, denn es ist ja schließlich wohlbekannt, daß in Sunnyvale im Moment wirklich alle Kapazitäten für die rechtzeitige Fertigstellung des Jaguar eingesetzt werden. MiNT 1.09 ist nur als „Diff‘-Datei erhältlich; man muß also die 1.08-Sourcen mittels patch.ttp und der Differenzdatei auf den neuesten Stand bringen.

Quellennachweis:

[1] Eric Smith: „Drag and Drop protocol, revision 1.1“, ATARI Corporation 1993

[2] Julian F. Reschke: „Noch mehr Tips für MiNT“, ST-Magazin 6/1993, Seite 54

[3] Julian F. Reschke: „Drag&Drop“, ST-Computer 10/1993, Seite 110

Die zur Betatestversion von MiNT 1.09 gehörigen Dateien

MiNT 1.09, Betatestversion für Programmierer

mint108s.zoo (301949 Bytes) Sourcecode der Betatestversion von MiNT 1.08; geeignet für GNU-CC und Pure-C

diff1819.zoo (4780 Bytes) Diff-Datei, um von Version 1.08 nach 1.09 zu kommen (benötigt „patch.ttp“)

MiNT-Libraries (Patchlevel 35, mit viel interessantem Beispielcode):

mntinc35.zoo (111966 Bytes) Headerfiles

mntlib35.zoo (400950 Bytes) die C-Quelltexte

Diese Dateien sollten in jeder besser sortierten Mailbox zu finden sein (zum Beispiel: Maus MS2). Leser mit Internet-Zugang können die Dateien auch u.a. auf den ftp-Servern atari.archive.umich.edu und ftp.uni-muenster.de im Verzeichnis atari/Mint bzw. atari/Mint/Lib finden. Selbstverständlich kann es sein, daß bis zum Erscheinungstermin eine neue MiNT-Version oder neue Libraries (Patchlevel >= 36) verfügbar sind.

/* @(#)atarium/drag.c
   (c)1993 by MAXON-Computer 
   Beispielroutinen für D&D 
   basierend auf Sample-Code der ATARI Corporation
*/
#include <tos.h> 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define AP_DRAGDROP 63

#define DD_OK 0
#define DD_NAK 1
#define DD_EXT 2
#define DD_LEN 3

/* Standard-Extensionliste */

#define DD_NUMEXTS 8
#define DD_EXTSIZE 32

/* Objektname; Maximallänge */
#define DD_NAMEMAX 128

static void *oldsig;

/* Kommandozeile tokenisieren. ' wird als Quote-
   Zeichen benutzt, '' wird durch ein einzelnes 
   Quote ersetzt. Die einzelnen Komponenten werden 
   durch '\0' voneinander getrennt und die Gesamtzahl 
   wird zurückgeliefert */

static int
parse_ARGS (char *str)
{
    int cnt = 1; 
    char *c = str; 
    int in_quote = 0;

    while (*c)
    {
        switch (*c)
        {
            case ' ' :
                if (!in_quote) {
                    *c = '\0'; 
                    cnt++;
                }
                break;
            case 39: /* quote char */ 
                strcpy (c, c + 1); 
                if (!in_quote) 
                    in_quote = 1; 
                else 
                {
                    if (*c != 39) { 
                        in_quote = 0;
                        *c = 0; 
                        if (c[1]) 
                            cnt++;
                    }
                }
                break;
            default: 
                break;
        }

        c += 1;
    }
    return cnt;
}

/* Kommandozeile verarbeiten */


static void
parse (char *obname, char *cmdline)
{
    int comps = parse_ARGS (cmdline); 
    char *c = cmdline;

    while (comps - 1) {
        /* c zeigt auf Element der Kommandozeile */ 
        puts (c);
        c += strlen (c) + 1; 
        comps—;
    }

    /* c zeigt auf letztes Element der Kommandozeile */ 
    puts (c);
}

/* Öffnet die Pipe anhand 'extension'. Bei Erfolg
   wird der Händler für SIGPIPE gesetzt. Ergebnis ist 
   das Dateihandle */

static long
dd_open_fifo (int pnum)
{
    char pipename[20]; 
    long fd;

    sprintf (pipename, "U:\\PIPE\\DRAGDROP.%c%c",(pnum & 0xff00) >> 8, pnum & 0xff);

    if (0 <= (fd = Fopen (pipename, 2)))
        oldsig = Psignal (SIGPIPE, (void *) 1);

    return fd;
}

/* D&D-Operation beenden */ 

void
dd_close (long fd)
{
    Psignal (SIGPIPE, oldsig);
    Fclose ((int) fd);
}

/* D&D-Pipe öffnen und Extensionliste hereinschreiben. 
   Wenn etwas schiefgeht, wird der alte SIGPIPE-Handler 
   wiederhergestellt. Ergebnis ist das Dateihandle oder 
   ein Fehlercode */

long
dd_open (int pipe_num, const char *extlist)
{
    long fd;
    char outbuf[DD_EXTSIZE + 1];

    fd = dd_open_fifo (pipe_num); 
    if (fd < 0) return fd;

    outbuf[0] = DD_OK;
    strncpy (outbuf + 1, extlist, DD_EXTSIZE);

    if (DD_EXTSIZE + 1 != Fwrite ((int) fd, DD_EXTSIZE + 1, outbuf)) { 
        dd_close ((int) fd); 
        return -1;
    }

    return fd;
}

/* Holt den nächsten Header aus der Pipe (fd) ab. 
   Ausgabeparameter sind 'name' (Name des Objekts, 
   mindestens DD_NAMEMAX Zeichen), 'datatype' (Datentyp, 
   4+1 Zeichen) und 'size' (Größe der Daten). 
   Rückgabewert ist 1 im Erfolgsfall, 0 sonst */

int
dd_getheader (long fd, char *obname, char *fname, char *datatype, long *size)
{
    short hdrlen; 
    size_t cnt, slen;
    char buf[PATH_MAX + DD_NAMEMAX + 1];

    if (2 != Fread ((int) fd, 2, fchdrlen)) return 0; 
    if (hdrlen < 9) return 0;

    if (4 != Fread ((int) fd, 4, datatype)) return 0; 
    datatype[4] = '\0';

    if (4 != Fread ((int) fd, 4, size)) return 0;

    hdrlen -= 8; 
    cnt = hdrlen;

    /* Objektnamen und Dateinamen lesen */ 
    if (cnt > PATH_MAX + DD_NAMEMAX) 
        cnt = PATH_MAX + DD_NAMEMAX; 
    if (cnt != Fread ((int) fd, ent, buf)) 
        return 0;

    buf[PATH_MAX + DD_NAMEMAX] = '\0';
    hdrlen -= cnt;
    slen = strlen (buf);

    if (slen < DD_NAMEMAX) 
        strcpy (obname, buf);

    if (slen < PATH_MAX + DD_NAMEMAX) {
        char *fp = buf + slen + 1;

        slen = strlen (fp);

        if (slen < PATH_MAX) 
            strcpy (fname, fp);
    }

    /* Rest überspringen */ 
    while (hdrlen) {
        size_t cnt = hdrlen; 
        if (cnt > sizeof (buf)) 
            hdrlen = sizeof (buf);

        Fread ((int) fd, cnt, buf); 
        hdrlen -= cnt;
    }

    return 1;
}

/* Ein-Zeichen-Antwort verschicken. Im Fehlerfall 
   wird die Pipe geschlossen. Rückgabewert: 0 bei 
   Fehlern */

int
dd_reply (long fd, char ack)
{
    if (1 != Fwrite ((int) fd, 1L, &ack)) { 
        dd_close (fd); 
        return 0;
    }
    return 1;
}

/* D&D durchführen */ 

void
dd_receive (int msg[])
{
    long fd;
    char obname[DD_NAMEMAX], ext[5]; 
    char fname[PATH_MAX]; 
    char ourexts[DD_EXTSIZE] = "ARGS"; 
    long size;

    fd = dd_open (msg[7], ourexts); 
    if (fd < 0) return;

    do
    {
        if (!dd_getheader (fd, obname, fname, ext, &size))
        {
            dd_close (fd); 
            return;
        }

        if (! strncmp (ext, "ARGS", 4)) {
            char *cmdline = malloc (size + 1);

            if (!cmdline) {
                dd_reply (fd, DD_LEN); 
                continue;
            }

            dd_reply (fd, DD_OK);
            Fread ((int) fd, size, cmdline);
            dd_close (fd);
            cmdline[size] = 0;
            parse (obname, cmdline);
            free (cmdline);
            return;
        }
    } while (dd_reply (fd, DD_EXT));
}

Julian F. Reschke
Aus: ST-Computer 12 / 1993, Seite 96

Links

Copyright-Bestimmungen: siehe Über diese Seite