# I/O functions and commands

## Contents

- Overview

- Error handling
- Handles
- var variables

- Opening a file with IOOPEN

- Mode Category 1 - Open mode
- Mode Category 2 - File format
- Mode Category 3 - Access flags

- Closing a file with IOCLOSE
- Reading a file with IOREAD

- Text files
- Binary files

- Writing to a file
- Positioning within a file
- Example — displaying a plain text file
- Asynchronous requests and semaphores

- Asynchronous requests
- The I/O semaphore
- Status words
- Cancelling an asynchronous request
- Waiting for a particular completion
- A first example using asynchronous I/O
- Polling rather than waiting

- I/O device handling and the asynchronous request functions
- Some useful IOW functions
- Example of IOW screen functions

## Overview

Section Contents

- Error handling
- Handles
- var variables

The memory which an OPL program may use is unlimited up to the constraint placed by the machine itself. This means that addresses must be represented by 32-bit integers, the widest numeric type available in OPL.

OPL includes powerful facilities to handle input and output (‘I/O’). These functions and commands can be used to access all types of files in the filing system, as well as various other parts of the low-level software.

This section describes how to open, close, read and write to files, and how to set the position in a file. The data file handling commands and functions have been designed for use specifically with data files. The I/O functions and commands are designed for general file access. You don’t need to use them to handle data files.

These are powerful functions and commands and they must be used with care. Before using them you must read this section closely and have a good grounding in OPL in general.

### Error handling

You should have a good understanding of error handling (see Error Handling) before using the I/O functions.

The functions in this section never raise an OPL error message. Instead they return a value - if this is less than zero an error has occurred. It is the responsibility of the programmer to check all return values, and handle errors appropriately. Any error number returned will be one of those in the list given in OPL error values. You can use `ERR$` to display the error as usual.

### Handles

Many of these functions use a handle, which must be a long integer variable. `IOOPEN` assigns this handle variable a value, which subsequent I/O functions use to access that particular file. Each file you `IOOPEN` needs a different handle variable.

### var variables

In this section, var denotes an argument which should normally be a `LOCAL` or `GLOBAL` variable. (Single elements of arrays may also be used, but not field variables or procedure parameters.) Where you see var the address of the variable is passed, not the value in it. (This happens automatically; don’t use `ADDR` yourself.)

In many cases the function you are calling passes information back by setting these var variables.

var is just to show you where you must use a suitable variable; you don’t actually type `var`.

For example: `ret%=IOOPEN(`var `handle&,name$,mode%)` in the syntax description indicates that `IOOPEN(h&,"abc",0)` is correct (provided `h&` is a simple variable), but `IOOPEN(100,"abc",0)` is incorrect.

It is possible, though, that you already have the address of the variable to use. It might be that this address is held in a field variable, or is even a constant value, but the most common situation is when the address was passed as a parameter to the current procedure.

If you add a `#` prefix to a var argument, this tells OPL that the expression following is the address to be used, not a variable whose address is to be taken.

