(c)  Copyright 1989 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice, and 
is provided "as is" without warranty of any kind, either expressed or implied.  
The entire risk as to the use of this information is assumed by the user.



                      Using the Clipboard Device

                            by Andy Finkel


The clipboard.device is an Exec device designed to be the standard
method of data exchange between application programs on the Amiga computer.
The clipboard.device lives in the DEVS: directory of Workbench, and keeps
its clips in the DEVS:clipboards directory.

The most common use for the clipboard is passing information by cutting it 
from one application and pasting it into another.  There are two methods an 
application can use to pass data to the clipboard.  The first is to simply 
WRITE the data to the clipboard.device.  In this case, the data is held in 
memory until another application (or the same application) asks to READ the 
data.  This is simple but uses a lot of memory if the data is large. Also
it might waste memory if the data cut to the clipboard is not used.

For this reason, the clipboard.device also supports a a POST mechanism. This 
is more complex than just writing the data to the clipboard.  To do a POST, 
an application informs the clipboard.device that data is available.
If that data is later requested from the clipboard by a second application, 
the clipboard sends a message to the first application asking for the POSTed 
information which is then sent.  That way, no memory is used unless it is 
really needed.

Of the two methods, POSTing is better but is harder to implement.  To keep
things simple, this article will cover only the WRITE method for using
the clipboard.



Clipboard Data

All data on the clipboard should be in IFF format.  This allows the data
to be self-identifying, thus avoiding the need for private formats to be 
established between various applications.  Generally, only two types of 
FORMs will be found on the clipboard: FTXT (text) and ILBM (graphics).
However, other types are possible.

The clipboard.device keeps data in one of two places - memory or disk.  If 
an application writes to the clipboard.device the data is copied into memory.  
If there is not enough memory, the data will be copied to disk.  When the 
clipboard.device is closed (ie no applications have it open) any data held
by the clipboard.device is copied into a clip file in the clipboard directory.
This allows applications to pass data even if both are not active at the same 
time.  If this feature is not desired, the last application to have the 
clipboard.device open may perform a reset before closing the clipboard.device.
Some application writers dislike having the clipboard.device write to disk if 
it contains data when closed.

The clipboard supports multiple data clips.  This is not to be confused with
multiple chunks in a single clip, which allows for different representations of
the same data.  Multiple clips store different data.  Applications performing
cut and paste operations generally specify the primary clip.  The alternate
clips are provided to aid applications in the maintainance of a set of clips
(ala a scrapbook).  The multiple clips are implemented as different units in
the clipboard device, and are thus accessed at open time.



Clipboard.device Commands

The commands and their implementation are as follows (the ones I'm going to 
use will be listed first):


    Read	Read data from the clipboard for a paste.  io_Offset and
		io_ClipID must be set to zero for the first read of a paste
		sequence.  An io_Actual that is less than the io_Length
		indicates that all the data has been read.  After all the data
		has been read, a subsequent read must be performed (one whose
		io_Actual returns zero) to indicate to the clipboard device
		that all the data has been read.  This allows random access of
		the clip while reading (provided only valid reads are
		performed).

    Write	Write data to the clipboard for a cut.  io_Offset and
		io_ClipID must be set to zero for the first write of a cut
		sequence.  An Update command must be given to tell the
		the clipboard that all the data has been written.

    Update	Indicates that the data provided with a Write command is
		complete, and available for a Read.  You cannot issue an
		Update command if any Writes are pending.

    Clear	Clear the data from this unit.  Subsequent Reads will have
		no data available.

    Reset	Reset the clipboard.device to power-on state without closing 
		it.  This will also clear all the data.

    Stop	Service no commands except Invalid, Start, Flush.

    Start	Resume command servicing.

    Flush	Abort all pending commands.

    Post	Post the availability of clip data.  io_ClipID must be set to
		zero, a subsequent write of this data does not have io_ClipID
		set to zero as described above, but to the value in io_ClipID

    CurrentReadID	
		Return the ClipID of the current clip to read.

    CurrentWriteID  
		Return the ClipID of the latest clip written.



