NEWS, EDITORIALS, REFERENCE

Subscribe to C64OS.com with your favorite RSS Reader
May 2, 2019#82 Software

File-Based Clipboard

Post Archive Icon

I'll begin with a quick progress update. With the exception of the Widget Toolkit, the core C64 OS is very stable and nearly built out. I now find myself working almost exclusively (the topic of this post being a recent exception) on the applications and utilities that I want to be included as part of the OS.

I have a list of utilities that I plan to create, and I've made some detailed UI mockups on graph paper (my favourite UI prototyping medium). Some of these utilities will depend on the Toolkit and in fact will be the playground and dogfooding of the Toolkit. Others do not need the toolkit and so I've been focused on banging a few of those out. For example, I've made the About C64 OS utility, and the About This App utility. And most recently I've been working on the Scientific Calculator utility. The calculator turned out to be trickier than I thought it would be, as I first had to reverse engineer the behavior of calculators, and found to my surprise that they all behave subtley differently.

The main application I've been working on is the App Launcher, one of the two Homebase apps, used to launch into other applications and to open utilities. While working on this I've also been using it as a playground for figuring out how to send messages between the running application and any utility. So, for example, my original implementation of the Colors utility was to have a "Choose" button at the bottom. This would send a color to the application and close the Color utility. And then I had a way of passing an initial color to the Colors utility when it first opened. This turned out to be a terrible workflow, the Colors utility had to be opened and closed over and over again while trying to set the colors of the various aliases on the App Launcher's desktop.

 

I've reworked things so that the application can send messages to any open utility at any time, even if the utility doesn't know how to handle that message. And for an open utility to be able to send messages to the application while leaving itself open.

The "Choose" button has been removed, and as you click on colors a message for that color is sent to the application immediately. Meanwhile, in the App Launcher, as you click on different aliases the color of the selected alias is sent to the utility immediately. You can even drag selection boxes around multiple aliases. If more than one color is represented a special mixed-color message is sent to the Colors utility, allowing it to represent that however makes sense. Picking a color can thus set the color of multiple selected aliases.

It's pretty cool, it makes the Colors utility feel a lot more like the macOS color picker.


Now, about the Calculator. Most of its functionality is done. It's got good keyboard support, supports switching between radians and degrees, has implemented arcsin and arccos to complete the set of sin, cos, tan and arctan that BASIC provides. It has a delete button to correct a mistake in the number currently being entered. And it has the standard memory buttons, MC, M+, M-, and MR, and the memory value is preserved even when the calculator is quit and reopened later. I like that a lot.

What the calculator needs now is the ability to copy what's shown on screen to the OS's universal clipboard so the result can be pasted into an application. That is, after all, where the power of an operating system starts to become evident. When I'm using an application on my C64 and need a calculation, why not just use the calculator on my iPhone? Because, at the very least, I'd have to manually transcribe the result. An operating system becomes a useful—and even powerful—tool by allowing disparate features that were not explicitly coupled together to enhance each other in novel and unforeseen ways.

One way is via these generic message types that can be passed between application and utility, another way is by drag and drop, but another oft-overlooked way is the clipboard. The remainder of this post will be focused on my thoughts about, and the the implementational details of, the C64 OS clipboard.

The Clipboard; Memory and Datatypes

Apparently the concept of a cut/copy/paste clipboard has been around since the early 1970s. Somehow this does not surprise me. A lot of modern computer concepts we take for granted originated one, two, sometimes three, decades before I was born.

Here's another thing that is not a big surprise. Different operating systems implement their clipboard in different ways. The Wikipedia article Clipboard (computing) gives a pretty good overview of how most clipboards work. There are a couple of traits that need to be discussed.

Usually the clipboard is a reserved buffer in memory, that can be accessed by some programmatic interface provided to applications by the operating system. An application can put some data on the clipboard by copying the data into that memory buffer. And another application can paste the data by copying out of the clipboard buffer and into its own memory space. On the surface, this seems pretty straightforward.

The first complication arises from exactly how big the clipboard buffer is, and exactly how much data can reasonably be transferred in this way. If you select 10 words and select copy from a menu option, it seems reasonable that the OS copies those 10 words, those 100-someodd bytes into a buffer somewhere. But, what happens if you select a megabyte of text and hit copy? Should it automatically allocate a new megabyte-sized memory buffer, and copy a megabyte of data from place to place in memory? Where is the limit reached? What if it's a gigabyte of text?

