Copyright (c) Hyperion Entertainment and contributors.

Clipboard Device

From AmigaOS Documentation Wiki
Revision as of 15:17, 12 April 2012 by Alexandre Balaban (talk | contribs) (Added to Need Update category)
Jump to navigation Jump to search
Warning.png This page is not yet fully updated to AmigaOS 4.x some of the information contained here may not be applicable in part or totally.

Clipboard Device

The clipboard device allows the exchange of data dynamically between one application and another. It is responsible for caching data that has been “cut” and providing data to “paste” in an application. A special “post” mode allows an application to inform the clipboard device that the application has data available. The clipboard device will request this data only if the data is actually needed. The clipboard will cache the data in RAM and will automatically spool the data to disk if necessary.

The clipboard device is implemented as an Exec-style device, and supports random access reads and writes on data within the clipboard. All data in the clipboard must be in IFF format. A new library, iffparse.library, has been added to the Amiga libraries. The routines in iffparse.library can and should be used for reading and writing data to the clipboard. This chapter contains a brief discussion of IFF as it relates to the clipboard (for more details see Appendix A).

|ll|

2cNew Clipboard Features for Version 2.0
Feature & Description
CBD_CHANGEHOOK & Device Command


Compatibility Warning: The new features for the 2.0 clipboard device are not backwards compatible.

Clipboard Device Commands and Functions

Command Command Operation
CBD_CHANGEHOOK Specify a hook to be called when the data on the clipboard has changed (V36).
CBD_CURRENTREADID Return the Clip ID of the current clip to read. This is used to determine if a clip posting is still the latest cut.
CBD_CURRENTWRITEID Return the Clip ID of the latest clip written. This is used to determine if the clip posting data is obsolete.
CBD_POST Post the availability of clip data.
CMD_READ Read data from the clipboard for a paste. Data can be read from anywhere in the clipboard by specifying an offset 0 in the I/O request.
CMD_UPDATE Indicate that the data provided with a write command is complete and available for subsequent read/pastes.
CMD_WRITE Write data to the clipboard as a cut.

Exec Functions as Used in This Chapter:

<tbody> </tbody>
CloseDevice() 8.9cmRelinquish use of the clipboard device. All requests must be complete before closing.
DoIO() 8.9cmInitiate a command and wait for completion (synchronous request).
GetMsg() Get next message from a message port.
OpenDevice() Obtain use of the clipboard device.
SendIO() 8.9cmInitiate a command and return immediately (asynchronous request).

Exec Support Functions as Used in This Chapter:

<tbody> </tbody>
CreateExtIO() 8.9cmCreate an I/O request structure of type IOClipReq. This structure will be used to communicate commands to the clipboard device.
CreatePort() 8.9cmCreate a signal message port for reply messages from the clipboard device. Exec will signal a task when a message arrives at the port.
DeleteExtIO() 8.9cmDelete an I/O request structure created by CreateExtIO().
DeletePort() 8.9cmDelete the message port created by CreatePort().

Device Interface

The clipboard device operates like the other Amiga devices. To use it, you must first open the clipboard device, then send I/O requests to it, and then close it when finished. See the “Introduction to Amiga System Devices” chapter for general information on device usage.


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 */
    STRPTR  io_Data;            /* either clip stream or post port */
    ULONG   io_Offset;          /* offset in clip stream */
    LONG    io_ClipID;          /* ordinal clip identifier */
};

See the include file devices/clipboard.h for the complete structure definition.

The clipboard device I/O request, IOClipReq, looks like a standard IORequest structure except for the addition of the io_ClipID field, which is used by the device to identify clips. It must be set to zero by the application for a post or an initial write or read, but preserved for subsequent writes or reads, as the clipboard device uses this field internally for bookkeeping purposes.

Opening the Clipboard Device

Three primary steps are required to open the clipboard device:

  • Create a message port using CreatePort(). Reply messages from the device must be directed to a message port.
  • Create an extended I/O request structure of type IOClipReq using CreateExtIO().
  • Open the clipboard device. Call OpenDevice(), passing the IOClipReq.
struct MsgPort  *ClipMP;          /* pointer to message port*/
struct IOClipReq *ClipIO;         /* pointer to IORequest */

