!Genie

(using the Iota Tech./Acorn endorsed clipboard protocol)
Current release is v0.2
Author: Benoît GILON
Logo de HTMLEdit v3

Overview

!Genie is a small application used for viewing the clipboard content as long as it can be displayed in text form. One of such application which use this protocol for data sharing is the Computer Concepts Impression product line (which is currently used to write this article). Readers who are interested in designing clipboard servers can also read the article !AddressB, a small address book which is designed as a clipboard data server.

User Guide

As soon as you double click on the !Genie icon, a window pops up on the screen as below (in this context no application originally claimed the clipboard). !Genie Main Window
From now on, as you perform cut/copy operations on other applications over text data, the clipboard viewer display is refreshed accordingly showing the name of the WIMP task which just claimed the clipboard. Clicking menu over the !Genie window and then clicking SELECT over the Paste item would display the content in the Content display field as shown below. !Genie in action

How it works

!Genie takes care of additional messages for dealing with specific requirements of the protocol:
...
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;
}
At initialisation time (as soon as the Paste menu is built), the application broadcasts a DataRequest message to detect wether an already running application owns the clipboard.
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;
...
}
If no application responds to the DataRequest message then the message bounces back to th originator and the function registered to handle these cases is called.
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;
}
In case a DataSave response message is received by !Genie, then the dialog box holding the task name is updated:
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;
}
Now what happens as the user clicks on the Paste item, part of the !Genie menu?
static int paste_event(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
initiate(0);
state= WAITFORDATASAVE3;
return 1;
}
The difference between the WAITFORDATASAVE state we saw earlier and the WAITFORDATASAVE3 state is that: as soon as a DataSave message is received while in the later state, the !Genie will endeavour to retrieve the content of the clipboard from the application which owns it. The basic idea here is to try the in memory transfer at first and revert to the file based transfert method if the clipboard owner does'know how to deal with in memory transfer.
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;
}
The in memory transfer is dealt by the function below wich iteratively send a RAMFetch message until the receive memory area is full.
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;
}
When !Genie receives a ClaimEntity message, it just has to reinitiate the same procedure as it applied during initailisation:
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;
}