What if the intention is merely to move some data from one document to another, that is owned by the same application? Does it really make sense to move a huge amount of data from one place in memory to another, only to move it back again to some memory area owned by the very same application whence it originally came? To deal with this, operating systems like macOS can put on the clipboard something like a data pipe with some metadata, and then it doesn't move the actual data anywhere. Upon pasting, the clipboard joins together the two applications, and the data is transferred by the receiving app reading it a byte at a time through the pipe. This is much better. Especially if you get in a situation where the source app copies a megabyte of text, but the destination only has a 256 byte buffer. The destination app can read through the pipe up until its own capacity is reached, and then stop. The overflow data perhaps never gets transferred anywhere at all.

Another complication is with differing data types. What happens if you select a file, in your file manager of choice, and hit copy? (First, this could be a huge file, so here we are in the above situation again.) But then, you open a text editor and hit paste. What's it supposed to do? What happens in macOS, at least, is that copying something can result in multiple representations. Let's say you select a JPEG file in the Finder, and select Edit → Copy. If you paste into a text editor, it pastes the name of the file which is a short string of text. But if you paste into something that deals with files, such as pasting to an FTP program or to another window in the Finder, a copy of the file itself is put there. And if you paste into a program that knows how to deal with image data, like say, Pixelmator, it extracts the data out of the file and puts that on a new layer in the current image.

That's pretty cool!

The Clipboard and Multitasking Applications

Everything described above about memory buffers, multiple datatypes, and pipes between apps, is predicated on a multitasking OS. And these clipboard features as well as multitasking apps are both predicated on a computer with a lot more memory.