if (ClipMP=CreatePort(0L,0L) )
    {
    if (ClipIO=(struct IOClipReq *)
                CreateExtIO(ClipMP,sizeof(struct IOClipReq)))
        {
        if (OpenDevice("clipboard.device",0L,ClipIO,0))
            printf("clipboard.device did not open\n");
        else
            {
             ... do device processing
            }
        {
    else
        printf("Error: Could not create IORequest\n");
    }
else
    printf("Error: Could not create message port\n");

Clipboard Data

Data on the clipboard resides in one of three places. When an application posts a cut, the data resides in the private memory space of that application. When an application writes to the clipboard, either of its own volition or in response to a message from the clipboard requesting that it satisfy a post, the data is copied to the clipboard which is either memory or a special disk file. When the clipboard is not open, the data resides in the special disk file located in the directory specified by the CLIPS: logical AmigaDOS assign.

Data on the clipboard is self-identifying. It must be a correct IFF (Interchange File Format) file; the rest of this section refers to IFF concepts. See the Appendix A in this manual for a complete description of IFF. If the top-level chunk is of type CAT with an identifier of CLIP, that indicates that the contained chunks are different representations of the same data, in decreasing order of preference on the part of the producer of the clip. Any other data is as defined elsewhere (probably a single representation of the cut data produced by an application).

The iffparse.library also contains functions which simplify reading and writing of IFF data to the clipboard device. See the “IFF Parse Library” chapter of the Amiga ROM Kernel Reference Manual: Libraries for more information.

A clipboard tool, which is an application that allows a Workbench user to view a clip, should understand the text (FTXT) and graphics (ILBM) form types. Applications using the clipboard to export data should include at least one of these types in a CAT CLIP so that their data can be represented on the clipboard in some form for user feedback.

You should not, in any way, rely on the specifics of how files in CLIPS: are handled or named. The only proper way to read or write clipboard data is via the clipboard device.

boxPlay Nice!Keep in mind that while your task is reading from or writing to a clipboard unit, other tasks cannot. Therefore, it is important to be fast. If possible, make a copy of the clipboard data in RAM instead of processing it while the read or write is in progress.

Multiple Clips

The clipboard supports multiple clips, i.e., the clipboard device can contain more than one distinct piece of data. This is not to be confused with the IFF CAT CLIP, which allows for different representation of the same data.

The multiple clips are implemented as different units in the clipboard device. The unit is specified at OpenDevice() time.


struct IOClipReq *ClipIO;
LONG unit;

OpenDevice("clipboard.device", unit, ClipIO, 0);

By default, applications should use clipboard unit 0. However, it is recommended that each application provide a mechanism for selecting the unit number which will be used when the clipboard is opened. This will allow the user to create a convention for storing different types of data in the clipboard. Applications should never write to clipboard unit 0 unless the user requests it (e.g., selecting Copy or Cut within an application).

Clipboard units 1–255 can be used by the more advanced user for:

  • Sharing data between applications within an ARexx Script.
  • Customizing applications to store different kinds of data in different clipboard units.
  • Customizing an application to use multiple cut/copy/paste buffers.
  • Specialized utilities which might display and/or automatically modify the contents of a clipboard unit.

All applications which provide Cut, Copy and Paste capabilities, should, at a minimum, provide support for clipboard unit 0.

Writing to the Clipboard Device

You write to the clipboard device by passing an IOClipReq to the device with CMD_WRITE set in io_Command, the number of bytes to be written set in io_Length and the address of the write buffer set in io_Data.

ClipIO->io_Data    = (char *) data;
ClipIO->io_Length  = 4L;
ClipIO->io_Command = CMD_WRITE;

An initial write should set io_Offset to zero. Each time a write is done, the device will increment io_Offset by the length of the write.

As previously stated, the data you write to the clipboard must be in IFF format. This requires a certain amount of preparation prior to actually writing the data if it is not already in IFF format. A brief explanation of the IFF format will be helpful in this regard.

For our purposes, we will limit our discussion to a simple formatted text (FTXT) IFF file. An FTXT file looks like:

<thead> </thead> <tbody> </tbody>
FORM
length of succeeding bytes
FTXT
CHRS
length of succeeding bytes
data bytes
pad byte of zero if the preceding chunk has odd length

Based on the above figure, a hex dump of an IFF FTXT file containing the string “Enterprise” would look like:

<tbody> </tbody>
0000 464F524D FORM
0004 00000016 (length of FTXT, CHRS, 0xA and data)
0008 46545854 FTXT
000C 43485253 CHRS
0010 0000000A (length of “Enterprise”)
0014 456E7465 Ente
0018 72707269 rpri
001C 7365 se

A code fragment for doing this:

LONG slen = strlen ("Enterprise");
BOOL odd = (slen & 1);      /* pad byte flag */

/* set length depending on whether string is odd or even length */
LONG length = (odd) ? slen + 1 : slen;

/* Reset the clip id */
ClipIO->io_ClipID = 0;
ClipIO->io_Offset = 0;

error = writeLong ((LONG *) "FORM");/* "FORM" */

length += 12;  /* add 12 bytes for FTXT, CHRS & length byte to string length */
error = writeLong (&length);
error = writeLong ((LONG *) "FTXT");/* "FTXT" for example */
error = writeLong ((LONG *) "CHRS");/* "CHRS" for example */
error = writeLong (&slen);      /* #  (length of string) */

ClipIO->io_Command = CMD_WRITE;
ClipIO->io_Data = (char *) string;
ClipIO->io_Length = slen;               /* length of string */
error = (LONG) DoIO (clipIO);   /* text string */


LONG writeLong (LONG * ldata)
{
    ClipIO->io_Command = CMD_WRITE;
    ClipIO->io_Data = (char *) ldata;
    ClipIO->io_Length = 4L;
    return ( (LONG) DoIO (clipIO) );
}

The fragment above does no error checking because it’s a fragment. You should always error check. See the example programs at the end of this chapter for the proper method of error checking.

boxIffparse That Data!Keep in mind that the functions in the iffparse.library can be used to write data to the clipboard. See the “IFF Parse Library” chapter of the Amiga ROM Kernel Reference Manual: Libraries for more information.

Updating the Clipboard Device

When the final write is done, an update command must be sent to the device to indicate that the writing is complete and the data is available. You update the clipboard device by passing an IOClipReq to the device with CMD_UPDATE set in io_Command.

ClipIO->io_Command = CMD_UPDATE;
DoIO(ClipIO);

Clipboard Messages

When an application performs a post, it must specify a message port for the clipboard to send a message to if it needs the application to satisfy the post with a write called the SatisfyMsg.


struct SatisfyMsg
{
struct  Message sm_Message; /* the length will be 6 */
UWORD   sm_Unit;            /* 0 for the primary clip unit */
LONG    sm_ClipID;          /* the clip identifier of the post */
}

This structure is defined in the include file devices/clipboard.h.


If the application wishes to determine if a post it has recently performed is still the current clip, it should compare the io_ClipID found in the post request upon return with that returned by the CBD_CURRENTREADID command.

If an application has a pending post and wishes to determine if it should satisfy it (for example, before it exits), it should compare the io_ClipID of the post I/O request with that of the CBD_CURRENTWRITEID command. If the application receives a satisfy message from the clipboard device (format described below), it must immediately perform the write with the io_ClipID of the post. The satisfy message from the clipboard may be removed from the application message port by the clipboard device at any time (because it is re-used by the clipboard device). It is not dangerous to spuriously satisfy a post, however, because it is identified by the io_ClipID.

The cut data is provided to the clipboard device via either a write or a post of the cut data. The write command accepts the data immediately and copies it onto the clipboard. The post command allows an application to inform the clipboard of a cut, but defers the write until the data is actually required for a paste.

In the preceding discussion, references to the read and write commands of the clipboard device actually refer to a sequence of read or write commands, where the clip data is acquired and provided in pieces instead of all at once.

The clipboard has an end-of-clip concept that is analogous to end-of-file for both read and write. The read end-of-file must be triggered by the user of the clipboard in order for the clipboard to move on to service another application’s requests, and consists of reading data past the end of file. The write end-of-file is indicated by use of the update command, which indicates to the clipboard that the previous write commands are completed.


Reading from the Clipboard Device

You read from the clipboard device by passing an IOClipReq to the device with CMD_READ set in io_Command, the number of bytes to be read set in io_Length and the address of the read buffer set in io_Data.

ClipIO->io_Command = CMD_READ;
ClipIO->io_Data = (char *) read_data;
ClipIO->io_Length = 20L;

io_Offset

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. Providing only valid reads are performed, your program can seek/read anywhere within the clip by setting the io_Offset field of the I/O request appropriately.

boxTell The Clipboard You Are Finished Reading.Your application must perform an extra read (one whose io_Actual returns zero) to indicate to the clipboard device that all data has been read, if io_Actual is not already zero.

The data you read from the clipboard will be in IFF format. Conversion from IFF may be necessary depending on your application.

boxIffparse That Data!Keep in mind that the functions in the iffparse.library can be used to read data from the clipboard. See the “IFF Parse Library” chapter of the Amiga ROM Kernel Reference Manual: Libraries for more information.

Closing the Clipboard Device

Each OpenDevice() must eventually be matched by a call to CloseDevice().

CloseDevice(ClipIO);

When the last task closes a clipboard unit with CloseDevice(), the contents of the unit may be copied to a disk file in CLIPS: so that the clipboard device can be expunged.

Monitoring Clipboard Changes

Some applications require notification of changes to data on the clipboard. Typically, these applications will need to do some processing when this occurs. You can set up such an environment through the CBD_CHANGEHOOK command. CBD_CHANGEHOOK allows you to specify a hook to be called when the data on the clipboard changes.

For example, a “show clipboard” utility would need to know when the data on the clipboard is changed so that it can display the new data. The hook it would specify would read the new clipboard data and display it for the user.

You specify a hook for the clipboard device by initializing a Hook structure and then passing an IOClipReq to the device with CBD_CHANGEHOOK set in io_Command, 1 set in io_Length, and the address of the Hook structure set in io_Data.

ULONG HookEntry ();                /* Declare the hook assembly function */
struct IOClipReq *ClipIO;          /* Declare the IOClipReq */
struct Hook *ClipHook;             /* Declare the Hook */

/* Prepare the hook */
ClipHook->h_Entry = HookEntry;       /* C interface in assembly routine HookEntry */
ClipHook->h_SubEntry = HookFunc;     /* Function to call when Hook is activated */
ClipHook->h_Data = FindTask(NULL);   /* Set pointer to current task */

ClipIO->io_Data = (char *) ClipHook; /* Point to hook struct */
ClipIO->io_Length = 1;               /* Add hook to clipboard */
ClipIO->io_Command = CBD_CHANGEHOOK;
DoIO(clipIO);

The above code fragment assumes that an assembly language routine HookEntry() has been coded:

; entry interface for C code
_HookEntry:
        move.l  a1,-(sp)                ; push message packet pointer
        move.l  a2,-(sp)                ; push object pointer
        move.l  a0,-(sp)                ; push hook pointer
        move.l  h_SubEntry(a0),a0       ; fetch C entry point ...
        jsr     (a0)                    ; ... and call it
        lea     12(sp),sp               ; fix stack
        rts

It also assumes that the function HookFunc() has been coded. One of the example programs at the end of this chapter has hook processing in it. See the include file utility/hooks.h and The Amiga ROM Kernel Reference Manual: Libraries for further information on hooks.

You remove a hook by passing an IOClipReq to the device with the address of the Hook structure set in io_Data, 0 set in io_Length and CBD_CHANGEHOOK set in io_Command.

ClipIO->io_Data = (char *) ClipHook;     /* point to hook struct */
ClipIO->io_Length = 0;                   /* Remove hook from clipboard */
ClipIO->io_Command = CBD_CHANGEHOOK;
(DoIO (clipIO))

You must remove the hook or it will continue indefinitely.

Caveats for CBD_CHANGEHOOK

  • CBD_CHANGEHOOK

    should only be used by a special application, such as a clipboard viewing program. Most applications can check the contents of the clipboard when, and if, the user requests a paste.

  • Do not add system overhead by blindly reading and parsing the clipboard everytime a user copies data to it. If all applications did this, the system could become intolerably slow whenever an application wrote to the clipboard. Only read and parse when it is necessary.

Example Clipboard Programs

/*
 * Clipdemo.c
 *
 * Demonstrate use of clipboard I/O.  Uses general functions
 * provided in cbio.c
 *
 * Compile with SAS C 5.10: LC -b1 -cfistq -v -y -L+cbio.o
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <devices/clipboard.h>
#include <libraries/dosextens.h>
#include <libraries/dos.h>

#include "cb.h"

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifdef LATTICE
int CXBRK(void) { return(0); }  /* Disable SAS CTRL/C handling */
int chkabort(void) { return(0); }  /* really */
#endif

#define FORGETIT 0
#define READIT   1
#define WRITEIT  2
#define POSTIT   3

/* prototypes */

int ReadClip( void );           /* Demonstrate reading clipboard data      */
int WriteClip( char * );        /* Demonstrate write to clipboard        */
int PostClip( char * );         /* Demonstrate posting data to clipboard */

void main( USHORT, char **);

char message[] = "\
\nPossible switches are:\n\n\
-r            Read, and output contents of clipboard.\n\
-w [string]   Write string to clipboard.\n\n\
-p [string]   Write string to clipboard using the clipboard POST mechanism.\n\n\
              The Post can be satisfied by reading data from\n\
              the clipboard.  Note that the message may never\n\
              be received if some other application posts, or\n\
              performs an immediate write to the clipboard.\n\n\
              To run this test you must run two copies of this example.\n\
              Use the -p switch with one to post data, and the -r switch\n\
              with another to read the data.\n\n\
              The process can be stopped by using the BREAK command,\n\
              in which case this example checks the CLIP write ID\n\
              to determine if it should write to the clipboard before\n\
              exiting.\n\n";


void main(argc,argv)
USHORT argc;
char **argv;
{

int todo;
char *string;

todo = FORGETIT;

if (argc)     /* from CLI ? */
    {

    /* Very simple code to parse for arguments - will suffice for
     * the sake of this example
     */

    if (argc > 1)
       {
       if (!(strcmp(argv[1],"-r")))
           todo = READIT;
       if (!(strcmp(argv[1],"-w")))
           todo = WRITEIT;
       if (!(strcmp(argv[1],"-p")))
           todo = POSTIT;

       string = NULL;

       if (argc > 2)
           string=argv[2];

       }

    switch (todo)
            {

            case READIT:

                 ReadClip();
                 break;

            case POSTIT:

                 PostClip(string);
                 break;

            case WRITEIT:

                 WriteClip(string);
                 break;

            default:

                 printf("%s",message);
                 break;

            }


    }
}

/*
 * Read, and output FTXT in the clipboard.
 *
 */

ReadClip()
{
struct IOClipReq *ior;
struct cbbuf *buf;


/* Open clipboard.device unit 0 */

if (ior=CBOpen(0L))
    {

    /* Look for FTXT in clipboard */

    if (CBQueryFTXT(ior))
        {

        /* Obtain a copy of the contents of each CHRS chunk */

        while (buf=CBReadCHRS(ior))
              {
              /* Process data */

              printf("%s\n",buf->mem);

              /* Free buffer allocated by CBReadCHRS() */

              CBFreeBuf(buf);
              }

        /* The next call is not really needed if you are sure */
        /* you read to the end of the clip.                   */

        CBReadDone(ior);
        }
    else
        {
        puts("No FTXT in clipboard");
        }

    CBClose(ior);
    }

else
    {
    puts("Error opening clipboard unit 0");
    }

return(0L);
}

/*
 * Write a string to the clipboard
 *
 */

WriteClip(string)
char *string;
{

struct IOClipReq *ior;

if (string == NULL)
    {
    puts("No string argument given");
    return(0L);
    }

/* Open clipboard.device unit 0 */

if (ior = CBOpen(0L))
    {
    if (!(CBWriteFTXT(ior,string)))
        {
        printf("Error writing to clipboard: io_Error = %ld\n",ior->io_Error);
        }
    CBClose(ior);
    }
else
    {
    puts("Error opening clipboard.device");
    }

return(0);
}


/*
 * Write a string to the clipboard using the POST mechanism
 *
 * The POST mechanism can be used by applications which want to
 * defer writing text to the clipboard until another application
 * needs it (by attempting to read it via CMD_READ).  However
 * note that you still need to keep a copy of the data until you
 * receive a SatisfyMsg from the clipboard.device, or your program
 * exits.
 *
 * In most cases it is easier to write the data immediately.
 *
 * If your program receives the SatisfyMsg from the clipboard.device,
 * you MUST write some data.  This is also how you reply to the message.
 *
 * If your program wants to exit before it has received the SatisfyMsg,
 * you must check the io_ClipID field at the time of the post against
 * the current post ID which is obtained by sending the CBD_CURRENTWRITEID
 * command.
 *
 * If the value in io_ClipID (returned by CBD_CURRENTWRITEID) is greater
 * than your post ID, it means that some other application has performed
 * a post, or immediate write after your post, and that you're application
 * will never receive the SatisfyMsg.
 *
 * If the value in io_ClipID (returned by CBD_CURRENTWRITEID) is equal
 * to your post ID, then you must write your data, and send CMD_UPDATE
 * before exiting.
 *
 */

PostClip(string)
char *string;
{

struct MsgPort *satisfy;
struct SatisfyMsg *sm;
struct IOClipReq *ior;
int mustwrite;
ULONG postID;

if (string == NULL)
    {
    puts("No string argument given");
    return(0L);
    }

if (satisfy = CreatePort(0L,0L))
    {

    /* Open clipboard.device unit 0 */

    if (ior = CBOpen(0L))
        {
        mustwrite = FALSE;

        /* Notify clipboard we have data */

        ior->io_Data    = (STRPTR)satisfy;
        ior->io_ClipID  = 0L;
        ior->io_Command = CBD_POST;
        DoIO( (struct IORequest *) ior);

        postID = ior->io_ClipID;

        printf("\nClipID = %ld\n",postID);

        /* Wait for CTRL-C break, or message from clipboard */
        Wait(SIGBREAKF_CTRL_C|(1L << satisfy->mp_SigBit));

        /* see if we got a message, or a break */
        puts("Woke up");


        if (sm = (struct SatisfyMsg *)GetMsg(satisfy))
            {
            puts("Got a message from the clipboard\n");

            /* We got a message - we MUST write some data */
            mustwrite = TRUE;
            }
        else
            {
            /* Determine if we must write before exiting by
             * checking to see if our POST is still valid
             */

            ior->io_Command = CBD_CURRENTWRITEID;
            DoIO( (struct IORequest *) ior);

            printf("CURRENTWRITEID = %ld\n",ior->io_ClipID);

            if (postID >= ior->io_ClipID)
                mustwrite = TRUE;

            }

        /* Write the string of text */

        if (mustwrite)
            {
            if (!(CBWriteFTXT(ior,string)))
                puts("Error writing to clipboard");
            }
        else
            {
            puts("No need to write to clipboard");
            }

        CBClose(ior);
        }
    else
        {
        puts("Error opening clipboard.device");
        }

    DeletePort(satisfy);
    }
else
    {
    puts("Error creating message port");
    }

return(0);
}
/*
 * Changehook_Test.c
 *
 * Demonstrate the use of CBD_CHANGEHOOK command.
 * The program will set a hook and wait for the clipboard data to change.
 * You must put something in the clipboard in order for it to return.
 *
 * Compile with SAS C 5.10: LC -cfist -v -y -L+Hookface.o+cbio.o
 *
 * Requires Kickstart 36 or greater.
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/tasks.h>
#include <exec/io.h>
#include <devices/clipboard.h>
#include <dos/dos.h>
#include <utility/hooks.h>
#include "cb.h"

#include <clib/macros.h>
#include <clib/alib_protos.h>
#include <clib/exec_protos.h>

#include <stdio.h>
#include <string.h>


LONG version = 1L;

extern ULONG SysBase, DOSBase;

/* Data to pass around with the clipHook */
struct CHData
{
    struct Task *ch_Task;
    LONG ch_ClipID;
};

struct MsgPort *clip_port;
struct Hook hook;
struct CHData ch;

ULONG clipHook (struct Hook * h, VOID * o, struct ClipHookMsg * msg)
{
struct CHData *ch = (struct CHData *) h->h_Data;

if (ch)
   {
   /* Remember the ID of clip */
   ch->ch_ClipID = msg->chm_ClipID;

   /* Signal the task that started the hook */
   Signal (ch->ch_Task, SIGBREAKF_CTRL_E);
   }

return (0);
}

struct IOClipReq *OpenCB (LONG unit)
{
struct IOClipReq *clipIO;

/* Open clipboard unit 0 */

if (clipIO = CBOpen( 0L ))
    {
    ULONG hookEntry ();

    /* Fill out the IORequest */
    clipIO->io_Data = (char *) &hook;
    clipIO->io_Length = 1;
    clipIO->io_Command = CBD_CHANGEHOOK;

    /* Set up the hook data */
    ch.ch_Task = FindTask (NULL);

    /* Prepare the hook */
    hook.h_Entry = hookEntry;
    hook.h_SubEntry = clipHook;
    hook.h_Data = &ch;

    /* Start the hook */
    if (DoIO (clipIO))
        printf ("unable to set hook\n");
    else
        printf ("hook set\n");

    /* Return success */
    return ( clipIO );
    }

/* return failure */
return (NULL);
}

void CloseCB (struct IOClipReq *clipIO)
{

/* Fill out the IO request */
clipIO->io_Data = (char *) &hook;
clipIO->io_Length = 0;
clipIO->io_Command = CBD_CHANGEHOOK;

    /* Stop the hook */
if (DoIO (clipIO))
    printf ("unable to stop hook\n");
else
    /* Indicate success */
    printf ("hook is stopped\n");

CBClose(clipIO);
}

main (int argc, char **argv)
{
struct IOClipReq *clipIO;

ULONG sig_rcvd;

printf ("Test v%ld\n", version);

if (clipIO=OpenCB (0L))
    {
    sig_rcvd = Wait ((SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E));

    if (sig_rcvd & SIGBREAKF_CTRL_C)
        printf ("^C received\n");

    if (sig_rcvd & SIGBREAKF_CTRL_E)
        printf ("clipboard change, current ID is %ld\n", ch.ch_ClipID);

    CloseCB(clipIO);
    }
}

Support Functions Called from Example Programs

/* Cbio.c
 *
 * Provide standard clipboard device interface routines
 *            such as Open, Close, Post, Read, Write, etc.
 *
 * Compile with SAS C 5.10: LC -b1 -cfistq -v -y
 *
 *  NOTE - These functions are useful for writing, and reading simple
 *         FTXT.  Writing, and reading complex FTXT, ILBM, etc.,
 *         requires more work - under 2.0 it is highly recommended that
 *         you use iffparse.library.
 */

#include <exec/types.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <devices/clipboard.h>

#define CBIO 1

#include "cb.h"

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/****** cbio/CBOpen *************************************************
*
*   NAME
*       CBOpen() -- Open the clipboard.device
*
*   SYNOPSIS
*       ior = CBOpen(unit)
*
*       struct IOClipReq *CBOpen( ULONG )
*
*   FUNCTION
*       Opens the clipboard.device.  A clipboard unit number
*       must be passed in as an argument.  By default, the unit
*       number should be 0 (currently valid unit numbers are
*       0-255).
*
*   RESULTS
*       A pointer to an initialized IOClipReq structure, or
*       a NULL pointer if the function fails.
*
*********************************************************************/

struct IOClipReq *CBOpen(unit)
ULONG unit;
{
struct MsgPort *mp;
struct IOStdReq *ior;

if (mp = CreatePort(0L,0L))
    {
    if (ior=CreateExtIO(mp,sizeof(struct IOClipReq)))
        {
        if (!(OpenDevice("clipboard.device",unit,ior,0L)))
            {
            return((struct IOClipReq *)ior);
            }
        DeleteExtIO(ior);
        }
    DeletePort(mp);
    }
return(NULL);

}

/****** cbio/CBClose ************************************************
*
*   NAME
*       CBClose() -- Close the clipboard.device
*
*   SYNOPSIS
*       CBClose()
*
*       void CBClose()
*
*   FUNCTION
*       Close the clipboard.device unit which was opened via
*       CBOpen().
*
*********************************************************************/

void CBClose(ior)
struct IOClipReq *ior;
{
struct MsgPort *mp;

mp = ior->io_Message.mn_ReplyPort;

CloseDevice((struct IOStdReq *)ior);
DeleteExtIO((struct IOStdReq *)ior);
DeletePort(mp);

}

/****** cbio/CBWriteFTXT *********************************************
*
*   NAME
*       CBWriteFTXT() -- Write a string of text to the clipboard.device
*
*   SYNOPSIS
*       success = CBWriteFTXT( ior, string)
*
*       int CBWriteFTXT(struct IOClipReq *, char *)
*
*   FUNCTION
*       Write a NULL terminated string of text to the clipboard.
*       The string will be written in simple FTXT format.
*
*       Note that this function pads odd length strings automatically
*       to conform to the IFF standard.
*
*   RESULTS
*       TRUE if the write succeeded, else FALSE.
*
*********************************************************************/

int CBWriteFTXT(ior,string)
struct IOClipReq *ior;
char *string;
{

ULONG length, slen;
BOOL odd;
int success;

slen = strlen(string);
odd = (slen & 1);               /* pad byte flag */

length = (odd) ? slen+1 : slen;

/* initial set-up for Offset, Error, and ClipID */

ior->io_Offset = 0;
ior->io_Error  = 0;
ior->io_ClipID = 0;


/* Create the IFF header information */

WriteLong(ior, (long *) "FORM");     /* "FORM"             */
length+=12L;                         /* + "[size]FTXTCHRS" */
WriteLong(ior, &length);             /* total length       */
WriteLong(ior, (long *) "FTXT");     /* "FTXT"             */
WriteLong(ior, (long *) "CHRS");     /* "CHRS"             */
WriteLong(ior, &slen);               /* string length      */

/* Write string */
ior->io_Data    = (STRPTR)string;
ior->io_Length  = slen;
ior->io_Command = CMD_WRITE;
DoIO( (struct IORequest *) ior);

/* Pad if needed */
if (odd)
    {
    ior->io_Data   = (STRPTR)"";
    ior->io_Length = 1L;
    DoIO( (struct IORequest *) ior);
    }

/* Tell the clipboard we are done writing */

ior->io_Command=CMD_UPDATE;
DoIO( (struct IORequest *) ior);

/* Check if io_Error was set by any of the preceding IO requests */
success = ior->io_Error ? FALSE : TRUE;

return(success);
}

WriteLong(ior, ldata)
struct IOClipReq *ior;
long *ldata;
{

ior->io_Data    = (STRPTR)ldata;
ior->io_Length  = 4L;
ior->io_Command = CMD_WRITE;
DoIO( (struct IORequest *) ior);

if (ior->io_Actual == 4)
    {
    return( ior->io_Error ? FALSE : TRUE);
    }

return(FALSE);

}


/****** cbio/CBQueryFTXT **********************************************
*
*   NAME
*       CBQueryFTXT() -- Check to see if clipboard contains FTXT
*
*   SYNOPSIS
*       result = CBQueryFTXT( ior )
*
*       int CBQueryFTXT(struct IOClipReq *)
*
*   FUNCTION
*       Check to see if the clipboard contains FTXT.  If so,
*       call CBReadCHRS() one or more times until all CHRS
*       chunks have been read.
*
*   RESULTS
*       TRUE if the clipboard contains an FTXT chunk, else FALSE.
*
*   NOTES
*       If this function returns TRUE, you must either call
*       CBReadCHRS() until CBReadCHRS() returns FALSE, or
*       call CBReadDone() to tell the clipboard.device that
*       you are done reading.
*
*
*********************************************************************/

int CBQueryFTXT(ior)
struct IOClipReq *ior;
{
ULONG cbuff[4];


/* initial set-up for Offset, Error, and ClipID */

ior->io_Offset = 0;
ior->io_Error  = 0;
ior->io_ClipID = 0;

/* Look for "FORM[size]FTXT" */

ior->io_Command = CMD_READ;
ior->io_Data    = (STRPTR)cbuff;
ior->io_Length  = 12;

DoIO( (struct IORequest *) ior);


/* Check to see if we have at least 12 bytes */

if (ior->io_Actual == 12L)
    {
    /* Check to see if it starts with "FORM" */
    if (cbuff[0] == ID_FORM)
        {
        /* Check to see if its "FTXT" */
        if (cbuff[2] == ID_FTXT)
            return(TRUE);
        }

    /* It's not "FORM[size]FTXT", so tell clipboard we are done */
    }

CBReadDone(ior);

return(FALSE);
}


/****** cbio/CBReadCHRS **********************************************
*
*   NAME
*       CBReadCHRS() -- Reads the next CHRS chunk from clipboard
*
*   SYNOPSIS
*       cbbuf = CBReadCHRS( ior )
*
*       struct cbbuf *CBReadCHRS(struct IOClipReq * )
*
*   FUNCTION
*       Reads and returns the text in the next CHRS chunk
*       (if any) from the clipboard.
*
*       Allocates memory to hold data in next CHRS chunk.
*
*   RESULTS
*       Pointer to a cbbuf struct (see cb.h), or a NULL indicating
*       a failure (e.g., not enough memory, or no more CHRS chunks).
*
*       ***Important***
*
*       The caller must free the returned buffer when done with the
*       data by calling CBFreeBuf().
*
*   NOTES
*       This function strips NULL bytes, however, a full reader may
*       wish to perform more complete checking to verify that the
*       text conforms to the IFF standard (stripping data as required).
*
*       Under 2.0, the AllocVec() function could be used instead of
*       AllocMem() in which case the cbbuf structure may not be
*       needed.
*
*********************************************************************/

struct cbbuf *CBReadCHRS(ior)
struct IOClipReq *ior;
{
ULONG chunk,size;
struct cbbuf *buf;
int looking;

/* Find next CHRS chunk */

looking = TRUE;
buf = NULL;

while (looking)
      {
      looking = FALSE;

      if (ReadLong(ior,&chunk))
          {
          /* Is CHRS chunk ? */
          if (chunk == ID_CHRS)
              {
              /* Get size of chunk, and copy data */
              if (ReadLong(ior,&size))
                  {
                  if (size)
                      buf=FillCBData(ior,size);
                  }
              }

            /* If not, skip to next chunk */
          else
              {
              if (ReadLong(ior,&size))
                  {
                   looking = TRUE;
                   if (size & 1)
                       size++;    /* if odd size, add pad byte */

                    ior->io_Offset += size;
                  }
              }
          }
      }

if (buf == NULL)
    CBReadDone(ior);        /* tell clipboard we are done */

return(buf);
}


ReadLong(ior, ldata)
struct IOClipReq *ior;
ULONG *ldata;
{
ior->io_Command = CMD_READ;
ior->io_Data    = (STRPTR)ldata;
ior->io_Length  = 4L;

DoIO( (struct IORequest *) ior);

if (ior->io_Actual == 4)
    {
    return( ior->io_Error ? FALSE : TRUE);
    }

return(FALSE);
}

struct cbbuf *FillCBData(ior,size)
struct IOClipReq *ior;
ULONG size;
{
register UBYTE *to,*from;
register ULONG x,count;

ULONG length;
struct cbbuf *buf,*success;

success = NULL;

if (buf = AllocMem(sizeof(struct cbbuf),MEMF_PUBLIC))
    {

    length = size;
    if (size & 1)
        length++;            /* if odd size, read 1 more */

    if (buf->mem = AllocMem(length+1L,MEMF_PUBLIC))
        {
        buf->size = length+1L;

        ior->io_Command = CMD_READ;
        ior->io_Data    = (STRPTR)buf->mem;
        ior->io_Length  = length;

        to = buf->mem;
        count = 0L;

        if (!(DoIO( (struct IOStdReq *) ior)))
            {
            if (ior->io_Actual == length)
                {
                success = buf;      /* everything succeeded */

                /* strip NULL bytes */
                for (x=0, from=buf->mem ;x<size;x++)
                     {
                     if (*from)
                         {
                         *to = *from;
                         to++;
                         count++;
                         }

                     from++;
                     }
                *to=0x0;            /* Null terminate buffer */
                buf->count = count; /* cache count of chars in buf */
                }
            }

        if (!(success))
            FreeMem(buf->mem,buf->size);
        }
    if (!(success))
        FreeMem(buf,sizeof(struct cbbuf));
    }

return(success);
}

/****** cbio/CBReadDone **********************************************
*
*   NAME
*       CBReadDone() -- Tell clipboard we are done reading
*
*   SYNOPSIS
*       CBReadDone( ior )
*
*       void CBReadDone(struct IOClipReq * )
*
*   FUNCTION
*       Reads past end of clipboard file until io_Actual is equal to 0.
*       This is tells the clipboard that we are done reading.
*
*********************************************************************/

void CBReadDone(ior)
struct IOClipReq *ior;
{
char buffer[256];

ior->io_Command = CMD_READ;
ior->io_Data    = (STRPTR)buffer;
ior->io_Length  = 254;


/* falls through immediately if io_Actual == 0 */

while (ior->io_Actual)
      {
      if (DoIO( (struct IORequest *) ior))
          break;
      }
}

/****** cbio/CBFreeBuf **********************************************
*
*   NAME
*       CBFreeBuf() -- Free buffer allocated by CBReadCHRS()
*
*   SYNOPSIS
*       CBFreeBuf( buf )
*
*       void CBFreeBuf( struct cbbuf * )
*
*   FUNCTION
*       Frees a buffer allocated by CBReadCHRS().
*
*********************************************************************/

void CBFreeBuf(buf)
struct cbbuf *buf;
{
FreeMem(buf->mem, buf->size);
FreeMem(buf, sizeof(struct cbbuf));
}
****************************************************************************
*        Hookface.asm
*        assembly routines for Chtest
*
*        Assemble with Adapt  hx68 hookface.a to hookface.o
*        Link with Changehook_Test.o as shown in Changehook_Test.c header
*
****************************************************************************
        INCDIR  'include:'
        INCLUDE 'exec/types.i'
        INCLUDE 'utility/hooks.i'
        xdef    _callHook
        xdef    _callHookPkt
        xdef    _hookEntry
        xdef    _stubReturn
***************************************************************************
* new hook standard
* use struct Hook (with minnode at the top)
*
* *** register calling convention: ***
*       A0 - pointer to hook itself
*       A1 - pointer to parameter packed ("message")
*       A2 - Hook specific address data ("object," e.g, gadget )
*
* ***  C conventions: ***
* Note that parameters are in unusual register order: a0, a2, a1.
* This is to provide a performance boost for assembly language
* programming (the object in a2 is most frequently untouched).
* It is also no problem in "register direct" C function parameters.
*
* calling through a hook
*       callHook( hook, object, msgid, p1, p2, ... );
*       callHookPkt( hook, object, msgpkt );
*
* using a C function:   CFunction( hook, object, message );
*       hook.h_Entry = hookEntry;
*       hook.h_SubEntry = CFunction;
****************************************************************************
* C calling hook interface for prepared message packet
_callHookPkt:
        movem.l a2/a6,-(sp)     ; protect
        move.l  12(sp),a0       ; hook
        move.l  16(sp),a2       ; object
        move.l  20(sp),a1       ; message
        ; ------ now have registers ready, invoke function
        pea.l   hreturn(pc)
        move.l  h_Entry(a0),-(sp)       ; old rts-jump trick
        rts
hreturn:
        movem.l (sp)+,a2/a6
        rts

* C calling hook interface for "varargs message packet"
_callHook:
        movem.l a2/a6,-(sp)     ; protect
        move.l  12(sp),a0       ; hook
        move.l  16(sp),a2       ; object
        lea.l   20(sp),a1       ; message
        ; ------ now have registers ready, invoke function
        pea.l   hpreturn(pc)
        move.l  h_Entry(a0),-(sp)       ; old rts-jump trick
        rts
hpreturn:
        movem.l (sp)+,a2/a6
        rts

* entry interface for C code (large-code, stack parameters)
_hookEntry:
        move.l  a1,-(sp)
        move.l  a2,-(sp)
        move.l  a0,-(sp)
        move.l  h_SubEntry(a0),a0       ; C entry point
        jsr     (a0)
        lea     12(sp),sp
_stubReturn:
        rts

Include File for the Example Programs

/***********************************************************************
 *
 * cb.h -- Include file used by clipdemo.c, changehook_test.c and cbio.c
 *
 ***********************************************************************/

struct cbbuf {

        ULONG size;     /* size of memory allocation            */
        ULONG count;    /* number of characters after stripping */
        UBYTE *mem;     /* pointer to memory containing data    */
};

#define MAKE_ID(a,b,c,d) ((a<<24L) | (b<<16L) | (c<<8L) | d)

#define ID_FORM MAKE_ID('F','O','R','M')
#define ID_FTXT MAKE_ID('F','T','X','T')
#define ID_CHRS MAKE_ID('C','H','R','S')

#ifdef CBIO

/* prototypes */

struct IOClipReq        *CBOpen         ( ULONG );
void                    CBClose         (struct IOClipReq *);
int                     CBWriteFTXT     (struct IOClipReq *, char *);
int                     CBQueryFTXT     (struct IOClipReq *);
struct cbbuf            *CBReadCHRS     (struct IOClipReq *);
void                    CBReadDone      (struct IOClipReq *);
void                    CBFreeBuf       (struct cbbuf *);


/* routines which are meant to be used internally by routines in cbio */

int                     WriteLong       (struct IOClipReq *, long *);
int                     ReadLong        (struct IOClipReq *, ULONG *);
struct cbbuf            *FillCBData     (struct IOClipReq *, ULONG);

#else

/* prototypes */

extern struct IOClipReq *CBOpen         ( ULONG );
extern void             CBClose         (struct IOClipReq *);
extern int              CBWriteFTXT     (struct IOClipReq *, char *);
extern int              CBQueryFTXT     (struct IOClipReq *);
extern struct cbbuf     *CBReadCHRS     (struct IOClipReq *);
extern void             CBReadDone      (struct IOClipReq *);
extern void             CBFreeBuf       (struct cbbuf *);

#endif

Additional Information on the Clipboard Device

Additional programming information on the clipboard device can be found in the include files for the clipboard device, iffparse library and utility library, and the Autodocs for all three. They are contained in the Amiga ROM Kernel Reference Manual: Includes and Autodocs.

|ll|

2cClipboard Device Information
Includes & devices/clipboard.h
& devices/clipboard.i
& libraries/iffparse.h
& libraries/iffparse.h
& utility/hooks.h
& utility/hooks.i
AutoDocs & clipboard.doc
& iffparse.doc
& utility.doc