Section 15 Home page Section 17

16. Scrap Library

The scrap library is GEM’s version of the cross-application clipboard. Using the scrap library, we can copy information from one application and paste it into another.

First, the bad news: many programs do not use the GEM scrap library. For example, some word processors such as Marcel and Protext use their own custom clipboards, and so do not support copy and pasting information to or from other applications. This is also true of our favourite IDE, AHCC.

Second, the even worse news: as with most features of GEM, the scrap library leaves everything to the programmer. The scrap library is nothing more than a file stored in a known folder. This folder is identified by calling scrap_read, which retrieves the current scrap folder. In addition, as different desktop environments may provide paths to the scrap library/clipboard, we need to check for these. If there is no current scrap folder, then our program must create it.

Once the scrap folder is located, we either read from a file named "SCRAP", if we are, for example, pasting a piece of copied information. Alternatively, we may write to a file named "SCRAP" if we are copying some information. The file extension for the "SCRAP" file is set appropriate to the data we are saving, e.g. ".TXT" for text data. When writing a file, we need to first delete any existing files named "SCRAP.*" in the scrap folder.

One final refinement: if we save a file into the scrap folder, we should let other applications and the desktop know, so they can update their displays or information about the scrap library.

16.1. Using the scrap library

I will simply give two functions here, which firstly find the location of the scrap folder, and secondly update any other users of the scrap folder when we have changed it.

The following function requires a reference to some allocated space, and will place into that space a full path name to a file "SCRAP.TXT" in the scrap folder. If the scrap folder does not already exist, it will be created.

void get_clipboard_file (char * scrap_path) {
        char * env_scrap_path = NULL;
        char dirname[PATH_MAX];
        struct ffblk * cur_file;

        /* check possible environment variables                    <1> */
        shel_envrn (&env_scrap_path, "CLIPBOARD=");
        if (env_scrap_path == NULL) {
                shel_envrn (&env_scrap_path, "CLIPBRD=");
        }
        if (env_scrap_path == NULL) {
                shel_envrn (&env_scrap_path, "SCRAPDIR=");
        }

        if (env_scrap_path == NULL) {
                /* if not found, use the scrap_path result */
                scrp_read (scrap_path);                         // <2>
        } else {
                /* copy env_scrap_path into scrap_path */
                strcpy (scrap_path, env_scrap_path);            // <3>
        }

        /* check we have a valid path, adding \ to end if needed */
        if (scrap_path[strlen(scrap_path)-1] != '\\') {
                if (strlen(scrap_path) < PATH_MAX - 2) {
                        int end = strlen(scrap_path)-1;
                        scrap_path[end] = '\\';
                        scrap_path[end+1] = 0;
                }
        }

        /* set up clipboard folder if one does not exist           <4> */
        if (strlen (scrap_path) == 0) {
                int curr_drive = Dgetdrv ();
                /* cannot modify an in-place string, so copy it:
                   this caused a strange error, where menu would not
                   reappear when re-focussing the application.
                 */
                char * folder = strdup("A:\\CLIPBRD\\");

                folder[0] += curr_drive; /* set to current drive */
                Dcreate (folder);        /* make sure it exists         <5> */
                scrp_write (folder);     /* write the clipboard folder  <6> */
                scrp_read (scrap_path);  /* read it back in */
                free (folder);
        }

        /* delete any SCRAP.* files currently in the scrap directory */
        strcpy (dirname, scrap_path);
        strcat (dirname, "SCRAP.*");
        if (findfirst (dirname, cur_file, 664) == 0) {          // <7>
                do {
                        char * filename = malloc (sizeof(char) * PATH_MAX);
                        if (strcmp (cur_file->ff_name, ".") == 0) continue;
                        if (strcmp (cur_file->ff_name, "..") == 0) continue;
                        strcpy (filename, scrap_path);
                        strcat (filename, cur_file->ff_name);
                        remove (filename);
                } while (findnext (cur_file) == 0);
        }
        /* Write data as plain text */
        strcat (scrap_path, "SCRAP.TXT");                       // <8>
}
  1. First, look in some standard environment variables for an existing path to the scrap folder.

  2. If one is not found, use scrap_read to see if GEM already has a record of the scrap folder.

  3. If one was found, use that one by copying it to scrap_path.

  4. If no existing path has been found, then we need to create the scrap folder.

  5. Create folder "CLIPBRD" in the current drive.

  6. And set it as the scrap folder using scrap_write, reading this back into scrap_path using scrap_read.

  7. Use findfirst and findnext to loop through all the "SCRAP.*" files existing in the scrap folder, to delete them.

  8. Finally, append the name of the scrap file to the path.

The following code can be used to update the clipboard observers, given the path of the scrap folder. The message types and appl_search functions are only suitable for later versions of TOS 4.0+, so don’t use this function on TOS 2.06 or earlier on your Atari ST.

void update_clipboard_observers (char * path) {
        short msg[8] = {0, 0, 0, 0, 0, 0, 0, 0};
        char name[PATH_MAX];
        int id, type;

        if (strlen(path) < 3) return; /* if too short to be a path */

        /* update desktop                                       <1> */
        msg[0] = SH_WDRAW;
        msg[1] = app_handle;
        msg[3] = toupper(path[0]) - 'A';                      // <2>
        if (appl_search (APP_DESK, name, &type, &id) == 1) {  // <3>
                appl_write (id, 0, msg);                      // <4>
        }

        /* inform other applications                            <5> */
        msg[0] = SC_CHANGED;
        msg[3] = 0x0002; /* updated a text file */
        if (appl_search (APP_FIRST, name, &type, &id) == 1) { // <6>
                do {
                        appl_write (id, 0, msg);              // <7>
                } while (appl_search (APP_NEXT, name, &type, &id) == 1); // <8>
        }
}
  1. If the desktop is showing the scrap folder, then we want it to refresh its display.

  2. The third argument gives the drive number: 0 is A, 2 is C etc.

  3. Finds the id reference for the desktop.

  4. Sends the desktop the message to update its display of the given drive.

  5. Other applications may be dealing with the scrap folder, so we let them know the scrap folder has changed.

  6. Use appl_search to locate the first application.

  7. Send the message to the current application.

  8. Loop with the next application, until all applications have been called.

16.2. Examples

Of my own programs, only BibFind uses the scrap library: http://peterlane.info/bibfind.html

For a sophisticated use of the scrap library, and indeed GEM in general, see GEMClip: http://gemdict.org/gemclip