Here is an example program:

        PROC doopen:(phandle&, name$, mode%)
              LOCAL error%
              REM IOOPEN, handling errors
              error% = IOOPEN(#phandle&, name$, mode%)
              IF error% : RAISE error% : ENDIF
        ENDP

The current value held in `phandle&` is passed to `IOOPEN`. You might call `doopen:` like this:

        LOCAL filhand%,...
        ...
        doopen:(ADDR(filhand%),"log.txt",$23)
        ...

The `doopen:` procedure calls `IOOPEN` with the address of `filhand%`, and `IOOPEN` will write the handle into `filhand%`.

## Opening a file with IOOPEN

Section Contents

- Mode Category 1 - Open mode
- Mode Category 2 - File format
- Mode Category 3 - Access flags

        ret%=IOOPEN(var handle%,name$,mode%

or

        ret%=IOOPEN(var handle%,address&,mode%)

for unique file creation.

Creates or opens a file (or device) called `name$` and sets `handle%` to the handle to be used by the other I/O functions.

`mode%` specifies how the file is to be opened. It is formed by ORing together values which fall into the three following categories:

### Mode Category 1 - Open mode

One and only one of the following values must be chosen from this category.

$0000 | Open an existing file (or device). The initial current position is set to the start of the file.
$0001 | Create a file which must not already exist.
$0002 | Replace a file (truncate it to zero length) or create it if it does not exist.
$0003 | Open an existing file for appending. The initial current position is set to the end of the file. For text format files (see $0020 below) this is the only way to position to end of file.
$0004 | Creates a file with a unique name. For this case, you must use the address of a string instead of `name$`. This string specifies only the path of the file to be created (any file name in the string is ignored). The string at `address%` is then set by IOOPEN to the unique file name generated (this will include the full path). The string must be large enough to take 130 characters (the maximum length file specification). For example:
        s$="C:\home\"
        IOOPEN(handle%,ADDR(s$),mode%)
This mode is typically used for temporary files which will later be deleted or renamed.

### Mode Category 2 - File format

One and only one of the following values must be chosen from this category. When creating a file, this value specifies the format of the new file. When opening an existing file, make sure you use the format with which it was created.

$0000 | The file is treated as a byte stream of binary data with no restriction on the value of any byte and no structure imposed upon the data. Up to 16K can be read from or written to the file in a single operation.
$0020 | The file is treated as a sequence of variable length records. The records are assumed to contain text terminated by any combination of the CR and LF (`$0D`, `$0A`) characters. The maximum record length is 256 bytes and Control-Z (`$1A`) marks the end of the file.

These values are declared as constants in `Const.oph`. See Including header files.

### Mode Category 3 - Access flags

Any combination of the following values may be chosen from this category.

$0100 | Update flag. Allows the file to be written to as well as read. If not set, the file is opened for reading only. You must use this flag when creating or replacing a file.
$0200 | Choose this value if you want the file to be open for random access (not sequential access), using the `IOSEEK` function.
$0400 | Specifies that the file is being opened for sharing for example, with other running programs. Use if you want to read, not write to the file. If the file is opened for writing (`$0100` above), this flag is ignored, since sharing is then not feasible. If not specified, the file is locked and may only be used by this running program.

## Closing a file with IOCLOSE

Files should be closed when no longer being accessed. This releases memory and other resources back to the system.

        ret%=IOCLOSE(handle%)

Closes a file (or device) with the handle `handle%` as set by `IOOPEN`.

## Reading a file with IOREAD

Section Contents

- Text files
- Binary files

        ret%=IOREAD(handle%,address&,maxLen%)

Reads up to `maxLen%` bytes from a file with the handle `handle%` as set by `IOOPEN`. `address&` is the address of a buffer into which the data is read. This buffer must be large enough to hold a maximum of `maxLen%` bytes. The buffer could be an array or even a single integer as required. No more than 16K bytes can be read at a time.

The value returned to `ret%` is the actual number of bytes read or, if negative, is an error value.

### Text files

If `maxLen%` exceeds the current record length, data only up to the end of the record is read into the buffer. No error is returned and the file position is set to the next record.

If a record is longer than `maxLen%`, the error value ‘Record too large’ (-43) is returned. In this case the data read is valid but is truncated to length `maxLen%`, and the file position is set to the next record.

A string array `buffer$(255)` could be used, but make sure that you pass the `address ADDR(buffer$)+1` to `IOREAD`. This leaves the leading byte free. You can then `POKEB` the leading byte with the count (returned to `ret%`) so that the string conforms to normal string format. See the example program.

### Binary files

If you request more bytes than are left in the file, the number of bytes actually read (even zero) will be less than the number requested. So if `ret%<maxLen%`, end of file has been reached. No error is returned by `IOREAD` in this case, but the next `IOREAD` would return the error value ‘End of file’ (-36).

To read up to 16K bytes (8192 integers), you could declare an integer array `buffer%(8192)`.

## Writing to a file

        ret%=IOWRITE(handle%,address&,length%)

Writes `length%` bytes stored in a buffer at `address&` to a file with the handle `handle%`.

When a file is opened as a binary file, the data written by `IOWRITE` overwrites data at the current position.

When a file is opened as a text file, `IOWRITE` writes a single record; the closing CR/LF is automatically added.

## Positioning within a file

`ret%=IOSEEK(handle%,mode%,var offset&)`

Seeks to a position in a file that has been opened for random access (see `IOOPEN` above).

`mode%` specifies how the argument `offset&` is to be used. `offset&` may be positive to move forwards or negative to move backwards. The values you can use for `mode%` are:

1 | Set position in a binary file to the absolute value specified in `offset&`, with 0 for the first byte in the file.
2 | Set position in a binary file to `offset&` bytes from the end of the file
3 | Set position in a binary file to `offset&` bytes relative to the current position.
6 | Rewind a text file to the first record. `offset&` is not used, but you must still pass it as a argument, for compatibility with the other cases.

`IOSEEK` sets the variable `offset&` to the absolute position set.

## Example — displaying a plain text file

This program opens a plain text file — such as one created with the `Export as text` option in the `File` menu of the Program editor — and types it to the screen. Press `Esc` to quit and any other key to pause the typing to the screen.

        PROC ioType:
              LOCAL ret%,fName$(128),txt$(255),address&
              LOCAL handle%,mode%,k%
              PRINT "Filename?", :INPUT fName$ : CLS
              mode%=$0400 OR $0020  REM open=$0000,text=$0020,share=$0400
              ret%=IOOPEN(handle%,fName$,mode%)
              IF ret%<0
                    showErr:(ret%)
                    RETURN
              ENDIF
              address&=ADDR(txt$)
              WHILE 1
                    k%=KEY
                    IF k%                        REM if keypress
                          IF k%=27                   REM Esc pressed
                                RETURN            REM otherwise wait for a key
                          ELSEIF GET=27
                                RETURN            REM Esc pressed
                          ENDIF
                    ENDIF
                    ret%=IOREAD(handle%,address&+1,255)
                    IF ret%<0
                          IF ret%<>-36            REM NOT EOF
                                showErr:(ret%)
                          ENDIF
                          BREAK
                    ELSE
                          POKEB address&,ret%      REM leading byte count
                          PRINT txt$
                    ENDIF
              ENDWH
              ret%=IOCLOSE(handle%)
              IF ret%
                    showErr:(ret%)
              ENDIF
              PAUSE -100 :KEY
        ENDP

        PROC showErr:(val%)
              PRINT "Error",val%,err$(val%)
              GET
        ENDP

## Asynchronous requests and semaphores

Section Contents

- Asynchronous requests
- The I/O semaphore
- Status words
- Cancelling an asynchronous request
- Waiting for a particular completion
- A first example using asynchronous I/O
- Polling rather than waiting

- Polling example

This section provides the general background necessary for understanding how EPOC I/O devices can be accessed by an OPL application program.

### Asynchronous requests

Many operating system services are implemented in two steps:

1. make the service request (sometimes referred to as the queuing of a request)
2. wait for the requested operation to complete

In most cases, as well as providing functions for each step, the system provides a function containing both the above steps. Such functions are called synchronous because they automatically synchronise the requesting process by waiting until the operation has completed. The internal function that makes the request without waiting for completion is called an asynchronous function.

Examples of asynchronous request functions are:

IOC (or IOA) | for requests on an open I/O channel
KEYA | for requests on the keyboard channel
GETEVENTA32 | for requests of events from the window server
PLAYSOUNDA: | (in `System.opx`) for requests to play sound files

The synchronous versions of the above functions are IOW , GET, GETEVENT32 and PLAYSOUND: respectively.

Applications use asynchronous requests in situations like the following:

1. make request A
2. make request B
3. wait for either of the requested operations to complete

Processes wait for the completion of asynchronous requests by waiting on their I/O semaphore where each request is associated with a status word.

### The I/O semaphore

When a process is created, the system automatically creates an I/O semaphore on its behalf (a more accurately descriptive name would have been the asynchronous request semaphore). After making one or more asynchronous requests using IOC or IOA, a process calls IOWAIT to wait on the I/O semaphore for one of the requests to complete. A typical application spends most of its time waiting on its I/O semaphore. For example, an interactive application process that is waiting for user input is waiting on the I/O semaphore.

Semaphores are provided to synchronise cooperating processes (where, in this context, a process includes a hardware interrupt). In OPL semaphores are used for synchronising the completion of asynchronous requests.

The semaphores in EPOC are counting semaphores, having a signed value that is incremented by calling `IOSIGNAL` and decremented by calling `IOWAIT`. A semaphore with a negative value implies that a process must wait for the completion of an event.

The process or the hardware interrupt handler that implements the requested operation sends a signal to the process (using a generalised version of `IOSIGNAL` directed to the required process) to indicate that the operation has completed. If one or more wait handlers have been installed (wait handlers are described below), they may process the signal and re-signal using `IOSIGNAL`. In some cases, it is convenient for the requester to use `IOSIGNAL` to signal itself and subsequently to process that signal in a central call to `IOWAIT`.

### Status words

Although the arguments to asynchronous request functions vary, they all take a so-called status word argument, which subsequently contains the status of the requested operation. The status word is a 16-bit integer except when calling an asynchronous OPX procedure that specifically requires a 32-bit integer status word, such as `PLAYSOUNDA:` in `System.opx`.

All asynchronous requests exhibit the following behaviour:

- While the request is pending, a 16-bit status word contains the negative -46.

A 32-bit status word however contains &80000001. Const.oph provides constant definitions for these values.

- When the operation has completed, a value other than -46 is written to the status word. This value is zero or positive to indicate success, or a negative error number to indicate failure. For 16-bit status words, an OPL error value is used. See Error Handling for a list of these.

For 32-bit status words an EPOC error value is used. The EPOC error values are listed in EPOC Error Values.

- The requesting process’s I/O semaphore is signalled (after the status word has been written).

Making a request while a previous request on the same status word is still pending will normally result in a system panic where the program is killed immediately, without being able to trap the error.

When there are multiple requests, each request is associated with a different status word. After returning from `IOWAIT`, the caller typically polls each status word until one is found that contains other than -46. That completion is then processed (which might include renewing the asynchronous request) and `IOWAIT` is called again to process the next completion.

### Cancelling an asynchronous request

Most asynchronous request functions made using `IOC` (or `IOA`) can be cancelled. To cancel such requests use the `IOCANCEL` function.

For asynchronous requests made using a mechanism other than `IOC` (or `IOA`), a specific cancelling function must be used instead:

- Use `KEYC` to cancel a `KEYA` request.
- Use `GETEVENTC` to cancel a `GETEVENTA32` request.
- Use `STOPSOUND&:`to cancel a `PLAYSOUNDA:` request.

The following general principles apply to all functions that cancel an asynchronous request:

- the cancel precipitates the completion of the operation (it does not stop the operation from completing).
- the cancel may or not be effective (that is, the operation may complete naturally before the cancel is processed).
- after a cancel, you must still process the completion of the asynchronous request (typically by immediately calling `IOWAITSTAT` or `IOWAITSTAT32`, described below, to "use up" the signal). An exception to this rule is that `GETEVENTC` and KEYC themselves wait for the cancellation signal, so `IOWAITSTAT` must not be used.

A 16-bit status word is set to -48 when a request has been effective.

A 32-bit status word is set to -3 (EPOC’s error code for ‘Cancel error’) when a request using a 32-bit status word is cancelled.

### Waiting for a particular completion

When waiting for the completion of a particular asynchronous request, the wait on the I/O semaphore must be sure that it is not fooled into a premature return by the completion of any other pending asynchronous request. This is done by calling `IOWAITSTAT` which behaves in a similar way to IOWAIT except that it only returns when the associated status word is other than -46.

For a 32-bit status word request, `IOWAITSTAT32` is used instead, again behaving in a similar way to `IOWAIT` except that it only returns when the associated 32-bit status word is other than &80000001.

In general, `IOWAITSTAT` (`IOWAITSTAT32`) is a safer option than `IOWAIT` to "use up" the signal resulting from the cancelled operation. If the cancel is not immediately effective and another completion causes `IOWAIT` to return, the program could continue and make another request before the cancelled operation completes (which would result in a process panic).

### A first example using asynchronous I/O

In the following example, the opened asynchronous timer `timChan%` is used to construct a synchronous function which attempts to write the passed string to the opened serial channel `serChan%`. If it takes more that 5 seconds to complete the write, the procedure raises the ‘inactivity’ error -54. For simplicity it is assumed that there are no other outstanding events which could complete and that both requests started successfully. Thus it is certain that on return from the call to IOWAIT, one of the two asynchronous requests has completed.

        PROC strToSer:(inStr$)
              LOCAL str$(255)      REM local copy of inStr$ needed for ADDR
              LOCAL len%,timStat%,serStat%,timeout%,ret%,pStr&

              str$=inStr$
              pStr&=ADDR(str$)+1
                          REM pointer to string skipping leading count byte
              len%=LEN(str$)
              IOC(serChan%,2,serStat%,#pStr&,len%)
              REM request asynchronous serial write
              timeout%=50                        REM 5 second timeout
              IOC(timChan%,1,timStat%,timeout%)
                                                  REM Relative timer - function 1
              IOWAIT
              IF serStat%=-46                  REM must have timed out
              IOCANCEL(serChan%)                  REM cancel serial request
              IOWAITSTAT serStat%            REM use up the signal
              RAISE -54                              REM inactivity timeout
              ENDIF
              IOCANCEL(timChan%)                  REM cancel timer request
              IOWAITSTAT timStat%            REM use up the signal
        ENDP

### Polling rather than waiting

In a multi-tasking operating system it is extremely anti-social to wait for an operation to complete by polling the status word in a tight loop rather than call `IOWAIT` (because the polling will "hog" the processor to no benefit). However, when there is useful work to be done between each poll, it can be appropriate to poll - for example, to check periodically for user input while performing an extended calculation.

When a poll detects a completed status word, it is still obligatory to "use up" the signal by calling IOWAIT (otherwise you will get a "stray signal" later).

#### Polling example

The status word will be set only when either `IOWAIT` or `IOYIELD` is called. For a 32-bit status word, call `IOWAITSTAT32` instead.

For example:

        ...
        IOC(handle%,func%,stat%,#pBuf&,len%)
        DO
              calc:
              REM perform part of some calculation until request complete
              IOYIELD                                REM allow status word to be set
        UNTIL stat%<>-46
        ...

## I/O device handling and the asynchronous request functions

The following I/O functions provide access to devices. A full description is not within the scope of this manual, since these functions require extensive knowledge of the EPOC operating system and related programming techniques. The syntax and argument descriptions are provided here for completeness.

The previous section explains in detail the semantics of asynchronous I/O. In the descriptions of the asynchronous I/O functions below, a careful reading of that section is assumed.

`ret%=IOW(handle%,func%,var arg1,var arg2)
`The device driver opened with `handle%` (as returned by `IOOPEN`) performs the synchronous I/O function `func%` with the two further arguments. The size and structure of these two arguments is specified by the particular device driver’s documentation.

`IOC(handle%,func%,var stat%,var a1,var a2)
IOC(handle%,func%,var stat%,var a1)
IOC(handle%,func%,var stat%)
`Make an I/O request with guaranteed completion. The device driver opened with `handle%` (as returned by `IOOPEN`) performs the asynchronous I/O function `func%` with up to two further arguments. The size and structure of these arguments is specified by the particular device driver’s documentation.

As explained in detail in the previous section, asynchronous means that the `IOC` returns immediately, and the OPL program can carry on with other statements. `status%` will always be set to -46, which means that the function is still pending.

When, at some later time, the function completes, `status%` is automatically changed. (For this reason, `status%` should usually be global since if the program is still running, `status%` must be available when the request completes, or the program will probably crash). If `status%`>=0, the function completed without error. If `status%<0`, the function completed with error. The return values and error codes are specific to the device driver.

If an OPL program is ready to exit, it does not have to wait for any signals from pending `IOC` calls.

`ret%=IOA(handle%,func%,var status%,var arg1,var arg2)
`The device driver opened with `handle%` (as returned by `IOOPEN`) performs the asynchronous I/O function `func%` with two further arguments. The size and structure of these two arguments is specified by the particular device driver’s documentation.

This has the same form as IOC, but the return value cannot be ignored. IOC is effectively the same as:

        ret%=IOA(h%,f%,stat%,...)
        IF ret%<0
              stat%=ret%
              IOSIGNAL
        ENDIF

`IOC` allows you to assume that the request started successfully - any error is always given in the status word `stat%`. If there was an error, `stat%` contains the error code and the `IOSIGNAL` causes the next `IOWAIT` to return immediately as if the error occurred after completion. There is seldom a requirement to know whether an error occurred on starting a function, and `IOC` should therefore nearly always be used in preference to IOA.

`IOWAIT
`Wait for an asynchronous request (such as one requested by `IOC`, `KEYA` or `GETEVENTA32`) to complete. `IOWAIT` returns when any asynchronous I/O function completes. Check `status%` to find out which request has completed. Use `IOWAITSTAT` with the relevant status word to wait for a particular request to complete.

`IOSIGNAL
`Replace a signal of an I/O function’s completion.

As shown in ‘A simple example using asynchronous I/O’ in the previous section, it is sometimes useful to construct a synchronous operation from two asynchronous operations by waiting for either to complete before returning. In that case it waited for either the serial write request or a timeout. As noted there, the example assumed that there were no other outstanding asynchronous requests. If there had been one or more asynchronous requests made before calling that procedure, such as a request to play a sound file, and if that request had completed before both the serial write and the timer request, the procedure would incorrectly assume that the timer had completed:

        IOWAIT
        IF serStat%=-46                  REM must have timed out
              IOCANCEL(serChan%)            REM cancel serial request
              IOWAITSTAT serStat%      REM use up the signal
              RAISE -54                        REM inactivity timeout
        ENDIF
        IOCANCEL(timChan%)                  REM cancel timer request

        IOWAITSTAT timStat%            REM use up the signal

To deal with this situation correctly, the procedure should instead check that one of the particular two requests it knows about has completed and re-signal to ‘put back’ the signal consumed for the sound file completion:

        PROC strToSer:(inStr$)
              LOCAL str$(255)      REM local copy of inStr$ needed for ADDR
              LOCAL len%,timStat%,serStat%,timeout%,ret%,pStr&
              LOCAL signals%            REM count of external signals
              LOCAL err%

              str$=inStr$
              pStr&=ADDR(str$)+1
                                REM ptr to string skipping leading count byte
              len%=LEN(str$)
              IOC(serChan%,2,serStat%,#pStr&,len%)
              REM request asynchronous serial write
              timeout%=50                  REM 5 second timeout
              IOC(timChan%,1,timStat%,timeout%)
                                            REM relative timer - function 1
              WHILE 1                        REM forever loop
                    IOWAIT                        REM wait for any completion
                    IF timStat%<>-46            REM timed out
                          IOCANCEL(serChan%)            REM cancel serial request
                          IOWAITSTAT serStat%      REM use up the signal
                          err%=-54            REM inactivity timeout (raised
                                            REM below after re-signalling)
                          BREAK                  REM stop waiting and re-signal
                    ELSEIF serStat%<>-46            REM serial write complete
                          IOCANCEL(timChan%)            REM cancel timer request
                          IOWAITSTAT timStat%      REM use up the signal
                          BREAK                  REM stop waiting and re-signal
                    ELSE                                    REM other unknown request
                          signals%=signal%+1            REM count other signals
                                                        REM loop again for next
                    ENDIF
              ENDWH
              WHILE signals%>0
                    REM now re-signal any consumed external signals
                    IOSIGNAL
                    signals%=signals%-1
              ENDWH
              IF err%
                    RAISE err%
              ENDIF
        ENDP

IOSIGNAL is called only after exiting the IOWAIT loop, otherwise the signal would cause the IOWAIT to return immediately.

`IOWAITSTAT var status%
`Wait for a particular asynchronous function, called with IOC, to complete.

`IOWAITSTAT32 var stat&
`Similar to `IOWAITSTAT` but takes a 32-bit status word. `IOWAITSTAT32` should be called only when you need to wait for completion of a request made using a 32-bit status word when calling an asynchronous OPX procedure. `status&` will be &80000001 while the function is still pending, and on completion will be set to the appropriate EPOC error code, listed in Error Handling. See also About OPXs and Keyword Reference.

`IOYIELD
`Ensures that any asynchronous function is given a chance to run. Some devices are unable to perform an asynchronous request if an OPL program becomes computationally intensive, using no I/O (screen, keyboard etc.) at all. In such cases, the OPL program should use `IOYIELD` before checking its `status%` variable. This is always the case under EPOC. `IOYIELD` is the equivalent of `IOSIGNAL` followed by `IOWAIT` - the `IOWAIT` returns immediately with the signal from `IOSIGNAL`, but the `IOWAIT` causes any asynchronous handlers to run.

`IOCANCEL(handle%)
`Cancels any outstanding asynchronous I/O request made using `IOC` (or `IOA`) on the specified channel, causing them to complete with the completion status word containing -48 (‘I/O cancelled’). The return value is always zero and may be ignored.

The `IOCANCEL` function is harmless if no request is outstanding (e.g. if the function completed just before cancellation requested).

`GETEVENTA32(var status%,var event&())
`This is an asynchronous window server read function. You must declare a long integer array with at least 16 elements. If a window server event occurs, the information is returned in `event&()` as described under `GETEVENT32` in the alphabetic listing.

`GETEVENTC(var status%)
`Cancels a `GETEVENTA32`. Note that `IOWAITSTAT` should not be called after `GETEVENTC` - OPL consumes the `GETEVENTC` signal.

`err%=KEYA(var status%,key%())
`This is an asynchronous keyboard read function. You must declare an integer array with two elements here, `key%(1)` and `key%(2)` to receive the keypress information. If a key is pressed, the information is returned in this way:

`key%(1)` is assigned the character code of the key.

The least significant byte of `key%(2)` takes the key modifier, in the same way as `KMOD 2` for Shift down, `4` for Control down and so on. `KMOD` cannot be used with `KEYA`.

The most significant byte of `key%(2)` takes the count of keys pressed (0 or 1).

`KEYA` needs an `IOWAIT` in the same way as `IOC`.

## Some useful IOW functions

`IOW` has this specification:

        ret%=IOW(handle%,func%,var arg1,var arg2)

Here are some uses:

        LOCAL a%(6)
        IOW(-2,8,a%(),a%())             REM 2nd a% is ignored

senses the current text window (as set by the most recent `SCREEN` command) and the text cursor position ignoring any values already in the array `a%()`. This gives the same information as `SCREENINFO`, which should be used in preference (see Keyword Reference).

The first four elements are set to represent the offset of the current text window from the default text window. `a%(1)` is set to the x-offset of the top left corner of the current text window from the default text window’s top left corner and `a%(2)` to the y-offset of the top left corner of current text window. Similarly `a%(3)` and `a%(4)` give the offset of bottom right corner of text window from the bottom right corner of the default text window. For example, if the most recent `SCREEN` command was `SCREEN` 10,11,4,5 the first four elements of the array a%() would be set to (3,4,13,15). The x and y positions of the cursor relative to the current text window are written to `a%(5)` and `a%(6)` respectively. All positions and offsets take 0,0, not 1,1, as the point at the top left.

        LOCAL i%,a%(6)
        i%=2
        a%(1)=x1% :a%(2)=y1%
        a%(3)=x2% :a%(4)=y2%
        IOW(-2,7,i%,a%())

clears a rectangle at `x1%,y1%` (top left), `x2%,y2%` (bottom right). If `y2%` is one greater than `y1%`, this will clear part or all of a line.

## Example of IOW screen functions

The final two procedures in this module call the two `IOW` screen functions described beforehand. The rest of the module lets you select the function and values to use. It uses the technique used in Menus of handling menus and short-cut keys by calling procedures with string expressions.

        PROC iotest:
              GLOBAL x1%,x2%,y1%,y2%
              LOCAL i%,h$(2),a$(5)
              x1%=2 :y1%=2
              x2%=25 :y2%=5                         REM our test screensize
              SCREEN x2%-x1%,y2%-y1%,x1%,y1%
              AT 1,1
              PRINT "Text window IO test"
              PRINT "Control-Q quits"
              h$="cr"                               REM our shortcut keys
              DO
                    i%=GET
                    IF i%=$122                  REM MENU key
                          mINIT
                          mCARD "Set","Rect",%r
                          mCARD "Sense","Cursor",%c
                          i%=MENU
                          IF i% AND INTF(LOC(h$,CHR$(i%)))
                                a$="proc"+chr$(i%)
                                @(a$):
                          ENDIF
                    ELSEIF i% AND $200                  REM shortcut key
                          i%=(i%-$200)
                          i%=LOC(h$,CHR$(i%))      REM One of ours?
                          IF i%
                                a$="proc"+MID$(h$,i%,1)
                                @(a$):
                          ENDIF            REM ignore other weird keypresses
                    ELSE                        REM some other key, so return it
                          PRINT CHR$(i%);
                    ENDIF
              UNTIL 0
        ENDP

        PROC procc:
              LOCAL a&
              a&=iocurs&:
              PRINT "x";1+(a& AND &ffff);
              PRINT "y";1+(a&/&10000)
        ENDP

        PROC procr:
              LOCAL xx1%,yy1%,xx2%,yy2%
              LOCAL xx1&,yy1&,xx2&,yy2&
              dINIT "Clear rectangle"
              dLONG xx1&,"Top left x",1,x2%-x1%
              dLONG yy1&,"Top left y",1,y2%-y1%
              dLONG xx2&,"Bottom left x",2,x2%-x1%
              dLONG yy2&,"Bottom left y",2,y2%-y1%
              IF DIALOG
                    xx1%=xx1&-1 :xx2%=xx2&-1
                    yy1%=yy1&-1 :yy2%=yy2&-1
                    iorect:(xx1%,yy1%,xx2%,yy2%)
              ENDIF
        ENDP

        PROC iocurs&:
              LOCAL a%(4),a&
              REM don’t change the order of these!
              a%(1)=x1% :a%(2)=y1%
              a%(3)=x2% :a%(4)=y2%
              IOW(-2,8,a%(),a%())       REM 2nd a% is ignored
              RETURN a&
        ENDP

        PROC iorect:(xx1%,yy1%,xx2%,yy2%)
              LOCAL i%,a%(6)
              i%=2 :REM "clear rect" option
              a%(1)=xx1% :a%(2)=yy1%
              a%(3)=xx2% :a%(4)=yy2%
              IOW(-2,7,i%,a%())
        ENDP