How To Use the Clipboard.device

To use the clipboard.device, you must set up an IORequest to talk to the 
device.  The IORequest structure for the clipboard is called an IOClipReq.
It is defined in the header file devices/clipboard.h and looks like this:

struct IOClipReq {
    struct  Message io_Message;
    struct  Device  *io_Device;		/* device node pointer  */
    struct  Unit    *io_Unit;		/* unit (driver private)*/
    UWORD   io_Command;			/* device command */
    UBYTE   io_Flags;			/* including QUICK and SATISFY */
    BYTE    io_Error;			/* error or warning num */
    ULONG   io_Actual;			/* number of bytes transferred */
    ULONG   io_Length;			/* number of bytes requested */
    SPTR    io_Data;			/* either clip stream or post port */
    ULONG   io_Offset;			/* offset in clip stream */
    LONG    io_ClipID;			/* ordinal clip identifier */
}

Notice that this is the same as a standard IO request structure with one
extra field, io_ClipID.  To use the clipboard.device you first Open it by
passing it the address of an IOClipReq and a clip unit number as follows:

    OpenDevice("clipboard.device", unit, &IOClipReq, 0);

The primary clip unit used by applications to share data is unit 0, use of 
alternate clip units is by private convention.  After opening the clipboard,
the IOClipReq must be initialized before doing a cut or paste operation.

When you do a READ or WRITE to the clipboard.device, you must initialize the 
io_ClipID field to 0.  The clipboard.device will take care of it from then 
on - the ID is used internally by the clipboard to keep track of clips.  You 
must also initialize the io_Offset field to 0; this is used for random access 
READs.  By using the offset, you can skip around inside the clip.  Only when 
you trigger the end of clip by trying to read past the end does the 
clipboard.device know you are finished with the clip.

The program listed below, CBIO, shows how to use the clipboard.device for
simple text operations.  This code is based on an example by Bob Burns.



/*********************************************************************/
/*                                                                   */
/*  Program name:  cbio                                              */
/*                                                                   */
/*  Purpose:  To provide standard clipboard device interface routines*/
/*            such as Open, Post, Read, Write, etc.		     */
/*                                                                   */
/*********************************************************************/
#include "exec/types.h"
#include "exec/ports.h"
#include "exec/io.h"
#include "devices/clipboard.h"


struct IOClipReq clipboardIO = 0;
struct MsgPort clipboardMsgPort = 0;
struct MsgPort satisfyMsgPort = 0;

int CBOpen(unit)
int unit;
{
    int error;

    /* open the clipboard device */
    if ((error = OpenDevice("clipboard.device", unit, &clipboardIO, 0)) != 0)
	return(error);

    /* Set up the message port in the I/O request */
    clipboardMsgPort.mp_Node.ln_Type = NT_MSGPORT;
    clipboardMsgPort.mp_Flags = 0;
    clipboardMsgPort.mp_SigBit = AllocSignal(-1);
    clipboardMsgPort.mp_SigTask = (struct Task *) FindTask((char *) NULL);
    AddPort(&clipboardMsgPort);
    clipboardIO.io_Message.mn_ReplyPort = &clipboardMsgPort;

    satisfyMsgPort.mp_Node.ln_Type = NT_MSGPORT;
    satisfyMsgPort.mp_Flags = 0;
    satisfyMsgPort.mp_SigBit = AllocSignal(-1);
    satisfyMsgPort.mp_SigTask = (struct Task *) FindTask((char *) NULL);
    AddPort(&satisfyMsgPort);
    return(0);
}

CBClose()
{
    if(clipboardMsgPort.mp_SigBit >0)FreeSignal(clipboardMsgPort.mp_SigBit);
    if(satisfyMsgPort.mp_SigBit >0)FreeSignal(satisfyMsgPort.mp_SigBit);
    RemPort(&satisfyMsgPort);
    RemPort(&clipboardMsgPort);
    CloseDevice(&clipboardIO);
}

