Go to the previous, next section.
As we have discussed extensively (see section The Stdgraph Graphics Kernel), most plotting devices can be accommodated within the framework of stdgraph/graphcap without writing a line of C; raster devices can use the raster device driver. Sometimes, however, this is not possible and you must write a real driver. An example would be making SM run native under X11; running under a terminal emulator can be done with graphcap.
If you do make any changes, write any new drivers, or modify graphcap (except trivially) please send us copies of your modifications.
Let us assume that you really do need a new driver, how do you go
about it?
SM communicates with devices solely through a graphcap entry,
some external variables
(defined in sm.h) and a structure called
DEVICES
which is defined in devices.h
and declared in
plotsub/devices.c
.
When you issue a DEVICE devname args
command, the stdgraph driver is called with the string devname args
,
and it opens the graphcap entry for devname
. If it finds an entry
of the form :DV=driver:
it tries to find a hardcoded device driver
called "driver"
(you'll see in a moment where the names come
from). If it can find such a driver it calls its setup function (to
be defined in a moment) with args
as an argument. Your device
driver is now in charge, and no more stdgraph routines are called
until your device is closed; after you return from your close function
stdgraph's close function is called to close the graphcap descriptor.
What do you have to remember from this paragraph? That you need a
graphcap entry, if only a trivial one along the lines of
my_device|all_mine:DV=my_driver:which associates the driver
my_driver
with the device referred
to as either my_device
or all_mine
. Because stdgraph's
stg_open
and stg_close
functions are called around your
device code
the standard graphcap support for OW
, IF
, DC
, OF
,
SY
, and CW
is automatically provided if you want it; see
the imagen
driver and graphcap entries for an example. You might also want to
remember that you can directly access any graphcap entry for your
device from within your driver, using the functions ttygets
,
ttygetr
, ttygeti
, and ttygetb
.
The current definition of DEVICES
looks like this:
typedef struct { /* output device dependent functions */ char d_name[40]; /* name of device */ int (*dev_setup)(); /* initialisation */ void (*dev_enable)(), /* enable graphics */ void (*dev_line)(), /* draw a line from x1, y1 to x2, y2 */ void (*dev_reloc)(), /* relocate current position */ void (*dev_draw)(), /* draw a line from current pos to x,y */ int (*dev_char)(), /* hardware characters */ int (*dev_ltype)(), /* hardware line type */ int (*dev_lweight)(), /* hardware line weight */ void (*dev_erase)(), /* erase graphics screen */ void (*dev_idle)(), /* switch from graph to alpha mode */ int (*dev_cursor)(), /* cursor display */ void (*dev_close)(), /* close device */ int (*dev_dot)(), /* draw a single pixel dot */ int (*dev_fill_pt)(), /* draw a filled point */ void (*dev_ctype)(), /* set colour of line */ int (*dev_set_ctype)(), /* set possible colours of lines */ void (*dev_gflush)(); /* flush graphics */ void (*dev_page)(); /* start a new page */ void (*dev_redraw)(); /* redraw the screen, if need be */ } DEVICES;
Note that this is the DEVICES structure at the time of writing;
be sure to see that it is still correct when you write your driver!
To add a new device to SM, all you have to do is to write functions
to fill as many of the slots in DEVICES
as possible, add their
definitions to `sm_declare.h', then add an
element to the array devices[] in devices.c. After recompiling, SM will
recognise your device by the name d_name
that you gave it in devices[].
Traditionally the functions have the
same name as those given above with the dev
replaced by some
mnemonic for your new device. You should surround both your driver and
the entry in devices[] with #ifdef's so that all I have to do to
totally lose your device is to not #define it to the compiler. In writing
your device you may want to use graphcap to give it various snippets
of information. This can be done; see the imagen driver for an example.
You should include the file `sm_declare.h' (which automatically includes `options.h') which provides declarations for all SM functions (including prototypes if the compiler supports them), and you should add your own declarations to this file.
If you don't provide some of the functions, SM will try to
emulate them in its software. For example, of your ltype function returns
-1, then we will chop your lines for you. There are a set of functions
for nodevice
that have the correct types, you can use them for
functions that you don't want to provide (e.g. if you don't support
dev_ltype
you can use no_ltype
).
Some of the routines, e.g. dev_char
are sometimes passed NULL arguments to inquire if they can provide
particular capabilities. In the following paragraphs we discuss all
the functions, their arguments, what they do, and what they return.
All coordinates are given in screen units, where the device is 32768 pixels along a side.
dev_setup(str) (char *str;)
DEVICE
command was DEVICE newdev abcde zyxwvut
, setup will be passed
abcde zyxwvut
. If the device can't be opened for some reason
setup should return -1, otherwise 0. Setup has a number of
book-keeping responsibilities: setting the value of termout to 1 if
the device is a terminal, otherwise 0; setting the value of ldef to
the maximum spacing between lines if they are to appear as one
thick line rather than a set of parallel ones, (used if we have to
emulate LWEIGHT
); setting the variables xscreen_to_pix and
yscreen_to_pix to give the conversion from SCREEN to device coordinates;
calling default_ctype with the name of the default
colour for lines (e.g. default_ctype("white")
). There is no need
to look in the `.sm' file for a foreground
entry, this is
done for you (by set_dev()) after dev_setup returns, but you should
deal with any background
entry if you feel so inclined.
There is a function parse_color
that converts a colour name into r, g, and b values (see the sunwindows
setup function for an example of its use) that may help.
Note that you should not call stg_setup
-- this was done for you
automatically before your setup function was called. No return value.
dev_enable()
dev_line(x1,y1,x2,y2) (int x1, y1, x2, y2;)
dev_reloc(x,y) (int x,y;)
dev_draw(x,y) (int x,y;)
dev_char(str,x,y) (char *str; int x,y;)
dev_ltype(i) (int i;)
LTYPE
to be i, as in the
interactive command.
Return 0 if you support the ltype asked for, otherwise -1. If you can't support
a linetype, make sure that you are drawing solid lines, so that
SM can emulate it for you. LTYPE
10 is special, it is
generated by the LTYPE ERASE
command, and asks you to delete lines
as they are drawn, LTYPE 11
is used to indicate the end of LTYPE ERASE
mode.
dev_lweight(i) (int i;)
LWEIGHT
to be i, as in the interactive command. Lines
with lweight
0 should be the natural thickness of a line on your
device, higher lweight
lines are usually drawn with a thickness
of LDEF*lweight
in SCREEN coordinates. Return 0 if you support
the lweight asked for, otherwise -1.
dev_erase()
dev_idle()
dev_cursor(x,y) (int *x,*y)
dev_close()
stg_close
-- this is done for you
automatically. No return value.
dev_dot(x,y) (int x,y)
DOT
command).
You'll have to do the move to (x,y)
yourself. Return 0 if you can draw dots, otherwise -1.
dev_fill_pt(n) (int n;)
PTYPE n 3
points.
Remember to allow for EXPAND
and ANGLE
.
Return 0 if you can draw the point, otherwise -1;
dev_ctype(r,g,b) (int r,g,b;)
dev_set_ctype(col,n) (COLOR *col; int n;)
dev_gflush()
dev_page()
dev_redraw(fildes) (int fd)
get1char()
before it tries to read a
character from SM's standard input (file descriptor fd
).
It is intended for the use of drivers that need to keep an
eye on `their' window. For example, the Silicon Graphics driver needs
to redraw a window every time that it is moved. Such device drivers
will probably want to poll fildes
(e.g. using select()
)
to see that there really is input before returning; look at
the sgi
or x11
driver if you want an example.
No return value.
If you are still confused, look at some of the hardware drivers that are already available.
Porting to new Unix machines should be relatively simple. If the
machine runs BSD Unix the only changes that should be necessary are
to the exception handler routines in main.c, to reflect the error
conditions signalled by your new machine. For a Sys V Unix, you'll have
to edit `options.h' to define SYS_V
.
Otherwise, your only
problems should be hidden bugs which flourish in new architectures.
Otherwise there isn't all that much that I can tell you. SM runs on Unix (BSD and SysV) and VMS machines, and the places where the code is #ifdef'd for those operating systems is your best bet for places where there are machine dependencies. That said, there are a few obvious problem areas.
First consider get1char, which is the routine that reads terminal input. It has to be able to get one character a time from the terminal, and not interpret control characters. This is obviously operating system dependent (CBREAK under 4.3BSD, PASSALL under VMS, ...). Get1char expects to be passed ^A to start operations, and EOF to end them. Anything else means `return a character please'. The terminal output functions used by stdgraph are also machine dependent, for example they use QIO calls under VMS.
As mentioned above, the exception handlers try to interpret the exceptions that they receive and this may require a little modification in main.c.
Hardcopy devices need the system() system call to send commands to the operating system, and if you don't have one you are in trouble.
In a few places SM needs to make assumptions about how file
names are put together, and these will need changing. On a similar
note, some operating systems (e.g. VMS) are very picky about opening
files and you may have to be careful. Note the use of the DT
graphcap capability to signal particular requirements to the programme.
A machine that didn't use ascii would be rather a nuisance as makeyyl assumes that the characters for A-Z are contiguous, and although this could be easily fixed I am not sure that other problems wouldn't arise.
A final rather horrifying thought is that Bison might not compile on a machine that doesn't have YACC as an alternative. I don't know how to fix that, you'd just have to hope for the best, or else run Bison/YACC on a different machine and copy over control.c.
Go to the previous, next section.