Terminal Emulation
Volume Number: 2
Issue Number: 3
Column Tag: Threaded Code: Neon
Scrap Support for Terminal Emulation 
By Jörg Langowski, EMBL, c/o I.L.L., Grenoble, Cedex, France,
MacTutor Editorial Board
"NQSD" terminal emulator
First, lets continue and finish the example from last month's NEON column by
adding (finally!) the scrap support (You may remember that I promised to write
something about scrap handling some time ago).
Actually, I have reworked the example somewhat. Neon supports a rudimentary
form of multitasking which makes event handling a lot simpler, so I added that; then, of
course, I promised to add support for serial I/O so that we would end up with a
not-quite-so-dumb terminal program (not quite, since we will be able to do
cutting/pasting and therefore capture text files). The 'finished' example (to be
expanded and improved by you) is shown in Listing 1.
Scrap handling in NEON (and other Forths, for that matter)
Let's first look at the way the scrap can be passed around between applications. I
will limit myself to text scrap only, since that's what our example is using.
The crucial point here is that the 'scrap' that TextEdit uses is internal to the
TextEdit routines, so that it will not automatically become part of the clipboard after a
TECut is executed. If you don't believe this, start up last month's example and open the
Note Pad desk accessory. Cut or Copy some text from the TE window (using the
TEmenu), click the notepad and then try to paste it (using ctrl-V, since the Edit menu
has been lost). Nothing happens. Also, if you cut text from the notepad and try to paste it
into the TE window, that won't work either.
This proves what you might have known already, namely that the desk scrap
(contained in the Clipboard) does not have anything to do with the TE scrap, and that we
need a set of routines that transfer text between those two kinds of scrap.
Refer to the listing. The magic numbers that we need are system globals, the
length of the TE scrap at $AB0 and its handle at $AB4. The length of the desk scrap is
maintained in $960.
Two routines transfer the scrap from TE to desk and vice versa. put.tescrap, as
you might have guessed, takes the TE scrap and puts it into the clipboard. It uses the
toolbox trap putscrap, which is a function that returns a 32-bit result code (0 if ok)
and is called with the stack set up as follows:
- 0 for the return code
- a 32-bit integer giving the length of the data that goes into the scrap
- the resource type, 'TEXT' or 'PICT'
- a pointer to the beginning of the data.
We first check whether there is any text edit scrap available at all, and if it is,
zero the desk scrap (=: clear the clipboard). Then the parameters for putscrap are set
up, converting absolute addresses to NEON-relative (-base) and dereferencing handles
where necessary. After exiting, the clipboard will contain a copy of the text edit scrap.
The resource type constant 'TEXT'is contained in txtype, which is defined in FrontEnd.
get.tescrap does the opposite: if desk scrap is available, it will transfer it into
the TE scrap by calling getscrap with the parameters:
- 0 to hold the return result (the total length in bytes)
- the handle to the TE scrap
- the resource type 'TEXT'
- the value variable theOffset, also defined in FrontEnd.
We assume that there is only TEXT type scrap in the clipboard, so theOffset
(which tells you where in the clipboard the desired type of resource starts) won't
interest us. After the call, the TE scrap will contain whatever TEXT scrap was in the
clipboard.
put.tescrap and get.tescrap will now have to be built into the code that defines
our editing window. put.tescrap is installed into the window's activate action vector. No
vector is provided in NEON to handle deactivate events, therefore we redefine the
disable: method for that window so that it includes get.tescrap.
You might now want to install just these changes into last month's example, then
run the program and open the note pad. Now cutting text out of the note pad (using
ctrl-X), clicking the edit window and pasting it there (using the TEmenu) should give
the correct results. The opposite transfer should work, too.
In order to install the serial I/O support and simplify the whole application
somewhat, we'll use the multitasking that is provided in the NEON source files. Let me
first say some words about how multitasking can be achieved under NEON.
Multi-tasking the NEON way
The NEON system supports a simple form of 'concurrency' (contained in the file
Tasks that is part of the NEON system). Tasks redefines the null event vector NULL-EVT
in such a way that it sequentially executes a set of words that are contained in the
Ordered-Col list tasklist (This feature is not described in the first release of the
manual). To use the multitasking support simply type // tasks .
A new task is added to the tasklist by putting its cfa on the stack and calling
addtask, it is removed again from the list by calling killtask with the task's cfa on the
stack. So instead of redefining the event handler to include a call to TEIdle and cursor
adjustment (as we did last time), we can simply define
: idle.text
idle: mytext adjust.cursor ;
and then put the cfa of this word into the task list by saying
'c idle.text addtask
This will make the caret blink and give the cursor the correct shape while the
main event loop can be kept in the simple form given in the NEON manual
begin key makeint key: mytext again
where the makeint comes in because TEkey expects a 16-bit integer on the stack.
What is done here is not true multi-tasking, of course. Each word that is put into
the task list is executed all the way to its end before the next 'task' becomes active.
True multi-tasking would include some means to leave a word before it is finished,
saving the task's parameters so that it may be resumed where it was left.
One way multi-tasking can be done, for example, is to issue interrupts at
regular intervals (by some real-time clock or, in the Mac, through the vertical
retrace), and on each interrupt switch tasks. At this point one would save a copy of the
task's register status in an area local to that task and proceed to the next one. Another
possibility is that the task itself calls the scheduler to switch to the next task at certain
strategic points - like when a character is being output to the screen.
The way VBL tasks are handled in the Mac is an example of the first method [see
also the article by Bob Denny in MacTutor V1#9]. The problem on the Mac is that the
time allocated for a VBL task cannot be too long. All tasks within the VBL task queue
must complete before the next vertical blanking interrupt comes in (which is 16.67
msec). Here again, there is no possibility to leave a task in the middle of execution and
jump to another one.
An example for the other method (the task itself calls the scheduler, which
activates the next task) is the way desk accessories are handled, because here you will
call SystemTask from within your application each time you think the desk accessories
should get something to eat. However, the DA's Control routine will have to execute all
the way until it is finished - no interruption possible -, so in this case we have a
master task (the application) which calls slave tasks (the desk accessories).
A full implementation of the second method of task scheduling has been achieved
in a new Forth system - Mach1 - which has been advertised in this journal and which I
am going to review in the next article. This system - under Forth - is almost as close
as you can get to true multitasking on the Macintosh; watch for some new exciting
information next month.
For the time being, we will have to stick to the constraints of the NEON
multitasker, which means that words installed into the tasklist have to be short in
execution because they will have to finish before the next task gets its turn. The idling
routine is fast enough so that you don't notice any annoying delay.
Serial port handling
Another task that we will put into tasklist is a routine that will watch for
characters to come in through the serial port and put them into the TE record wherever
the insertion point is. Then we may - optionally - send the keystrokes not directly into
the TE record, but to the serial output port and convert our editing example into a
terminal emulator.
The listing shows how to do it. Serial port handling is contained in the NEON
source file drvr and serial, so these will have to be loaded first. We define modin and
modout for the serial input and output ports and initialize them to 8 data bits, 2 stop
bits, no parity and 300 baud. (Yes, unfortunately this program will handle only 300
baud, because no handshake has been added yet. You may use it at any speed, but have to
watch for characters getting lost.)
The init: , config: and baud: methods will set the configuration word of the port
object, but not actually reconfigure the serial port itself. This is done by reset:, which
may be used only after sending an open: to the port (otherwise -> bomb).
We implement the input and output handlers to work asynchronously, using the
methods readNW: and writeNW: provided by the system. These routines need the pointer
to a completion routine as a parameter. The completion routines are defined using the
NEON word :proc (note that you may never execute a :proc directly, but may only pass it
as a parameter to a toolbox routine).