CBSatisfyPost(string,length)
char *string;
int length;
{
    clipboardIO.io_Offset = 0;
    writeLong("FORM");
    length += 12;
    writeLong(&length);
    writeLong("FTXT");
    writeLong("CHRS");
    length -= 12;
    writeLong(&length);

    clipboardIO.io_Command = CMD_WRITE;
    clipboardIO.io_Data = string;
    clipboardIO.io_Length = length;
    DoIO(&clipboardIO);

    clipboardIO.io_Command = CMD_UPDATE;
    return(DoIO(&clipboardIO));
}

writeLong(ldata)
LONG *ldata;
{

int status;
    clipboardIO.io_Command = CMD_WRITE;
    clipboardIO.io_Data = ldata;
    clipboardIO.io_Length = 4;
    status=(DoIO(&clipboardIO));
}

CBCutS(string,length)
char *string;
int length;
{
    clipboardIO.io_ClipID = 0;
    return(CBSatisfyPost(string,length));
}

int
CBPasteS(string)
char *string;
{
    int length=0,status=0;

    clipboardIO.io_Command = CMD_READ; /* get the FORM */
    clipboardIO.io_Data = string;
    clipboardIO.io_Length = 4;
    clipboardIO.io_Offset = 0;
    clipboardIO.io_ClipID = 0;
    status -= DoIO(&clipboardIO);
    string[4]='\0';

    if(!strcmp(string,"FORM")) { /* iff form */
    	clipboardIO.io_Command = CMD_READ; /* get the total length */
    	clipboardIO.io_Data = &length;
    	clipboardIO.io_Length = 4;
	status -=DoIO(&clipboardIO);

    	clipboardIO.io_Command = CMD_READ; /* read the chunk and body */
    	clipboardIO.io_Data = string;
    	clipboardIO.io_Length = 8;
	status -=DoIO(&clipboardIO);
	string[8]='\0';

    	if(!strcmp(string,"FTXTCHRS")) {
	clipboardIO.io_Command = CMD_READ; /* get the length of the data */
	clipboardIO.io_Data = &length;
	clipboardIO.io_Length = 4;
	status -=DoIO(&clipboardIO);

	clipboardIO.io_Command = CMD_READ;
	clipboardIO.io_Data = string;
	clipboardIO.io_Length = length;
	status -=DoIO(&clipboardIO);
	}
    }
    /* force end of file to terminate read */
	clipboardIO.io_Command = CMD_READ;
	clipboardIO.io_Length = 1;
	clipboardIO.io_Data = 0;
	status -= DoIO(&clipboardIO);

	if(!status)return(length);
	else return(-1);
}


int
CBCurrentReadID()
{
    clipboardIO.io_Command = CBD_CURRENTREADID;
    DoIO(&clipboardIO);
    return(clipboardIO.io_ClipID);
}

int
CBCurrentWriteID()
{
    clipboardIO.io_Command = CBD_CURRENTWRITEID;
    DoIO(&clipboardIO);
    return(clipboardIO.io_ClipID);
}


BOOL
CBCheckSatisfy(idVar)
int *idVar;
{
    struct SatisfyMsg *sm;

    if (*idVar == 0)
	return(TRUE);
    if (*idVar < CBCurrentWriteID()) {
	*idVar = 0;
	return(TRUE);
    }
    if (sm = (struct SatisfyMsg *) GetMsg(&satisfyMsgPort)) {
	if (*idVar == sm->sm_ClipID)
	    return(TRUE);
    }
    return(FALSE);
}

CBCut(stream, length)
char *stream;
int length;
{
    clipboardIO.io_Command = CMD_WRITE;
    clipboardIO.io_Data = stream;
    clipboardIO.io_Length = length;
    clipboardIO.io_Offset = 0;
    clipboardIO.io_ClipID = 0;
    DoIO(&clipboardIO);
    clipboardIO.io_Command = CMD_UPDATE;
    DoIO(&clipboardIO);
}

int
CBPost()
{
    clipboardIO.io_Command = CBD_POST;
    clipboardIO.io_Data = &satisfyMsgPort;
    clipboardIO.io_ClipID = 0;
    DoIO(&clipboardIO);
    return(clipboardIO.io_ClipID);
}


