C'est la rentrée !!!
Programmation GFA (8)

Après les vacances, c'est encore les vacances : il suffit pour cela d'allumer votre bon vieil Atari. Un petit jeu sympa comme on n'en fait plus depuis longtemps ? on Calamuse un chouillat ? on s'adonne à la programmation GEM ? Oui ? Alors aujourd'hui, ça va être façon dolce vita...

Le confort avant tout...

Vous connaissez le protocle DHST ? Moi non plus, jusqu'à ce que mon correspondant allemand me demande de l'implémenter dans mon bébé de Joe version 1.46 . Il s'agit en fait de la même chose que les raccourcis pour lancer les documents dans le menu "démarrer" de zin00. Assez pratique, n'est-ce pas ? Mais savez-vous qu'on peux le faire nous aussi, pauvres peguenots d'Ataristes, grâce à Monsieur Thomas MUCH.

Vous aurez conclu que ce protocole Document HiSTory est géré par le petit Start (à partir de la version 8.0). MultiStrip le gère aussi, pour ne pas faire de jaloux :)

Aussi, au lieu de regarder l'ap_id& de START ou de MultiStrip (lequel est le serveur DHST ?), il faut aller regarder dans la Cookie-Jar et chercher la chaine "DHST". La valeur du cookie contient (dans le mot de poids faible) l'ap_id& du serveur DHST. C'est aussi simple. Je vous donne ma fonction pour regarder les cookies :

    FUNCTION test_cookie(cookie_name$,VAR cookie_valeur%)
      LOCAL read_cook%,nom_cook%,cookie%
      '
      nom_cook%=CVL(cookie_name$)
      cookie%=LPEEK(&H5A0)
      cookie_valeur%=0
      '
      IF cookie%<>0
        REPEAT
          read_cook%=LPEEK(cookie%)
          cookie_valeur%=LPEEK(ADD(cookie%,4))
          ADD cookie%,8
        UNTIL read_cook%=0 OR read_cook%=nom_cook%
        IF read_cook%=nom_cook%
          RETURN TRUE
        ELSE
          RETURN FALSE
        ENDIF
      ELSE
        RETURN FALSE
      ENDIF
    ENDFUNC

Comme cela, on sait qui appeler avec notre sempiternel APPL_WRITE. Il faudra donc utiliser le tube GEM, et donner au serveur DHST une adresse vers une structure. En C, cela donne la structure (qui doit être en mémoire partagée) :

    typedef struct
    {
      char *appname, // adresse vers le nom de l'application gérant le document
           *apppath, // adresse vers le nom complet (chemin+nom) de l'appliction
           *docname, // adresse vers le nom du document
           *docpath; // adresse vers le nom complet (chemin+nom) du document
    } DHSTINFO

En GFA, ça donne lors de l'initialisation générale :

    IF magic! OR mint!
      dhst_mem%=GEMDOS(68,L:1056,W:3)   ! allocation en mémoire partagée
      dhst_appname%=ADD(dhst_mem%,16)   ! on réserve 256 octets pour  chaque
      dhst_apppath%=ADD(dhst_mem%,272)  ! item
      dhst_docname%=ADD(dhst_mem%,528)
      dhst_docpath%=ADD(dhst_mem%,784)
      '
      LONG{dhst_mem%}=dhst_appname%           ! on remplie la "structure"
      LONG{ADD(dhst_mem%,4)}=dhst_apppath%    ! par les adresses
      LONG{ADD(dhst_mem%,8)}=dhst_docname%    !
      LONG{ADD(dhst_mem%,12)}=dhst_docpath%   !
      '
      CHAR{dhst_appname%}="Joe"               ! par exemple
      CHAR{dhst_apppath%}=chemin$+"JOE.PRG"   ! nom complet
      CHAR{dhst_docname%}=""                  ! rempli avant appel
      CHAR{dhst_docpath%}=""                  ! rempli avant appel
    ENDIF

En même temps, on a créé les buffers pour le contenu. Pour les puristes, vous pouvez effacer et reconstruire les buffers et la structure DHSTINFO. En ce qui me concerne, elle est permanente dans Joe.

