!Genie

(utilisation du protocole presse-papier de Iota Tech./Acorn)
La version courante est la v0.2
Auteur: Benoît GILON
Logo de HTMLEdit v3
Retour à la page principale de !Genie

Présentation

!Genie est une petite application utiliser pour visualiser le contenu du presse-papier dès lors que ce dernier contient des données textuelles. Une application commerciale qui utilise le protocole pour l'échange de données est la ligne de produits Impression de la société Computer Concepts et qui m'a servi pour écrire le présent article. Les lecteurs qui sont intéressés pae la conception de serveurs de données peuvent consulter la page relative à une telle application !AddressB qui est conçue comme un serveur de données pour presse-papiers.

Guide de l'utilisateur

Dès que l'utilisateur doucle clique sur l'icône de !Genie, une fenêtre surgit comme indiqué ci-dessous (dans ce contexte, aucune application n'a réclamé le presse-papier). !Genie Main Window
À partir de là, lorsque vous réalisez des opérations de couper/copier sur des zones textuelles, la fenêtre de !Genie est raffraichie montrant le nom de la tâche WIMP qui vient juste de réclamer le presse-papier. Cliquer MENU au dessus de la fenêtre de !Genie et faire le choix de coller affiche à ce moment là le contenu du presse-papier dans le champ dévolu à l'affichage du contenu (uniquement la première ligne en cas de contenu réparti sur plusieurs lignes) comme indiqué ci-dessous:
!Genie en action

Comment ça marche

!Genie prend soin d'enregistrer des gestionnaires pour traiter les messages mis en oeuvre pour implémenter le protocole:
...
int main()
{
int toolbox_events = 0,
wimp_messages[] =
        {
        Wimp_MClaimEntity, Wimp_MDataRequest, Wimp_MDataSave, Wimp_MDataSaveAck, Wimp_MDataLoad,
        Wimp_MRAMFetch, Wimp_MRAMTransmit, Wimp_MTaskCloseDown,
        0
        };
...
/* register ourselves with the Toolbox. */
(void) toolbox_initialise(0, WimpVersion, wimp_messages, &toolbox_events,
        "<GenieR$Dir>", &mbl, &id_block, 0, &mytskhdl, 0);
...
event_register_message_handler(Wimp_MClaimEntity, ce_message, 0);
event_register_message_handler(Wimp_MDataSave, ds_message, 0);
event_register_message_handler(Wimp_MRAMTransmit, rt_message, 0);
event_register_message_handler(Wimp_MDataLoad, dl_message, 0);
event_register_message_handler(Wimp_MQuit, quit_message, 0);
event_register_message_handler(Wimp_MTaskCloseDown, tcd_message, 0);
...
/* Messages sent from !Genie usually require acknowledgment.
   That's why the application registers a WIMP event handler
   for handling the case where no application was interested
   in the message the !Genie application just sent. */
event_register_wimp_handler(-1, Wimp_EUserMessageAcknowledge, no_ack_event, 0);
...
while(TRUE) event_poll(&event_code, &poll_block, NULL);

return EXIT_SUCCESS;
}
Au moment de l'initialisation (dès que le menu pour coller est crée), l'application diffuse un message DataRequest pour déterminer si une application déjà installée détient le presse-papier.
static void initiate(const int tskh)
{
(void) wimp_send_message(Wimp_EUserMessageRecorded,
        &monmess, tskh, 0, NULL);
refm= monmess.hdr.my_ref;
}
...
static int object_created_event(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
const char *const ptn= ((ToolboxObjectAutoCreatedEvent *) event)->template_name;
const ObjectId oid= id_block->self_id;
...
if(!strcmp(ptn, "Menu"))
        {
        menuoid= oid;
        initiate(0);
        state= WAITFORDATASAVE;
        return 1;
        }
return 0;
}
...
int main()
{
...
monmess.hdr.action_code= Wimp_MDataRequest;
monmess.hdr.size= sizeof(monmess.hdr) + sizeof(WimpDataRequestMessage);
monmess.data.data_request.flags= CLIPBOARD_CLAIMED;
monmess.data.data_request.filelist[0]= 0xfff;
monmess.data.data_request.filelist[1]= -1;
...
}
Si aucune application ne répond à ce message DataRequest, alors ce dernier "rebondit" à la tache Wimp source et la fonction enregistrée pour traiter ce cas est appelée.
static int no_ack_event(int ec, WimpPollBlock *pb, IdBlock *id_block, void *handle)
{
WimpUserMessageAcknowledgeEvent *pumae= &(pb->user_message_acknowledge);
if(pumae->hdr.my_ref == refm)
        switch(state)
                {
                ...
                case WAITFORDATASAVE:
                        tskhdl= -1;
                        paste_set_entry(Gadget_Faded);
                        (void) mdisplayfield_set_value("");
                        state= IDLE;
                        return 1;
                }
return 0;
}
Dans le cas où un message DataSave est reçu en réponse par !Genie, alors la boîte de dialogue contenant devant présenter le nom de la tâche est mis à jour.
static int ds_message(WimpMessage *message, void *handle)
{
WimpDataSaveMessage *pmes= &(message->data.data_save);
if(state == IDLE || refm!= message->hdr.your_ref) return 0;
updappname(message->hdr.sender);
switch(state)
        {
        case WAITFORDATASAVE2:
        case WAITFORDATASAVE:
                (void) paste_set_entry(0);
                state= IDLE;
                break;
        ...
        }
return 1;
}
Maintenant, qu'arrive-t-il si l'utilisateur clique sur l'entrée "Coller" du menu !Genie?
static int paste_event(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
initiate(0);
state= WAITFORDATASAVE3;
return 1;
}
La différence entre l'état WAITFORDATASAVE abordé précédemment et l'état WAITFORDATASAVE3 est que, aussitôt que un message DataSave est reçu alors que !Genie se trouvait dans ce dernier état, il enclenche alors le mécanisme pour connaitre le contenu du presse-papier de l'application propriétaire. L'idée de base est d'essayer le transfert en mémoire en premier, et de se rabbatre sur le transfert par fichier si cette application ne pas gèrer les transferts en mémoire.
static size_t mlen= 0;
static char *pstr= NULL;
static size_t moffset;
static size_t cursize;

static void reset_ramtransfert(void)
{
free(pstr); pstr= NULL;
moffset= mlen= 0;
}
...
#define ERROR_CHECK(p) { \
_kernel_oserror *px= (p); \
if(px) (void) wimp_report_error(px, 1,"Genie"); \
}
...
static void *wimp_send_ramfetch(WimpMessage *message, const size_t increment)
{
char *pstr2= realloc(pstr, mlen+= cursize= increment);
if(pstr2)
        {
        pstr= pstr2;
        message->data.ram_fetch.buffer= pstr + moffset;
        message->data.ram_fetch.buffer_size= cursize;
        message->hdr.your_ref= message->hdr.my_ref;
        message->hdr.action_code= Wimp_MRAMFetch;
        message->hdr.size= sizeof(monmess.hdr) + sizeof(WimpRAMFetchMessage);
        ERROR_CHECK(wimp_send_message(Wimp_EUserMessageRecorded,
                message, message->hdr.sender, 0, NULL))
        refm= message->hdr.my_ref;
        }
return pstr2;
}
...
static int ds_message(WimpMessage *message, void *handle)
{
WimpDataSaveMessage *pmes= &(message->data.data_save);
if(state == IDLE || refm!= message->hdr.your_ref) return 0;
updappname(message->hdr.sender);
switch(state)
        {
        ...
        case WAITFORDATASAVE3:
                reset_ramtransfert();
                if(wimp_send_ramfetch(message, pmes->estimated_size))
                        {
                        if(mfile) fputs("RAMFetch Envoyé\n", mfile);
                        state= WAITFORRAMFTRANSMIT;
                        return 1;
                        }
                else
                        reset_ramtransfert();
        case WAITFORDATASAVE4:
                wimp_send_datasaveack(message, message->hdr.my_ref);
                break;
        }
return 1;
}
...
static void wimp_send_datasaveack(WimpMessage *message, const int my_ref)
{
WimpDataSaveMessage *pmes= &(message->data.data_save);
strcpy(pmes->leaf_name,"<Wimp$Scrap>");
message->hdr.your_ref= my_ref;
message->hdr.action_code= Wimp_MDataSaveAck;
message->hdr.size= (sizeof(monmess.hdr) + sizeof(WimpDataSaveAckMessage) - 212 + sizeof("<Wimp$Scrap>") + 3) <<2;
message->hdr.size= message->hdr.size >> 2;
(void) wimp_send_message(Wimp_EUserMessageRecorded,
        message, tskhdl, 0, NULL);
refm= message->hdr.my_ref;
state= WAITFORDATALOAD;
}

