Copy & Paste for PET 2001
A quick update on the PET 2001 online emulator.
The PET 2001 online emulator just received its first sponsored update, by this advancing to version 1.4. And this update is all about Copy & Paste integration. — TLDR version; have a look at the respective section in the online help, accessible via the “Help” button on top of the emulator’s page.
Implementation
Thing is, I had been thinking about this for some time. There was even some (unused) code for this and a context menu to the emulated PET screen for copying the screen contents as Unicode text. However, I had been a bit wary regarding how to implement this in more general way, especially the integration of paste. There are mainly two reasons for this:
- Varying browser support for the clipbard integration and varying approches to security implications of this.
- What would be the best way to transfer any pasted content to the virtual PET?
How it Works — Copy & Paste Interaction
First things first, this is how it is finally implemented:
1) Copy & Paste via the Screen Context Menu
Regardless of how clipboard access is supported by the browser, the context menu of the emulated screen will show as its first two items:
- Copy As Text
This exports the contents of the screen as Unicode.
If the browser supports clipboard interaction for this feature, the text will be immediately transferred to the system clipboard. Otherwise, a dialog similar to one brought up by the option “Utils/Export” > “Screen Text As Unicode” will be displayed. - Paste To PET
If the clipboard feature is supported, this will directly transfer the text content of the system clipboard as typed text to the PET. A browser may show an additional button (typically labeled “Paste” or similar) or a permissions dialog (to be acknowledged once) as a security feature to confirm the interaction. If the clipboard interaction is not directly supported by the browser (e.g., Mozilla Firefox or older browsers), selecting the context menu item will bring up a dialog with a text-area for pasting.
Generally, “Paste” is available only, if the emulated PET is in interactive state, i.e. running (not paused) and showing an active cursor (more about this in a minute.). For both interactions, we’re displaying a dialog with a text-area for convenient transfer as a fallback. (Another way of working around this, would be using a transparent overlay set to “content-editible
”, however for our purpose, this would collide with our established means of keyboard input.)
The good news is that any pasted text will be evaluated for case (so this should work conveniently both with lower-case text and with upper-case text) and you may use any of the usual markup for PETSCII codes, like “{ddd}
”, “{$hh}
”, or “{label}
”.
2) Paste via the Browser Menu and Shortcuts
There’s also an even simpler option for pasting: If this is supported by the browser (as of writing this, this feature is suported by all major desktop browsers), selecting “Paste” from the browser’s menu or pressing the related keyboard shortcut, like Ctrl + V
or Command + V
, will transfer the content of the system clipboard to the emulated PET, as well. The emulator will pop-up a short confirmation dialog, just to make sure.
With the important stuff out of the way, we may turn our attention towards our second concern, namely, how this should interact with the emulated PET.
The Way to Implementation
Copy is quite simple: just grab the screen contents and convert it to Unicode equivalents for further use. This was already implemented, both by a context menu item that may have been available or not and by the “Screen Text As Unicode” export option, which is now used as a fallback.
Paste, however, is a different beast. How should this work? Should we jam the content somehow into memory, load this as a virtual program from an emulated floppy disk (like it is already implemented for drag’n’drop file transfer), or is there an even more usable approach to this?
The solution, I came up with, is a ghostly auto-type operation of the PET’s keyboard. Thus, we may not only enter a program, we can also supply input to a program (which may come quite handy for testing) or even mix both applications. It will even work with programs that prefer to directly read the keyboard matrix instead of relying on the PET’s Kernal.
E.g., pasting the following will enter, run, and supply input to the executing program:
10 PRINT "RECTANGLE" 20 INPUT "SIDE A"; A 30 INPUT "SIDE B"; B 40 PRINT "AREA: "; A*B RUN 4 5
Which results in the following automated operation:
Here is another one (by Daryn Bee):
10 rem *** commodore pet 2021 *** 20 R=0 30 r=r+0.3 40 p=20+sin(r)*10 50 print tab(p);"pet" 60 goto 30 run
BTW, Daryn Bee isn’t especially new to this, as proven by this popular image:
So now that we know what we’d want to do, we’re left with the question of how to perform the trick.
The emulator already had a basic auto-typing facility, mainly used for automatic loading and execution of programs. However, this was basically (no pun intended) based on ASCII input. Which isn’t necessarily the best way to handle things, since we’ll have to transcode any input forth and back, from Unicode to PETSCII (for all the fancy PETSCII character markup) and from this to ASCII, which would be entered into the virtual PET in order to become PETSCII code again. Since ASCII and PETSCII are not a perfect match, this comes with the caveats of losing some in translation.
You might ask, why is the emulator handling input by ASCII code at all? Simply, because any keyboard input as read by the emulator will be in ASCII (actually in Unicode, but almost all relevant keys are in the ASCII subset) and the principal purpose of the emulation is to transfer this to the emulated hardware.
The core of the emulator was written by Tom Skibo, who implemented this by two lookup tables converting ASCII codes to appropriate values for the keyboard matrix scanned by the PET 2001. — In best medieval tradition, I’m just the dwarf sitting on the shoulders of giants. — The lookup tables look like this, providing matrix values in rows of 16 for the first 127 ASCII values:
var ascii_to_pet_row = [ -1,-1,-1,-1,-1,-1,-1,-1, 0,-1,-1,-1,-1, 3,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 4,-1,-1,-1,-1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 4, 4, 1, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 2, 3, 4, 4, 4, 3, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 4, 0, 4, 1, 0, -1, 2, 3, 3, 2, 1, 2, 2, 2, 1, 2, 2, 2, 3, 3, 1, 1, 1, 1, 2, 1, 1, 3, 1, 3, 1, 3,-1,-1,-1,-1,-1 ]; var ascii_to_pet_col = [ -1,-1,-1,-1,-1,-1,-1,-1,15,-1,-1,-1,-1,10,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 9,-1,-1,-1,-1, 6, 0, 1, 2, 3, 4, 6, 5, 8, 9,15,15, 7,14,13,15, 12,12,13,14,12,13,14,12,13,14, 9, 8, 7,15, 8, 9, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 3, 7, 4,10,10, -1, 0, 4, 2, 2, 2, 3, 4, 5, 7, 6, 7, 8, 6, 5, 8, 9, 0, 3, 1, 4, 6, 3, 1, 1, 5, 0,-1,-1,-1,-1,-1 ];
As there’s probably no way around this, implementing a way for transferring code, which is already in PETSCII, encompasses the implementation of a new input method based on PETSCII codes.
Therefore, implementing paste and the associated code transfer consists of the following tasks:
- Implement a general routine to convert a text to a stream of PETSCII codes.
This also evalutes case (done individually for each line) and resolves any markup for PETSCII codes. Any future additions that may require a service like this, should be handled by this routine, as well. - Implement the paste-glue for the UI.
- Implement a PETSCII keyboard matrix.
- Rewrite the auto-type routine in order to use
- said transcoding routine, see (1)
- said PETSCII lookup tables, see (2)
PET 2001 Keyboard Matrix
In order to come up with the rquired lookup tables, we’ll have to concern ourselves with how the PET reads the keyboard. This is done at the beginning of the vertical blank period of the screen (which occurs 60 times per second with the NTSC standard, which is also used by the PET) in the main interrupt routine, AKA IRQ handler. The keyboard matrix is scanned by rows and columns, and stops, when a connection (i.e. pressed key) is encountered, on this with the row number in Port A of PIA 1 ($E810), bits 0-3 and the column number in Port B ($E812), bits 0-7. As may be inferred from this, there are 4 bits for the row number and 8 bits for a column each, and the respective bits are set on default with an unset bit indicating the pressed key.
Here’s an overview over the keyboard matrix and how it is scanned to PIA 1, Ports A and B:
As may be observed, there are 5 keyboard rows (encoded in the 4 bits of PA0…PA3) and 8 vertical wires, which cover two key columns each in a winding, serpent-like fashion and are directly stored in PB0…PB7. As we may already know, any shifted keys will result in a PETSCII code with the high-bit (bit 7) set, so we’ll have to deal with alpha-numeric keys only, since the order of the graphical characters in the PETSCII character set is directly derived from the PET 2001 keyboard layout, which halves our problem.
A quick web search brings up the following decoding table:
(…) The position is 8 * (9 - row) + (7 - column) + 1, i.e. starting at 1, incrementing left to right and top to bottom. The keyboard is scanned by writing the row number into the row select port ($E810), then reading the column bits ($E812). Each bit that reads 0 represents a pressed key. ; 00 = Shift ; 10 = Repeat ; 80 flags unshiftable key ; FF = No key Graphics keyboard decoding table: ----+------------------------ row | 7 6 5 4 3 2 1 0 ----+------------------------ 9 | 3D 2E FF 03 3C 20 5B 12 | = . -- ^C < sp [ ^S ^C = STOP, ^S = HOME | 8 | 2D 30 00 3E FF 5D 40 00 | - 0 rs > -- ] @ ls rs = right shift, ls = left shift | 7 | 2B 32 FF 3F 2C 4E 56 58 | + 2 -- ? , n v x | 6 | 33 31 0D 3B 4D 42 43 5A | 3 1 ^M ; m b c z ^M = return | 5 | 2A 35 FF 3A 4B 48 46 53 | * 5 -- : k h f s | 4 | 36 34 FF 4C 4A 47 44 41 | 6 4 -- l j g d a | 3 | 2F 38 FF 50 49 59 52 57 | / 8 -- p i y r w | 2 | 39 37 5E 4F 55 54 45 51 | 9 7 ^ o u t e q | 1 | 14 11 FF 29 5C 27 24 22 | ^T ^Q -- ) \ ' $ " ^T = DEL, ^Q = cursor down | 0 | 1d 13 5F 28 26 25 23 21 | ^] ^S <- ( & % # ! ^] = cursor right, ^S = home ----+------------------------ The original gfx keyboard reflects the matrix perfectly: ! " # $ % ' & \ ( ) <- ^s ^q ^] ^t q w e r t y u i o p ^ 7 8 9 / a s d f g h j k l : ^m 4 5 6 * z x c v b n m , ; ? ^m 1 2 3 + LS ^r @ [ ] SPACE > < ^c RS 0 . - = The shift key always set the high bit, producing a graphic character.
Thankfully, this table already lists PETSCII codes by keyboard rows and columns. It should not be too difficult to come up with a tiny program that reads the table and collects the according row and column numbers for our lookup table. However, there’s a problem with this. The table lists the codes in 10 rows and 8 columns, whereas the lookup table corresponds more to the physical arrangement, with 5 rows and 16 columns. Oops.
The table also comes with a conversion formula that is also applied by the emulator in a second, even more low-level step, which converts this to values to be jammed into PA and PB of PIA 1. So we’ll have to reverse this formula in order to come up with our lookup tables:
var petscii_to_pet_row = [ 4,-1,-1, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1, 3,-1,-1, -1, 0, 4, 0, 0,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1,-1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 4, 4, 1, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 2, 3, 4, 4, 4, 3, 4, 2, 3, 3, 2, 1, 2, 2, 2, 1, 2, 2, 2, 3, 3, 1, 1, 1, 1, 2, 1, 1, 3, 1, 3, 1, 3, 4, 0, 4, 1, 0, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, ]; var petscii_to_pet_col = [ 0,-1,-1, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,-1, -1,13, 1,12,15,-1,-1,-1,-1,-1,-1,-1,-1,14,-1,-1, 6, 0, 1, 2, 3, 4, 6, 5, 8, 9,15,15, 7,14,13,15, 12,12,13,14,12,13,14,12,13,14, 9, 8, 7,15, 8, 9, 2, 0, 4, 2, 2, 2, 3, 4, 5, 7, 6, 7, 8, 6, 5, 8, 9, 0, 3, 1, 4, 6, 3, 1, 1, 5, 0, 3, 7, 4,10,10, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, ];
Any values of -1
represent inputs which can’t possibly be generated by the PET’s keyboard. These are especially the last two rows of each table, where the PETSCII character set has duplicate values for the range of 0x20
…0x3F
. Now, instead of losing any input on any PETSCII codes falling into this range (as may be accomplished by PETSCII code markup by value), we’d better copy the respectives values for these as a mirror. By this arriving at our final PETSCII-to-keyboard lookup tables:
var petscii_to_pet_row = [ 4,-1,-1, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1, 3,-1,-1, -1, 0, 4, 0, 0,-1,-1,-1,-1,-1,-1,-1,-1, 0,-1,-1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 4, 4, 1, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 2, 3, 4, 4, 4, 3, 4, 2, 3, 3, 2, 1, 2, 2, 2, 1, 2, 2, 2, 3, 3, 1, 1, 1, 1, 2, 1, 1, 3, 1, 3, 1, 3, 4, 0, 4, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 4, 4, 1, // 0x60.. 0x7F same 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 2, 3, 4, 4, 4, 3 // as 0x20..0x3F ]; var petscii_to_pet_col = [ 0,-1,-1, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,10,-1,-1, -1,13, 1,12,15,-1,-1,-1,-1,-1,-1,-1,-1,14,-1,-1, 6, 0, 1, 2, 3, 4, 6, 5, 8, 9,15,15, 7,14,13,15, 12,12,13,14,12,13,14,12,13,14, 9, 8, 7,15, 8, 9, 2, 0, 4, 2, 2, 2, 3, 4, 5, 7, 6, 7, 8, 6, 5, 8, 9, 0, 3, 1, 4, 6, 3, 1, 1, 5, 0, 3, 7, 4,10,10, 6, 0, 1, 2, 3, 4, 6, 5, 8, 9,15,15, 7,14,13,15, // 0x60.. 0x7F same 12,12,13,14,12,13,14,12,13,14, 9, 8, 7,15, 8, 9 // as 0x20..0x3F ];
A Last Question
Having accomplished this, we’re left with a final question, namely, how to tell our users about this. At least, it’s not a feature too obvious and too easily to be discovered. In the days of UX, best practice is to smack this in the face of the user, as a friendly pop-up notification, probably adorned by a smiling emojy to celebrate our achievement. However, I’m more aligned to a usability mindset and not that great a friend of any modal pop-ups, which are to be clicked away first in order to advance to the content, thus introducing a self-laudatory obstacle between the application and the user.
Which is why there is this post. :-)
Norbert Landsteiner,
Vienna, 2021-02-17