Let's say we have only 30K of free memory, after the OS's code and the application's code. Now let's say our application is a web-browser, of sorts, using the MText renderer and the C64 OS proxy webservices. It has 25K of MText content in memory. (That's around 4,500 words, a decent sized article.) You've got 5K of free RAM. But now let's say that what you really want to do is copy 25% of the article, maybe 6K of text, so you can paste it into an email. The question is, where is that 6K supposed to go?

There are many situations where an in-memory buffer is just not going to fly. If you copy some data, you immediately double the memory requirements for that data, and it has to co-exist with the rest of all the data you haven't copied. How much you can copy will be highly constrained.

Okay, well, maybe this is the argument for just having the clipboard hold some sort of a pipe mechanism. The data can remain unduplicated until it starts to get read into the memory space of the app you're pasting into. This now assumes that both applications are loaded and running at the same time. But if we did that, we'd be even more memory constrained than we already are. I suppose it could flag a block of memory as the "copied" memory, and then you could quit the current app, and deallocate all of its memory except what's part of the copy buffer, then load another app in around it.

Sometimes zany ideas are good to roll around, but this one would be even more difficult than it sounds. A "copied" segment of text may not be stored sequentially in memory. Or, it may be embedded inside a larger, page-allocated, memory pool. Deallocating part of it and leaving part of it allocated would be a complex maneuver, which requires memory-consuming complex code to implement. Not to mention the possibility of leaving a giant island of unavailable memory in the middle of the next application's ordinarily free space.

Use the File System

Unlike in the olden days of a single 1541 disk side (144K total, and no guarantee about how much of even that is available,) now storage space is not an issue. A one-gigabyte SD Card is 16,384 times more than the entire 64K contents of RAM. And that's only "one" gigabyte. 32 gigabyte SD Cards cost… 9$. And you can buy them at Walmart. I love to reiterate this, our C64's storage capacity approximates to infinite.

The easy solution then is to just write the copied data out to a temporary file. Doing so has a few advantages:

  • You'll never not have enough space.1
  • The clipboard persists across applications, across reboots of C64 OS and across restarts of your C64.
  • You can easily store multiple clipboards or a clipboard history.

One question might be, why have a clipboard at all, if all you're going to do is write some data to a file and later read the data into some other app?

When I'm programming in Turbo Macro Pro I often need to move some snippet of code from one file to another. TMP can only have a single document open at a time, although it can preview a second document at the same time. This simple ability alone greatly increases productivity. But, TMP has no standard clipboard. It has the ability to define blocks, by setting a "block start" marker on some line, and then setting a "block end" marker on a later line. Once a block is defined, there are commands to move or copy the block to wherever the cursor is in the same document. This is entirely an in-memory operation, and something that any C64 OS text editor will someday have to support as well.

You can also "write block to disk." You give it a filename, which, according to how Commodore/CMD DOS works can be prepended by a partition and path specifier. The only catch about specifying the path from within Turbo Macro Pro is that it only allocates you space for a single 16 character filename. But, if your path and filename are short and you can fit them all into 16 characters, then you're good. I used to dream up a new filename every time I wanted to do this. Something like "temp1.t" And hope it wouldn't collide with anything I cared about. Then I'd load in the other source code file, and TMP has a feature to insert a text file from disk into the current file. You simply use this to read in your temp file. The thing I found generated the most friction was remembering what arbitrary temp filename I'd picked. I would frequently forget and have to list the directory looking for the file.

Eventually I realized that life would be much easier and go much smoother if I made the conscious decision to standardize on a "clipboard" temporary file. Then I could embed into my muscle memory the commands necessary to copy and paste. That worked out very well! The "write block to disk" filename I now always supply is this:

@//os/:clip.t

It's 13 characters long, so it fits in the space alloted for a single filename. The @ tells the DOS to overwrite the file if it already exists (which is the behavior I want for a "clipboard.") The //os/: is an absolute path to the root of my main working folder, where all my main source code files live. Reading the file back in to "paste" is then equally muscle memory based.

//os/:clip.t

The advantage of the absolute path means that I can forget about what sub-sub-folder I happen to be working in. My copies always go to the same place, and my pastes always come from that same place. It works very well.

I'd say the only real problem with this "solution," is that it is entirely ad hoc. And it took me like a year of persistent usage to evolve my usage pattern and muscle memory to the point where it now feels natural and I am quite efficient at moving chunks of text from file to file. What C64 OS needs is to make this technique consistent and standardized and yet obvious and easy.

There are some downsides, and a few things, like multiple datatypes, that we'll come back to.


Implementing a Clipboard

In theory, a process only needs to be consistent, standardized, obvious and easy, from the perspective of the user. As long as every application and utility implements the clipboard file the same way, the user will never know the difference, right?

In theory, there is no difference between theory and practice. But in practice, there is. Benjamin Brewster, 1882, https://quoteinvestigator.com/2018/04/14/theory/

Unfortunately, I know as well as any other programmer that developers are just another type of user, and if the process of development isn't consistent, standardized, obvious and easy for them too, then each one will implement the same thing a different way. Heck, the same programmer will implement the same thing two different ways in two of his own applications.

At its core, then, the clipboard is just a temporary file, in a common place that is accessed in common by every app and utility. To set this up, however, is quite an involved process, especially in assembly. Assuming the clipboard file will be somewhere within the system folder, first you'd need to call the routine from the service module getsfref. This allocates memory for a new file reference, and prepopulates it with the device, partition and path of the system folder. Next, if it is to be in a subfolder, you'd have to manually append the subfolder name onto the path. And then set the common clipboard filename into the fileref. Next, you'd need to pull in the file module, fopen the fileref for write (and overwrite or append), and then do one or more fwrites to get the data out.

So far, there is a lot of room for different implementations, and it sounds like a pain. After writing out the file, you'd have to fclose it, and then you'd probably want to free the memory used for the file reference. So you'd have to bring in the memory module too. The very next time the user wants to copy some data to the clipboard, the entire process has to be repeated. There is no way anyone, including me, would ever want to write all these steps into every application. But there is a worse problem than this merely being an annoying pain to code everywhere. What happens if I decide to change where that standard filename or path is? What if in version 2.0 I want to add support for using an REU to hold the clipboard? Every application would have hardcoded where and how the data gets written out! *shudder*

We need to conveniently wrap all of this into some lightweight calls.

The C64 OS Clipboard API

There are two main ways, the easy way but with some limitations, and the slightly harder but more flexible way. Let's start with the slightly harder way.

I've implemented these routines in the File module. Just as there are four main file routines, fopen, fread, fwrite and fclose, there are now four parallel clipboard-specific routines:

  • copen
  • cread
  • cwrite
  • cclose

These four routines can be used when the data you have to be written is stored non-contiguously. For example, in a text editor, the data is not necessarily stored in one long continuous string. If it were all one string, then after a certain length it becomes very inefficient to insert characters near the beginning of the string. Each insertion forces all subsequent data to be shifted down. A common alternative is to store each paragraph in its own allocated block of memory. Paragraph memory structures can be chained together in a doubly linked list.

The issue with a complex layout of data is that no external process can be expected to understand how the data is structured. This is one more reason why it wouldn't be possible for an application to be quit, but leave its data in memory for the next application to access. The next application could be anything, and can't be expected to understand the doubly linked list structure of that efficient text editor. It has to be the responsibility of the source application to serialize the data. In systems where two applications are running at the same time, that serialization can be deferred until the receiving application starts reading the data in, but for us, it has to happen when the user chooses copy.

To begin an output to the clipboard, we call copen. However, unlike with fopen, we do not need to pass in a file reference. Which means you don't have to allocate memory for one and you don't have to configure it. The path and filename of the clipboard is entirely opaque. That's a good thing. Every app that calls copen is necessarily opening the same clipboard. The one parameter that needs to be passed in is the standard fopen flags. We need to specify if we're opening the clipboard for read or write. And if for write, you can also OR in the append flag. This is pretty cool, as it allows an application to append data to the current clipboard. If the append flag is not used then overwrite is used implicitly.

UPDATE: May 6, 2019

See addendum to this post at the bottom. As cool as I thought it would be to allow the user to append data to the clipboard, I have since realized how problematic this could be. It is too easy for the clipboard to end up with mixed-type content. Everything is much more sane if "open, write, (write, write, write...) close" sessions are atomic. Copen now forces an overwrite if the write flag is used.

Once copen is called, the clipboard is open, but it doesn't return the file reference to you. It is just implicitly open. You can then call cwrite one or more times. Fwrite, as you may recall, makes use of inline arguments, because the amount of data to be passed exceeds what can be passed in registers alone. Partly this is because you have to pass in the file reference. To cwrite you don't need to pass the file reference. Instead you pass a RegPtr (X/Y) which points to the start of the data to be written, and the length of the data to be written in .A. Unfortunately the accumulator is 8-bit, so you can only specify a write length of up to 255 bytes. If the data is a terminated string (for example, the data in a paragraph memory structure may exceed 256 bytes, but still be a regular terminated c-string) then you can pass the accumulator as 0. Instead of writing zero bytes, which is useless, it instead interprets this as "write to the end of the string." All the internal writing is handled for you. If the data to write doesn't end at a string terminator, but still exceeds 255 bytes, then you have to call cwrite more than once.

Of course, you may have to call cwrite more than once anyway, in order to handle writing out discontiguous ranges of memory. When all the writing is done, you just call cclose. No parameters are required. It implicitly closes the internally managed clipboard file reference. The memory for that file reference is also automatically managed.

UPDATE: May 6, 2019

Cclose has been extended to take a datatype. See addendum to this post at the bottom, for further discussion on datatyping.

The Hard Way to Paste

Pasting data from the clipboard is in a similar boat. The main difference is that you are reading data of arbitrary length into memory that has a very specific capacity. For example, perhaps you are pasting into a text field that is backed by an allocated buffer that is exactly 100 bytes. But the clipboard might contain 35K of data!

Open the clipboard the same way, call copen but pass the read flag. It is now open and you can make one or more cread calls. Pass a RegPtr pointing to where you want the data to be pasted to, and this time use the .A register to specify how much data to read in. In the case of your 100 byte text field, you'd want to read in the total size, minus the insertion point offset. It will read in up to the maximum number of bytes you've specified, but if there is less data available on the clipboard it will stop when it hits the end of the data.

What if you want to read more than 255 bytes? When you call copen you can pass the stat flag along with the read flag. The size of the clipboard will be returned in a RegWrd (X/Y). When you call cread passing 0 in the accumulator will tell it to read in everything. Alternatively, you can make multiple calls to cread, changing where to read to with each call. When you're finished calling cread you have to close the clipboard with a call to cclose.

UPDATE: May 6, 2019

Clipboard metadata, including exact byte size and datatype have since been implemented. See addendum to this post at the bottom. Requesting the clipboard's file size in blocks (an imprecise measure) using the file stat flag is no longer supported.

The Easy Way to Copy and Paste

Using copen, cread, cwrite and cclose is fairly easy. It's much easier than the lower-level fopen, fread, fwrite and fclose parallels. But, it's still not exactly dead simple. What about for all those cases that are truly dead simple? I want to copy a 5 byte structure (like a float) and then paste those 5 bytes. Must I really make 3 separate calls? That would also involve importing those three routines into my source code. What a pain.

Instead, there are even lighter-weight wrappers around these.

  • clipin
  • clipout

If you know that you just have one small piece of data to copy to the clipboard, you can skip the rigamarole, and just call clipout. You pass a RegPtr to the start of the data, and pass the length to copy in .A. Or, once again, .A can be set to 0 to have it automatically write out to the end of the string. No need to open anything, no need to close anything, no memory to be managed, no file reference to think about.

Pasting from the clipboard can be done just as easily with clipin. Call clipin and pass a RegPtr to where to put the data. Pass the maximum number of bytes to read in .A, or 0 to read in everything.

Handling DataTypes

How to handle different datatypes is an additional consideration. In its initial implementation everything is assumed to be plain text. (text/plain).

The thought of using MIME types on the Commodore 64 seems like a bit of overkill. But the general idea is a good one. The only other system on the C64 that I know of (correct me if I'm wrong) that supports universal cut, copy and paste is GEOS. GEOS gets around this issue of data types by being quite simplistic. It supports two types of "scraps." Everything is either a text scrap or an image scrap.

The text scraps are not plain text, they are geoWrite document scraps, text that uses geoWrite formatting codes (like my proposed MText, but without the separation of semantics from styles.) Every GEOS app that supports pasting text can only paste a text scrap, and has to support the geoWrite format. If the application only wants plain text, it has to support extracting the plain text out of the geoWrite formatting.

Image scraps are hires bitmap, in the format of GeoPaint. There are no other types of scraps. Interestingly, separate text and image scraps can exist on the same disk at the same time. So if you make a selection in GeoPaint and copy, you get an image scrap in the root of the current disk (subdirectories are not supported.) Then if you open a geoWrite document, select some text and hit copy, you get a separate text scrap file on the same disk. But geoWrite has the ability to paste either image or text. To handle this, in the Edit menu are two separate paste functions! Commodore-T is the shortcut to paste text, or Commodore-W to paste an image. Whichever you choose, it looks for the presence of that kind of scrap on the disk and loads it in.

This is all well and good. But GEOS lacks the ability handle any other kind of data. For example, the GEOS calculator cannot copy its screen value to a scrap. And the Note Pad, another desk accessory, cannot paste an existing text scrap. And even further out of view, the GEOS calendar cannot copy the 1980's version of VCAL data, nor even copy a date. In those programs that are not the main tentpole applications (geoWrite, geoPaint, geoCalc, geoPublish, etc) if they support copying some text, they have to convert their content into the geoWrite text scrap format. After which, the true source format loses all identity, it just becomes any other generic text scrap.

GEOS Calculator. GEOS Notepad.

UPDATE: August 2nd, 2019

Upon realizing that the GEOS screenshot of the Notes desk accessory has a note written in German, I figured I should translate it to see what I'm actually displaying in my weblog. With the assistance of Google Translate, here's what it says:

"This is a small text editor that goes over several pages. The little notes can be inserted in any GEOS application."

Cute, but, I think the person who wrote this is mistaken. Perhaps they've made an assumption based on modern expectations. There is no way to copy the content out of the GEOS Notes desk accessory.


For now, for version 1.0, I'm going to leave the clipboard untyped. It will just be assumed to be plain text. But I have done some thinking about support for datatyping.

UPDATE: May 6, 2019

The ruminations that follow have since been implemented, more or less as discussed. See the addendum at the bottom of this post for more specific details on how datatypes have been implemented.

The idea of a MIME type is that the data has two types, a major general type, and a more specific sub-type. HTML, for instance, is just text, it can be opened by anything that can open text. But it's not merely text, because it's structured in such a way as to contain more information than just the plain text content. So its MIME type is text/html. Truly plain text on the other hand has a MIME type of text/plain. There are other types of text, like CSV (comma separated values), it's text, but it's structured with commas and carriage returns into rows and columns. Its MIME type is text/csv.

When some data is assigned a type, that is, when an application copies some data and declares its type, that type informs another application whether the data can be digested. If you're a text editor and you can view text, you only need to look at the general datatype, is it "text"-something? If so, the text editor can take it. If it's not, if it was actually some chunk of a hires bitmap that was copied to the clipboard, then the text editor can reject it, refuse to read it in.

It's not quite this smooth though, to be honest. Because it occurs to me that only the text type is generically digestable. Think about it, you have a GIF and a JPEG, their MIME types are image/gif and image/jpeg respectively. They're both images, right? So, there must be some magical image viewing software that doesn't care whether the subtype is JPEG or GIF, it's okay with just viewing any old image type data. This obviously is not the case. Unlike with text, it isn't just the case that the data in an image is semantically different, it's completely different. A JPEG is structured in a way that is completely and utterly uninterpretable by anything other than something specifically designed to work with JPEG data.

About the only way that the generic "image" type is useful is the ease of an image program in rejecting non-image type data, or the ease of a program that never handles images in rejecting image type data. But if you're looking for image data, and the type is image, that's not enough to be useful. You have to know the subtype or you can't even begin to deal with it, despite the fact that it's technically designated as some kind of image data.

There aren't very many generic types.

  • application
  • audio
  • font
  • image
  • model
  • music
  • text
  • video

There are a tiny handful of others that are extremely rare, and "application" is basically a generic catch-all for any binary data that usually needs a specific application to deal with it. All the others, besides text, are like I described for image. You can't do anything without the subtype.

Here is a list of common MIME types, though certainly not an exhaustive list.

I recommend converting each part, type and subtype, into 8-bit ints. Assign number values for each of the general types. Make text be 0, because it's the most common and most generic. Then assign a number to every subtype, unique only per type. So, type 0 is text, and subtype 0 is plain. Then let's say type 2 is audio, then say aiff can be 0. So, text/plain would be 0,0 and audio/aiff would be 2,0. Both subtypes are allowed to be zero and mean different things because they are unique per type. We can host a list of the assignments on C64OS.com, and as new ones get dreamed up, add them to the list. It may not be a perfect analog to the MIME types, but the Commodore 64 lives in a simpler world. It may be the case that there are 500 image file formats available to PCs, but >90% of them will be obscure and don't really need representation in our world.

Meanwhile, there are certain exchange formats that would be very useful but that don't have a MIME type. For example, a date. It seems useful to me that a C64 OS date picker should be able to export (via drag and drop, or to the clipboard) a standard date formated string, like YYYY-MM-DD, and then to declare its type as text/date (0,23 or whatever number gets assigned for the "date" subtype.) Then in another utility, such as Today that you could paste that date to it, and its interpretation of having a date pasted to it is to change to show that date to the user.

Other obvious ones include, an email address, a web url, a datetime, a 5-byte floating point number, a 3x3 character icon (the About This App utility could let you copy an app's icon, so it could be pasted to some other context,) a color code, a file reference, etc. A notes utility, that can take any text, can take a web url, or an email address, or even a date or datetime. But can't take a 3x3 icon (image type), or a float (application type). The long story short on datatyping is that it's very useful.

Reducing the type and subtype to one byte each is useful because it is trivially easy for an assembly program to work with. Whenever data is put onto the clipboard the type could be reset to something generic, like text/plain (0,0), but any application could optionally make a second call, cliptype (or something) that passes the type and subtype in X and Y. These could be stored in memory, just two bytes, and easily survive the quitting and reopening of various applications and/or utilities. The only problem I see is that, while the raw clipboard data would survive the computer being restarted, you'd list the type information unless it was being written to disk as well. (See addendum at the bottom of this post for details on how I've implemented this.)

Let's move on from this for now.

Underlying File System and Managing Clippings

While the clipboard routines obscure where the data is actually going, we should talk about it anyway.

In C64 OS, by design, everything necessary for a complete instance of the OS is contained within a single containing root folder. The "system" folder. Although, it can be named anything. Whatever the system folder is named simply has to be put into the main configuration file.

The root folder contains a number of sensibly named subfolders for organizing the resources.

  • applications
  • charsets
  • clipboard
  • desktop
  • docs
  • pointers
  • services
  • settings
  • utilities

Most of these are pretty obvious. And in amongst them is "clipboard." Inside this folder the default clipboard is a SEQ file named "0". (See addendum.)

The GEOS bundle included two clipboard related desk accessories: Text Manager and Photo Manager. Because these were desk accessories they could be opened concurrently with the main application. Each consisted of a notepad-like UI, with a dog-earred bottom left corner. Clicking the dog-ear lets the user flip backwards and forwards through the pages. Each page could contain one scrap. While you were on a page of one of these managers you could pick Edit → Copy to copy that page out to be the current scrap. If you picked Edit → Cut it would simultaneously remove it from the manager and save it out as the current scrap. You could also use Edit → Paste to paste whatever is the current scrap into the manager to save for later. All in all, that's pretty great functionality, I really like that.

Screenshot of GEOS Photo Manager. Design document for C64 OS Clipboard Manager

C64 OS will include a "clipboard" utility. Similarly to GEOS desk accessories, C64 OS utilities can be opened concurrently with whatever is the current application. My idea for this is to be a combination of a scrapbook and a clipboard viewer. The left side lists 8 "slots" for saving 8 clippings. The right side has two tabs, preview and current. Preview shows you the contents of the selected slot, and current shows you whatever is currently on the clipboard. When the current tab is selected, the "save" button is shown, and the "use" button is hidden. When the preview tab is selected, the opposite. The "save" button is hidden, and the "use" button is shown.

The save button allows you to save the current clipboard into one of the slots. And the use button allows you to use one of the saved clippings as the current clipboard. The delete button is there to empty the selected slot.

The GEOS text and photo managers are clever in their use of cut, copy and paste for managing their contents. But, I feel it's a bit too clever. The actual scrap remains ethereal, you never really have access to it. You can only paste it into the scrap manager the same way you can paste it into some other application. And you can't delete something from the manager, without overwriting the current scrap, because you have to "cut" it out of the manager. Whereas my clipboard metaphor is meant to be the actual thing to which cuts and copies go. But, they always go, by default, onto the topmost page. But if you want to you can save the topmost page to some other lower page. And if you want, you can tear one of those pages out and throw it away. Or, you can copy a saved page back to the topmost page. But, there is no clipping that is not contained by the clipboard utility.

Final Thoughts

The original impetus to figure out the clipboard is that I've just written the Calculator utility and I wanted to be able to copy the result for pasting elsewhere. With the above implemented this is now possible. A simple double-click of the calculator's screen calls clipout with a pointer to the start of the screen's string buffer, and a 0 in .A to write out to the end of the string. That's it. Supporting copying the calculator screen to the clipboard will probably take about 15 bytes of code!

My final thoughts are to come back to the idea of datatypes associated with the clippings.

If a datatype were assigned to the current clipboard, somehow that would have to be preserved to disk, perhaps to an alternative file in the clipboard folder. Perhaps 0.d is for data, and 0.t contains its corresponding type. The clipboard manager would have to manager both files, for example, if you saved the current clipboard to slot 5, it would first delete 5.d and 5.t. And then copy 0.d to 5.d and 0.t to 5.t.

When previewing the data of the clipboard, if its data type isn't previewable it would have to just show something like "No Preview Available." and then show its datatype.

For now, it's text/plain.


Addendum: Datatyping Implemented

After I wrote this post, I got it up online for last Friday afternoon. (I've sinced fixed numerous small typos and improved some awkward sentences.) I went home and all I could think about was this issue of datatypes for the clipboard contents. So I sat down on Friday night and implemented the ideas above, fleshing out whatever was missing. By Saturday night I'd tested, debugged, and implemented copy functionality (with differing datatypes) into three different places.

List of C64 OS datatypes

The C64 OS source code now contains a new constants file:

//os/s/:types.s

This declares the types, ct_text = 0, ct_plain = 0, etc. Grouped and nested according to their relationship. I added in maybe 25 or 30 types and subtypes, the ones I think are the most important and the most common. I will publish this list to the C64 OS technical documentation, but I will only do this closer to the time of release for C64 OS v1.0, as I want to maintain the ability to make changes to it up until the first release. After that, it will become a canonical resource.

Conversion from MIME type to a C64 OS type is just a matter of association. text/plain shall forever be 0,0.

Declaring a Clipping's Datatype

First I'll talk about how a program declares and checks the datatype of the clipboard. Then I'll return to discuss how that is stored in memory and on disk.

If you are using copen, cwrite, and cclose, the "append" option has been removed. Every time you copen with the write flag passed in, regardless of whether you specify overwrite or append as other flags, the flags are rewritten as overwrite before being forwarded to fopen. This is to guarantee that the contents of the clipboard are always of one type, and the clipboard is always in a state that it can be written.

The call to cclose formerly took no parameters. Now, it takes the type and subtype in X and Y respectively. There is no need for a special routine, cliptype, or some such. You copen with the write flag. You then make one or more cwrite calls until all the data is output, you then call cclose and simultaneously declare the type and subtype that you just wrote. Very clean.

When you use clipout, it automatically supplies the type and subtype as text/plain. Therefore, while clipout is fast and convenient, it can only be used for things that wish to declare the type as text/plain. If you want to copy some other type, use copen, cwrite and cclose.

Clipboard Metadata

The datatype of the clipboard is clipping metadata. This metadata needs to be stored, and we'll come back to that in a moment. But, now that we're in the game of having clipboard metadata, the precise size of the clipboard data is also important to have. Originally, I'd thought, well, the user can just add the file stat flag when calling copen. This can be forwarded to fopen which will read the block count of the clipboard file and return it from copen. This would work, but it's clumsy. Block count is only precise to the block, rather than to the byte. And it takes more work and effort to get that information.

It occurs to me that cwrite knows precisely how many bytes you're writing out, so it tracks the size of the clipboard. It zeroes the size upon copen, then expands the number with each cwrite, up until the cclose, at which time it has the final byte size and the type and subtype all together.

The size of the clipboard is a 16-bit number, for a maximum size of 64K. But, given the memory size of the C64, I think this is sufficient.

Storing and Restoring the Clipboard

Here's how it works. There are 4 bytes reserved in workspace memory for the clipboard.

  • type
  • subtype
  • size low
  • size high

When copen is called, size low and size high are set to zero. Each time cwrite is called, the 16-bit byte count passed to fwrite is added to size low and size high, in memory. When cclose is called, the type and subtype are written to memory. The clipboard's data file 0.d is closed. The file reference to the clipboard 0.d is updated to set the filename's extension to "t" for type and metadata. And then the 4-byte metadata structure is written out.

When pasting in from the clipboard, the metadata does not need to be read in from disk. It is already available in memory, and it's always an accurate reflection of what is currently on the clipboard. Only when C64 OS is rebooted, or the C64 is restarted, is that in-memory metadata lost. The C64 OS Booter, then, has been updated to load 0.t into workspace memory. During boot is the only time that 0.t needs to be read, for working with the one standard clipboard.

Using clipout is exactly the same process. Clipout wraps calls to copen, a single call to cwrite, and then a call to cclose with text/plain supplied automatically. Everything else at a lower level works the same.

The Clipboard Utility

Lastly then is the clipboard utility. It will need to read in the x.t files to know the size and datatype of each slot. When clicking on a slot, if the type is not text, it can just display the type instead, and it can also conveniently report on the exact byte size of each slot and the current clipboard.

When saving the current clipboard to a slot it will simply copy 0.t and 0.d to the slot's x.t and x.d files. When using a previously saved slot as the new current clipboard, it will copy that slot's x.t and x.d file to 0.t and 0.d. However, it will also have to read in that 0.t file into the clipboard workspace memory. And that's it!

Final, Final Thoughts

I have already implemented the ability to copy to the clipboard the 3x3 character custom icon of an application via the About This App utility. It puts 72 bytes into 0.d, and it puts type image and uses the custom "3icon" as the subtype, as well as putting 72 as the size into 0.t.

It works very well. And opens interesting doors. In macOS, from the Get Info panel of the Finder, users have long been able to copy and paste custom application and document icons. It now feels trivially easy to do this in C64 OS. A paste to the "About This App" icon merely needs to check if the type is image/3icon. If not, just give the standard OS "alert"2 and do nothing.

But if the clipboard contains image/3icon data, regardless of where that data originally came from, the utility can write a file "alt.icon.charset" to the application bundle, leaving the existing, original, "icon.charset" in tact. This allows the user to restore the original icon if the user issues a "cut" or "delete" to the About This App utility. Either one removes alt.icon.charset leaving the original behind. And Cut, of course, puts alt.icon.charset back onto the clipboard.

I LIKE IT!

  1. One should never say never. What happens if you're running C64 OS not from an SD Card but from a CMD FD? Well. Bad things will ensue. Try running macOS off a startup disk that has just given you the warning that it's out of space. Startup Disk Almost Full []
  2. I've been toying with how to create a "standard" system alert. One way is to flip bit 3 of the border color, momentarily, and then flip it back a few hundred milliseconds later. It would leave the border in its original color, but produce some visual feedback that some action got rejected. Such as, any unhandled keyboard shortcut, or a paste that is rejected because of an incompatible type. An alternative is to play a short, SID tune, but this option would not be available if a SID player were already using the SID. So some system flag would be necessary to keep track of when the SID is in use. []

Do you like what you see?

You've just read one of my high-quality, long-form, weblog posts, for free! First, thank you for your interest, it makes producing this content feel worthwhile. I love to hear your input and feedback in the forums below. And I do my best to answer every question.

I'm creating C64 OS and documenting my progress along the way, to give something to you and contribute to the Commodore community. Please consider purchasing one of the items I am currently offering or making a small donation, to help me continue to bring you updates, in-depth technical discussions and programming reference. Your generous support is greatly appreciated.

Greg Naçu — C64OS.com

Want to support my hard work? Here's how!