Copyright (c) Hyperion Entertainment and contributors.
Clipboard Device
Contents
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
<tbody> </tbody>Command | Command Operation |
CBD_CHANGEHOOK | 8.1cmSpecify a hook to be called when the data on the clipboard has changed (V36). |
CBD_CURRENTREADID | 8.1cmReturn 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 | 8.1cmReturn 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 | 8.1cmRead data from the clipboard for a paste. Data can be read from anywhere in the clipboard by specifying an offset <math>>0</math> in the I/O request. |
CMD_UPDATE | 8.1cmIndicate 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