Reste plus qu'à prévenir le serveur DHST qu'on a utilisé tel document. La doku, écrite par Thomas MUCH lui-même, indique qu'il faut faire l'appel à chaque chargement d'un nouveau fichier. Okay, mais si l'on sauvegarde le fichier sous un nouveau nom ? Là je ne sais pas trop.
L'appel est de la forme (tiré du source de Joe ;-) :

    IF @test_cookie("DHST",dhst_id%)=TRUE  ! toujours tester avant l'appel
      dhst_id&=WORD(dhst_id%)  ! on récupère l'ap_id& dans le mot de poids faible
      ' remplisage des buffers de DHSTINFO avec le nom du document
      CHAR{dhst_docname%}=LEFT$(MID$(nom_htm$,SUCC(RINSTR(nom_htm$,"\"))),254)
      CHAR{dhst_docpath%}=LEFT$(nom_htm$,254)
      IF dhst_id&>0
        INT{m_adr%}=56029             ! DHST_ADD ou 0xDADD
        INT{ADD(m_adr%,2)}=ap_id&     ! ap_id& de votre programme
        INT{ADD(m_adr%,4)}=0
        LONG{ADD(m_adr%,6)}=dhst_mem% ! adresse de la struture
        INT{ADD(m_adr%,10)}=0
        INT{ADD(m_adr%,12)}=0
        INT{ADD(m_adr%,14)}=0
        ~APPL_WRITE(dhst_id&,16,m_adr%)
      ENDIF
    ENDIF

Voilà ! Au cas où vous voulez effacer la struture et les buffers après cet appel, le serveur DHST vous renvoie un DHST_ACK (56030 ou 0xDADE) par l'EVNT_MULTI principal de votre programme. Le message correspond presque exactement aux 16 octet que vous avez envoyé avec APPL_WRITE, c'est à dire l'adresse de DHSTINFO dans `LONG{msg_adr%+3}`. Il y a en plus dans `INT{msg_adr%+14}` une réponse : si c'est nul alors il y a un blème, sinon les informations ont été indexées dans une petite base attenante à Start ou MultiStrip.

Oh ! j'oubliais qu'il faut la version 8.0 de Start et que vous devez rajouter les lignes dans le start.set (SMUSetup ne gérant pas encore cela) :

    /documents
    /documents_max	20
    /documents_maxperapp	5

Atariste Phone Home

Modernes ces allemands ! Toutes les nouveautés viennent de chez eux. Une petit ajout par-ci, un autre par-là... pas forcément par les mêmes logiciels. Tout cela donne donc un OS très modulaire, on a ce qu'on veux mais le petit revers de la médaille est qu'il faut connaître les petits programmes (ou gros) qui rendent nos Atari plus confortable qu'un PC sous zin00, voire plus convivial qu'un MacG3.

Ainsi parmi les variables d'environnement, ajoutez $HOME sous la forme : `#_ENV HOME=C:\GEMSYS\HOME\` dans MAGX.INF par exemple. Les programmes modernes prennent en compte ce répertoire et sauvent leurs fichiers de configuration dans celui-ci. (On a vu comment récupérer une variable d'environnement dans les chapitres précédents de ce fanzine merveilleux qu'est le STimulus)

Avantages ?

- vous pouvez essayer la dernière version de votre application préférée à partir d'un cédérom (ou d'un répertoire temporaire) et vous avez votre configuration habituelle.
- mieux, et je me demande pourquoi cela n'a pas été fait avant : faites plusieurs dossiers de type HOME, que vous renommez USER1, USER2, USERn... avec plusieurs MAGX.INF, un pour chaque type de HOME et vous bricolez un petit sélecteur au démarrage, genre XBOOT pour MagiC, qui va copier le bon MAGX.INF dans C:\ selon l'utilisateur ou la configuration choisie. Comme dans un certain zin00 avec ses "profils utilisateurs".

Bref, on a le beurre (sans l'avoir rance :) et l'argent du beurre. Mais que reste-t-il donc à zin00 ? Les ActiveX ? Ben non : on les a aussi ! Toujours grâce à Monsieur MUCH (too much, ce gars ;-).

Comment je contrôle une application : GEMScript

Le principe d'ActiveX, en gros, est de pouvoir contrôler une application zin au travers d'une langage dérivé de Visual Basic et d'une interface regroupant des "composants", qui ne sont autres que des équivalent des objects GEM comme les boutons de dialogue. On peut ainsi refaire l'interface de IE4 (vu de mes yeux vus), faire un applicatif avec base de données sous Excel5 (mon boulot à l'armée), ou quelques gentils macro-virus qui vous salopent le disque dur.
Vous comprendrez donc que cette fonctionnalité est puissante mais également dangeureuse.

GEMScript est une application indépendante qui peut interpréter des scripts (fichiers *.SIC) et les éxécuter. Le langage ressemble au C, mais est très accessible. On peut ainsi lancer avec une instruction une application comme Texel et lui faire faire plein de choses par l'intermédiaire du script, comme des copier/coller, chargement de fichiers, etc.

GEMScript s'occupe de traduire votre code et de l'envoyer, via un protocole spécial, à l'application voulue. Il est nécessaire que l'application supporte ce protocole, ainsi que les commandes usuelles qui lui seront fournies par GEMScript.

Je ne fais ici que déchiffrer la documentation, n'ayant pas encore intégré ce protocole dans Joe. msg% est un buffer de réception de l'EVNT_MULTI ou d'envoi de APPL_WRITE :

GS_REQUEST : GEMScript vers votre application (reçu par EVNT_MULTI)

    INT{msg%}-> 4944
    INT{ADD(msg%,2)}-> ap_id& ! de GEMScript
    INT{ADD(msg%,4)}-> 0
    LONG{ADD(msg%,6)}-> gs_info% ! adresse de la structure GS_INFO
    INT{ADD(msg%,10)}-> 0
    INT{ADD(msg%,12)}-> 0
    INT{ADD(msg%,14)}-> gems_id& ! votre identité sous GEMScript

GS_REPLY : vous supportez GEMScript et vous écrivez avec APPL_WRITE

    INT{msg%}=4945
    INT{ADD(msg%,2)}=ap_id& ! de votre application
    INT{ADD(msg%,4)}=0
    LONG{ADD(msg%,6)}=gs_info% ! même adresse de la struture GS_INFO reçue
    INT{ADD(msg%,10)}=0
    INT{ADD(msg%,12)}=0 ! OK, vous supportez le protocole de communication
                     >1 ! une autre valeur sinon.
    INT{ADD(msg%,14)}=gems_id& ! votre id& sous GEMScript

Pour information, la struture GS_INFO est de la forme :

    typedef struct {
       long len;       /* longueur de la struture en octets */
       int  version;   /* numéro de version (si MKI$ donne 0110 alors  c'est la 1.1) */
       int  msgs;      /* Bitmap des fonctionnalités GSM_xxx  */
       long ext;       /* extension du fichier script : MKL$ donne ".SIC" */
    } GS_INFO;

Pour les fonctionnalités, veuillez vous taper la doc en allemand, merci. Il existe plusieurs protocoles, dont l'écriture de macros entières !!! En ce qui nous concerne ici, on ne s'occupe que des commandes vers une application, qui est supporté si le bit 0 est à 1, c-a-d les valeur de msgs impaires.

Passé l'étape de reconnaissance, notre application peut recevoir des ordres via des commandes, toujours données par GEMScript, via le tube GEM. Ces commandes sont de la forme :

GS_COMMAND : GEMScript vers notre application

    INT{msg%}->4946
    INT{ADD(msg%,2)}-> ap_id& ! de GEMScript
    INT{ADD(msg%,4)}-> 0
    LONG{ADD(msg%,6)}-> adresse vers une chaine de commande en mémoire non protégée
    INT{ADD(msg%,10)}-> 0
    INT{ADD(msg%,12)}-> 0
    INT{ADD(msg%,14)}-> votre identifiant donné par GS_REQUEST

Attention, car la chaine de commande est formatée d'une façon particulière : tous les paramètres sont séparés par un null-byte, la fin de chaîne étant terminée elle-même par 2 null-bytes, comme dans la chaîne d'environement d'un SHEL_WRITE (voir épisodes précédents du Stimulus)

`"Commande"+CHR$(0)+"Paramètre 1"+CHR$(0)+"Paramètre n"+CHR$(0)+CHR$(0)`

Faites donc attention, il n'y a pas forcément 1 paramètre, et il faut manipuler `CHAR{}` avec précaution. Ci-dessous les commandes standard que toute application digne de ce nom se doit de supporter. Vous pouvez, si votre programme gère d'autres fonctions, les faire commander par un script. Il faut alors déclarer les paramètres de commande dans votre documentation pour que l'utilisateur les emploie dans ses scripts.

Commande | Paramètre 1 | Retour 1 | Commentaire
Close | nom du fichier (optionnel) | - |
Copy | nom du fichier (optionnel) | - | fonction du clipboard
Cut | nom du fichier (optionnel) | - | fonction du clipboard
Delete | nom du fichier (optionnel) | - | fonction du clipboard
GetFront | - | nom du fichier | on demande le fichier géré en avant plan
New | - | - | ouverture d'un fichier vierge
Open | nom du fichier | - |
Paste | nom du fichier (optionnel) | - | fonction du clipboard
Print | nom du fichier (optionnel) | - |
Quit | - | - |
Save | nom du fichier (optionnel) | - |
SaveAs | nom du fichier (obligatoire) | - |
SelectAll | nom du fichier (optionnel) | - | fonction du clipboard
ToFront | nom du fichier | - | mettre en avant plan le fichier
Undo | nom du fichier (optionnel) | - |
AppGetLongName | - | nom de l'application | pour le mettre dans MultiStrip ou autre

Vous aurez compris que, si vous devez supporter GEMScript, votre application doit au moins obéir à ces commandes et agir, c'est-à-dire sauver le fichier EN VRAI si la commande est "Save".

D'autres commandes sont décrites dans la doc de GEMScript : il s'agit des commandes pour faire charger et écécuter un script par GEMScript ! C'est le serpent qui se mort la queue :)

Mais on a pas fini. Car il faut renvoyer un ACK, c'est-à-dire le message de retour disant j'ai bien compris et éxécuté la chose (ou non), avec renvoi de certaines informations (d'où la colonne Retour 1 dans notre tableau).

GS_ACK : de l'application vers GEMScript

    INT{msg%}=4947
    INT{ADD(msg%,2)}=ap_id& ! de votre application
    INT{ADD(msg%,4)}=0
    LONG{ADD(msg%,6)}=commande_adr% ! adresse de la chaine de commande qui vous a été envoyée
    LONG{ADD(msg%,10)}=retour_adr% ! chaine de retour, en mémoire non protégée
    INT{ADD(msg%,14)}=0 ! GSACK_OK-> c'est tout bon
                      1 ! GSACK_UNKNOWN-> commande inconnue
                      2 ! GSACK_ERROR-> erreur

On donne donc l'adresse d'une chaine de retour. Celle-ci est formatée comme la chaine de commande, c'est-à-dire avec 1 null-byte entre les propositions et 2 null-bytes finaux. La chaine de retour peut contenir un message d'erreur si l'opération ne s'est pas bien passée.

Et on est propre sur soi. Quand on quitte, on oublie pas de prévenir GEMScript par :

GS_QUIT : application vers GEMSscript

    INT{msg%}=4948
    INT{ADD(msg%,2)}=ap_id& ! de votre application
    INT{ADD(msg%,4)}=0
    INT{ADD(msg%,6)}=0
    INT{ADD(msg%,8)}=0
    INT{ADD(msg%,10)}=0
    INT{ADD(msg%,12)}=0
    INT{ADD(msg%,14)}=gems_id& ! votre identifiant sous GEMScript

Il y a donc un début et une fin : une intialisation et terminaison pour le protocole GEMScript. Les plus malins verront qu'on peut très bien s'en passer ;-) . En effet, GEMScript ne sert qu'à analyser vos scripts et envoyer des commandes à l'application voulue.

Mais on peut très bien envisager une autre application qui se servirait de la vôtre par l'intermédiaire des commandes, sans passer par l'initialisation et terminaison GEMScript.

Par exemple, un module de Joe pourrait dire à celui-ci : cherche la balise <BODY...>, met-la dans un buffer mémoire et donne moi l'adresse. J'analyse ça et affiche les paramètres dans ma boîte de dialogue en fenêtre. Quand l'utilisateur ferme et veut intégrer ça dans le texte édité sous Joe, alors je lui demande d'effacer son <BODY..> et de lire le tag que je lui donnerai.
Lumineux, non ?

Il y a donc plein d'applications possibles du concept de commandes introduit par GEMScript et son protocole. Le tout est de bien l'utiliser, car une application ou des gens mal intentionnés pourraient en faire mauvais usage. Des macro-virus sur Atari ? Zut ! notre système commence à devenir un peu trop moderne ! Il faut songer, si l'on passe des commandes sans passer par GEMScript de protéger les actions d'une manière quelconque.

Je vous conseille de lire la doc de GEMScript. Après ces quelques lignes, l'allemand vous sera plus facile à déchiffrer :) Et n'oubliez pas d'ajouter dans les variables d'environnement :

`#_ENV GEMSCRIPT=`chemin+nom de GEMScript

A la prochaine !

Rajah Lone
écrit le 19 Août 1999