static int no_ack_event(int ec, WimpPollBlock *pb, IdBlock *id_block, void *handle)
{
WimpUserMessageAcknowledgeEvent *pumae= >(pb->user_message_acknowledge);
if(pumae->hdr.my_ref == refm)
        switch(state)
                {
                case WAITFORRAMFTRANSMIT:
                        wimp_send_datasaveack(pumae, pumae->hdr.your_ref);
                        return 1;
                ...
                }
return 0;
}
Le transfert en mémoire est gérée par la fonction ci-dessous qui, de façon répétitive, envoie un message RAMFetch jusqu'à ce que la mémoire de réception soit pleine.
static int rt_message(WimpMessage *message, void *handle)
{
WimpRAMTransmitMessage *pmes= &(message->data.ram_transmit);
if(     (state!= WAITFORRAMFTRANSMIT && state!= WAITFORRAMFTRANSMIT2) ||
        refm!= message->hdr.your_ref) return 0;
moffset+= pmes->nbytes;
if(pmes->nbytes < cursize)
        {
        *(pstr + moffset)= '\0';
        (void) displayfield_set_value(DisplayField_Centred,
                woid, KDISPCLPTXT, pstr);
        state= IDLE;
        }
else    {
        (void) wimp_send_ramfetch(message, 16);
        state= WAITFORRAMFTRANSMIT2;
        }
return 1;
}
Lorsque !Genie reçoit un message ClaimEntity, il a juste à relancer la même procédure telle qu'exécutée à l'initialisation:
static int ce_message(WimpMessage *message, void *handle)
{
WimpClaimEntityMessage *pmes= &(((MWimpMessage *) message)->data.claim_entity);
if((pmes->flags & CLIPBOARD_CLAIMED) != 0)
        {
        tskhdl= message->hdr.sender;
        initiate(message->hdr.sender);
        state= WAITFORDATASAVE;
        return 1;
        }
return 0;
}