Find out which folder on the file server the LogicWorks program is located on from your lab instructor. Logiworks intruction manuals are available, on overnight reserve, in the library. To start LogicWorks double click on the icon. After a short time two windows will appear on your screen, a circuit window and a timing window. The circuit window is the window that is used most frequently so resize it to fill the screen by clicking on the zoom button in the top right corner. The following circuit window illustrates the short cut icons that are located in the bottom left hand corner of the window. All these functions can be accessed from the menus as well.


From the device menu select the device you would like to place in the circuit area. The pointer will be replaced by the flickering circuit symbol of the device you selected. The orientation can be changed by clicking on the orientation icon in the lower left corner of the circuit window. An alternate way of specifying the orientation is by pressing the appropriate arrow key on the keyboard. Move the symbol to the desired location and click the mouse to place the device there. This may be repeated as desired. To return to the pointer click anywhere on the menu bar or click on the pointer icon in the lower corner in the window.


To draw a wire press and hold the mouse button with the pointer near another wire or chip input or output. As long as the button is held down a wire will follow the pointer. Note that only horizontal or vertical wires can be drawn. If your wire crosses another wire there will be no connection made unless you stop at the point where you would like a connection made. Intersections will be marked with a small square simulating a solder connection. The cross icon in the lower corner of the circuit window provides a different way of placing wires. Click on the starting point and ending point of a desired wire and the wire is automatically drawn.


To delete something select Zap from the Edit menu. The pointer will change to a small lightning bolt. Move the tip of the bolt to whatever device or wire is to be deleted and click the mouse button. To switch back to the pointer click anywhere in the menu bar.


To move a device place the pointer inside the device and press and hold the mouse button. The selected device will now follow the mouse pointer although the program will limit the movement based on the current circuit.

8.1.6. NAMING

The organization of your circuit is greatly improved by labeling switches, gates, etc. to reflect their purpose. To name a device or wire select Name from the Edit menu. The pointer will switch to a pencil. Click the pencil near the wire you want to label(Don't click nearer than 5 pixels to a device or the naming won't work). The pencil will switch to an insertion point I-beam. As long as you hold down the mouse button the position of the I-beam can be moved. To type in the label release the mouse button and enter the text followed by return. The label can be moved at a later time just like any other device.

Devices can also be named. Click inside the device to name it. Miscellaneous text can be placed at any point on the circuit as well.


The timing window is like an oscilloscope in that it displays the value of a named signal on the vertical axis and time on the horizontal axis. In this way the behavior of a circuit can be monitored over time. The order of the signals can be changed by selecting the name and moving it up or down. To print out the timing diagram ensure that the timing window is selected and choose Print Timing from the File menu.


Both flip-flops have a set and reset input which is active low.They must be hooked up to a one for the flip-flop to behave normally. This can be done with a switch or by naming the set and clear wires with the special name "1". SHIFT REGISTER

The shift register accepts an input on the SI pin and shifts this to Q0, Q0->Q1, Q1->Q2, Q2->Q3, and Q3 is lost. The LD/ input controls whether a shift or a load operation will take place on the next positive edge of the clock. If LD/ is low a load will take place, if it is high a shift will occur. For a load the values on D0-D3 are transferred to Q0-Q3. COUNTER

The 4 bit counter counts up starting from zero. The binary count value is on the Q0-Q3 outputs. When an overflow occurs the Carry Out(CO) pin is set high. If the LD/ input is low then a load occurs instead of an increment(D0-D3 get transferred to Q0-Q3). SWITCH

The switch can be set to either a 1 or a 0. If the circuit is paused the change doesn't propagate until the simulation is started again. PROBE

Probes show the digital value at a given point in the circuit. The probe can have the values 0, 1, Z, X, and C. "Z" represents a floating wire which means that it is not connected to anything. "X" means the value is unknown. For example, if a AND gate has a 1 input and the other input is not connected then the output of the AND gate will be an unknown. Finally a "C" indicates a conflict. This occurs when two outputs are connected together. HEX KEYBOARD AND HEX DISPLAY

These are useful for displaying and entering four bit values. The low bit is at the bottom and the most significant bit is at the top. The pin on the bottom of the keyboard generates a short clock pulse every time one of the hex digits is selected. TRI-STATE BUFFER

A tri-state buffer is an electronic switch. The input on the top of the buffer controls whether a voltage is passed through to the output or not. In the following diagram the input is let through to the output when the switch is low. When the switch is high the input is not connected to the output and is therefore floating which is why the probe show a "Z".


The most common causes of circuit problems are:

Problem                                 Solution                                
1) wires that aren't joined             Double click on likely bad wires and    
                                        make sure they are joined. Place        
                                        probes on likely places to isolate      
                                        the fault.                              
2) a device is placed over top of       Try deleting a device which doesn't     
another device.                         seem to be working.                     
3) the set and clear inputs of          As in 1) double click on the set and    
flip-flops are not connected to 1       clear inputs to make sure they are      
                                        connected to one.                       
4) an unknown signal that resulted      See if selecting clear unknowns helps   
during construction of the circuit                                              
was not cleared.                                                                
5) outputs are connected together       As in 1) double click on wires to       
                                        isolate the problem.                    



DOS LogicWorks is installed on the PCs in cl 135.2. It resides in the directory "\lgworks". To start LogicWorks, cd to its directory and type "lw". For example:

C:\> cd lgworks

C:\lgworks> lw

After a short time three windows will appear on your screen, a Circuit Window, a Parts Window and a Timing Window. The Circuit Window is the window that is most frequently used, so you may want to resize it so that it is a bit more workable. This is done by selecting one of the window sizing controls in either of the four corners of the window, clicking the left mouse button, and dragging the mouse to resize the window.

You can drag the window around the screen by moving the mouse pointer over the title bar of the window, pressing the left button, and then dragging it to reposition the window. You can also close the circuit window by selecting the close button in the upper left-hand side of the window and clicking in it with the left mouse button. By pressing the right mouse button when the mouse pointer is in the title bar area of the window, you will be given a choice of whether to close the window or to enlarge it so that it fills the whole screen.

The Main Menu can be reached by pressing the right mouse button while the pointer is either inside a window or on the background.

The following Circuit Window illustrates features of a LogicWorks window, as well as the short-cut icons that are located in the bottom left-hand corner of the window. The functions that these icons offer can also be accessed from the Main Menu (shown above).


From the Parts Menu you can select the kind of device you would like to use, be it a gate (AND, OR, XOR, etc), an IO device (a probe, a switch, an led, etc), or some other generic device (a flip-flop, a clock, etc). To select the type of device you want to use, place the mouse cursor over the type selection button, just beneath the title bar in the Parts Menu, and press the left mouse button. A list of different types will appear. Move the mouse to select the appropriate type.

Once a type has been selected, a list of possible devices of that type is displayed. You can select the device you would like to place in the Circuit Window by double-clicking on that device with the left mouse button. The pointer will be replaced by a flickering circuit symbol of the device that you selected. The orientation can be changed by clicking on the orientation icon in the bottom left corner of the Circuit Window. An alternate way of specifying the orientation is by pressing the appropriate arrow key on the keyboard.

To place the device, move the symbol to the desired location in the Circuit Window and press the left mouse button to place the device there. This may be repeated as desired. To return to the pointer, click anywhere on the menu bar, press the space bar, or click on the pointer icon in the bottom left-hand corner in the window.


To draw a wire press and hold down the left mouse button with the pointer at the start of another wire or chip input/output. As long as the button is held down, a wire will follow the pointer. Note that only horizontal or vertical wires can be drawn. If your wire crosses another wire there will be no connection made unless you stop at the point where you would like a connection made. Intersections will be marked by a small square which represents a solder connection.

The wiring tool icon in the bottom left-hand corner of the Circuit Window provides a different way of placing wires. Clicking on the desired starting and ending points will cause a wire to be automatically drawn between these points. If there is no end point to join your wire to, then double-click on the left mouse button to draw a wire up to the point where the wiring tool is currently located.


To delete something, select the zap tool icon from the bottom left-hand corner of the Circuit Window. Move the tip of the bolt to whatever device or wire you want to delete and press the left mouse button. To switch back to the pointer, press the space bar once or click anywhere in the menu bar .


To move a device, place the pointer inside the device, press and hold the left mouse button. This "grabs" the device and is indicated by its change to a highlighted colour. The selected device will now follow the mouse anywhere in the current Circuit Window.

8.1.6. NAMING

The organization of your circuit will greatly improved if you label switches, gates, etc. to reflect their purpose. To name a device or wire select "Name" from the Edit menu or select the naming tool icon in the bottom left-hand corner of your Circuit Window. The pointer will change to a pencil. Click the pencil near the wire you want to label (Do not click nearer than 5 pixels to a device or the naming will not work.). The pencil will then change into an insertion point I-beam. As long as you hold down the mouse button the position of the I-beam can be moved. To type in the label release the mouse button and enter the text. The label can be moved at a later time just like any other device.

Devices can also be named. Simply click inside the device to name it. Miscellaneous text can also be placed at any point on the circuit as well.


The Timing Window is like an oscilloscope in that it displays the value of a named signal on the vertical axis versus time on the horizontal axis. In this way the behaviour of a circuit can be monitored over time.

In order for the timing information of a particular signal to appear in the Timing Window, you must name the signal with the naming tool, from the Circuit Window. The order of the signals can be changed in the Timing Window by selecting the name of the signal with the mouse and moving it up or down.

The speed of the Timing Window can be modified by using the icons in the bottom left hand corner of the window. The three timing icons correspond to the icons in the Circuit Window: pause, slow, and fast simulation. Selecting an icon in either of the Circuit or Timing windows changes the selection in the other window as well.

To print out the timing diagram make sure that the timing window is selected as the current window. Choose the "Print Timing..." option from the File menu.


Both flip-flops have a set and reset input which is active low. They must be hooked up to a one for the flip-flop to behave normally. This can be done with a switch or by naming the set and clear wires with the special name "1". SHIFT REGISTER

The shift register accepts an input on the SI pin and shifts this to Q0, Q0->Q1, Q1->Q2, Q2->Q3, and Q3 is lost. The LD/ input controls whether a shift or a load operation will take place on the next positive edge of the clock. If LD/ is low a load will take place, if it is high a shift will occur. For a load the values on D0-D3 are transferred to Q0-Q3. COUNTER

The 4 bit counter counts up starting from zero. The binary count value is on the Q0-Q3 outputs. When an overflow occurs the Carry Out(CO) pin is set high. If the LD/ input is low then a load occurs instead of an increment (D0-D3 get transferred to Q0-Q3). SWITCH

A switch can be set to either a 1 or a 0. If the circuit is paused the change doesn't propagate until the simulation is started again. PROBE

Probes show the digital value at a given point in the circuit. The probe can have the values 0, 1, Z, X, and C. "Z" represents a floating wire which means that it is not connected to anything. "X" means the value is unknown. For example, if an AND gate has a 1 input and the other input is not connected then the output of the AND gate will be an unknown. Finally a "C" indicates a conflict. This occurs when two outputs are connected together. HEX KEYBOARD AND HEX DISPLAY

These are useful for displaying and entering four bit values. The low bit is at the bottom and the most significant bit is at the top. The pin on the bottom of the keyboard generates a short clock pulse every time one of the hex digits is selected. TRI-STATE BUFFER

A tri-state buffer is an electronic switch. The input on the top of the buffer controls whether a voltage is passed through to the output or not. In the following diagram the input is let through to the output when the switch is low. When the switch is high the input is not connected to the output and is therefore floating which is why the probe show a "Z".


The most common causes of circuit problems are:

Problem                                 Solution                                
1) Wires that are not joined.           Double click on likely bad wires and    
                                        make sure they are joined. Place        
                                        probes on likely wires to monitor and   
                                        hence isolate faults.                   
2) A device is placed over on top of    Try deleting a device which does not    
another device.                         seem to be working.                     
3) The set and clear inputs of          As in 1) double click on the set and    
flip-flops are not connected to 1.      clear inputs to make sure they are      
                                        connected to 1.                         
4) An unknown signal that resulted      See if selecting the options/cear       
during construction of the circuit      unknowns  option from the main menu,    
was not cleared.                        will help.                              
5) Outputs are connected together.      Separate them.                          



Rayshade is a program for creating ray-traced images. It reads a description of a scene to be rendered and produces a colour image corresponding to that description.

Rayshade is available on all Unix platforms in the CS department. The version that this manual describes is version 4.06. Additional information can be acquired from the sources mentioned later in this document. USAGE

Rayshade read an input file specified on the command line. If any command line options are required, they must appear before the input file. Rayshade generates a 24 bit RLE file on standard output, so this should be redirected into whatever filename you wish. For example:

rayshade -n infile.ray > outfile.rle

The previous command invokes rayshade with the no shadow option. The scene description is contained in the file infile.ray and the output RLE file is placed in the file outfile.rle. Rayshade also produces a great deal of diagnostic and statistical information to standard error.

To view an RLE file, you can use the program xv. Xv will read a 24 bit RLE image and display it on the local workstation. Xv can also be used to convert the RLE image into another format, so as to conserve disk space since RLE images tend to be quite large as compared to GIF or JPEG images. COMMAND LINE OPTIONS

-C red_cutoff green_cutoff blue_cutoff

Set the adaptive ray tree pruning colour. If all channel contributions fall below the given cutoff values, no further rays are spawned.

-c Continue an interrupted rendering.

-E eye_separation

Set eye separation for stereo imaging.

-F report_freq

Set frequency, in lines, of status report (default 10).

-h Print a short usage message.

-j Perform jittered sampling.

-l Render image for left eye (requires -E option).

-n Do not trace shadow rays.

-O output_file

Override image file name in input file, if any.

-P cpp_arguments

Specify the options that should be passed to the C preprocessor.

-q Do not print warning messages.

-R xres yres

Set image resolution.

-r Render image for right eye (requires -E option).

-S samples

Specifies number of jittered samples.

-s Do not cache shadowing information.

-T red_thresh green_thresh blue_thresh

Specifies adaptive ray-depth cutoff threshold.

-V filename

Write verbose output to filename.

-v Write verbose output to standard output.

-W minx maxx miny maxy

Render the specified window. INPUT FILE FORMAT

The input file consists of commands (denoted by keywords) followed by numerical or character arguments. Spaces, tabs, or new lines may be used to separate items in the file. Coordinates and vectors are specified in arbitrary floating-point units, and may be written with or without a decimal point. Colours are specified as red-green-blue floating-point triplets which indicate intensity and range from 0 (zero intensity) to 1. (full intensity).

The following sections describe the keywords which may be included in the input file.


eyep x y z

Specifies the eye's position in space. The default is (0, -8, 0).

lookp x y z

Specifies the point at which the eye is looking. The default is (0, 0, 0).

up x y z

Specifies the direction which should be considered "up" from the eye's position. Note that this vector need not be perpendicular to the vector between the look point and the eye's position. The default is (0, 0,1.).

fov horizontal_field_of_view [vertical_field_of_view]

The horizontal_field_of_view specifies, in degrees, the angle between the center of the image and both the left-most and right-most columns of pixels If present, the vertical_field_of_view specifies the angle between the center of the image and the center of the top-most or bottom-most row. If not present, the vertical field of view is calculated using the screen resolution and the assumption that pixels are square. Thus, the fov keyword actually specifies twice the field-of-view. The default horizontal field-of-view is 45 degrees, while the default vertical field-of-view is calculated as described above. IMAGE GENERATION

When specified in the input file, many of the image generation commands may be overridden through command-line options. For example, the line "screen 1024 768" in an input file may be overridden by specifying "-R 128 128" on the command line.

screen x_resolution y_resolution

Specifies the horizontal and vertical resolution of the image to be rendered. This command may be overridden through use of the -R option. The default resolution is 512 by 512 pixels.

background red green blue

Specifies the colour that should be assigned to rays which do not strike any object in the scene. The default is black (0, 0, 0).

outfile filename

Specifies the name of the file to which the resulting image should be written. By default, the image is written to the standard output. This command may be overridden through the use of the -O option.

aperture aperture_radius

The aperture_radius is the radius, in world units, of the aperture centered at the eye point. This controls, in conjunction with focaldist, the depth of field, and thus the amount of focus blur present in the final image. Rays are cast from various places on the aperture disk towards a point which is focal_distance units from the center of the aperture disk. This causes objects which are focal_distance units from the eye point to be in sharp focus. Note that an aperture_radius of zero causes a pinhole camera model to be used, and there will be no blurring (this is the default). Increasing the aperture radius leads to increased blurring. When using a non-zero aperture_radius, it is best to use jittered sampling in order to reduce aliasing effects.

focaldist focal_distance

Specifies the distance, in world units, from the eye point to the focal plane. Points which lie in this plane will always be in sharp focus. By keeping aperture_radius constant and changing focal_distance, it is possible to create a sequence of frames which simulate pulling focus. By default, focal_distance is equal to the distance from the eye point to the look point.

maxdepth maximum_depth

Controls the maximum_depth of the ray tree. The default is 15, with eye rays considered to be of depth zero.

cutoff cutoff_threshold

Specifies the adaptive ray-depth cutoff_threshold. When any ray's maximum contribution to the final colour of a pixel falls below this value, the ray and its children (specularly transmitted and reflected rays) are not spawned. This threshold may be overridden through the use of the -T option. The default value is 0.002.

sample num_samples [jitter | nojitter]

Specifies num_samples2 jittered samples (default). See SAMPLING for details. When specified, this value may be overridden through the use of the -S option. The default value is 3, the maximum value is 5. If nojitter is specified, sample locations and times will not be jittered.

contrast red green blue

Specifies the maximum contrast allowed between samples in a (sub)pixel before subdivision takes place. See SAMPLING for details. When specified in the input file, these values may be overridden through the use of the -C option. The defaults for the red, green and blue channels are 0.25, 0.2, and 0.4, respectively. LIGHT SOURCES

Six types of light sources are supported: ambient, point, directional, extended, spot and area. Ambient light defines the amount of ambient light present in a scene. Point sources are specified by a location in world-space and produce shadows with sharp edges. Directional sources are specified by a direction. Extended sources are specified by a location and a radius. They produce shadows with "fuzzy" edges (penumbrae), but increase ray tracing time considerably. Spot sources produce a spotlight at a given location which is oriented as to be pointing somewhere. Area lighting produces a quadrilateral area light source.

Any number of light sources may be defined, but rendering time will increase with each new light source. It should also be kept in mind that light sources will not actually appear in the image, even if they are defined as being in the frame. By default, rayshade will create a directional light source of intensity 1.0 defined by the vector (1, -1, 1) if no other light source is specified.

In the definitions below, brightness specifies the intensity of the light source. If a single floating-point number is given, the light source emits a "white" light of the indicated normalized intensity. If three floating-point numbers are given, they are interpreted as the normalized red, green and blue components of the light source's colour.

Lights are defined as follows:

light intensity ambient

Define the amount of light present in the entire scene. Only one ambient light source is permitted. If more than one is specified, only the last instance is used. A surfaces ambient colour is multiplied by the intensity of the ambient source to give the total ambient light reflected from its surface. The default intensity is 1, 1, 1.

light intensity directional x y z

Define a light source with the given intensity that is defined to be in the given direction from every point it illuminates. The direction (x y z) does not need to be normalized.

light intensity point x y z

Creates a point source located at (x, y, z).

light intensity spot x y z tx ty tz a [in out]

Place a spotlight at position (x, y, z) oriented as to be pointing at (tx, ty, tz). Value in is the angle at which the light source begins to get attenuated. Value out is the angle at which the spotlight intensity is 0. By default, both are 180 degrees. The intensity of the light falls off as (cosine(angle))a.

light intensity extended x y z radius

Creates an extended source centered at (x, y, z) with the indicated radius. The images produced using extended sources are usually superior to those produced using point sources, but ray-tracing time is increased substantially. Rather than tracing one shadow ray to a light source, multiple rays are traced to various points on the extended source. The extended source is approximated by sampling a square grid of light sources. See SAMPLING for more details on the sampling of extended light sources.

light intensity area x1 y1 z1 x2 y2 z2 usamp x3 y3 z3 vsamp

Create a quadrilateral area light source. The u axis is defined by the vector from (x1, y1, z1) to (x2, y2, z2). Along this axis, a total of usamp samples will be taken. The v axis of the light source is defined from (x1, y1, z1) to (x3, y3, z3). Along this axis, a total of vsamp samples will be taken. SURFACES

Every primitive object has a surface associated with it. The surface specifies the colour, reflectivity, and transparency of an object, and may be defined anywhere in the input file as long as it is defined before it is used.

Surfaces are defined once, and may be associated with any number of primitive objects. A surface definition is given by:

surface surf_name <Surface Definition>

Surf_name is the name associated with the surface. This name must be unique for each surface.

The binding of a collection of surface properties to a given object is accomplished in a bottom-up manner. The surface that is "closest" in the modeling tree to the primitive being rendered is the one that is used to give the primitive its appearance. An object that has no surface bound to it is assigned a default surface that gives it the appearance of white plastic.

The Surface Definition consists of a number of keywords and numbers (usually rgb values). Each of the values in the colour triple are normalized, with zero indicating no intensity and 1 indicating total intensity. If any of the surface components are not included in the surface definition, it defaults to zero. The only exception is the index of refraction, which is assigned the value 1.

The following are the component keywords and their associated values:

ambient ar ag ab

Ar, ag and ab are used to specify the rgb components of the surface's ambient colour. This colour is always applied to a ray striking the surface.

diffuse dr dg db

Dr, dg and db specify the diffuse colour of the surface. This colour, the brightness component of each light source whose light strikes the surface, and dot product of the incident ray and the surface normal at the point of intersection determine the colour which is added to the colour of the incident ray.

specular sr sg sb

Sr, sg and sb are used to specify the specular colour of the surface. The application of this colour is controlled by the coef parameter, a floating-point number which indicates the power to which the dot product of the surface's normal vector at the point of intersection and the vector to each light source should be raised. This number is then used to scale the specular colour of the surface, which is then added to the colour of the ray striking the surface. This model (Phong lighting) simulates specular reflections of light sources on the surface of the object. The larger coef is, the smaller highlights will be.

specpow exponent

Controls the size of the specular highlight. The larger the exponent, the smoother the apparent finish.

body br bg bb

Specifies the body colour of the object. The body colour affects the colour of rays that are transmitteed through the object.

extinct coef

Specifies the extinction coefficient of the interior of the object.

transp transp

Transp indicates the transparency of the object. If non-zero, a ray striking the surface will spawn a ray which is transmitted through the object. The resulting colour of this transmitted ray is scaled by transp and added to the colour of the incident ray. The direction of the transmitted ray is controlled by the index parameter, which indicates the index of refraction of the surface.

reflect refl

Refl indicates the reflectivity of the object. If non-zero, a ray striking the surface will spawn a reflection ray. The colour assigned to that ray will be scaled by refl and added to the colour of the incident ray.

index n

Specifies the index of refraction. The default value is equal to the index of refraction of the atmosphere surrounding the eye.

translu translu tr tg tb stpow

Specifies the translucency, diffusely transmitted colour, and Phong exponent for transmitted specular highlights.


There are no shadows cast on this surface. PRIMITIVE OBJECTS

The ray tracer is capable of rendering a number of primitive objects. Primitives may be specified inside of an object definition block, in which case they are added to the list of primitives belonging to that object. In addition, primitives may be defined outside of object-definition blocks. Primitives such as these are added to the list of primitives belonging to the World object. See below for more details.

Rayshade usually ensures that a primitive's normal is pointing towards the origin of the incident ray when performing shading calculations. Exceptions to this rule are transparent primitives, for which rayshade uses the dot product of the normal and the incident ray to determine if the ray is entering or exiting the surface, and super quadrics, whose normals are never modified due to the nature of the ray/super quadric intersection code. Thus, all non transparent primitives except super quadrics will in effect be double sided.

Primitives are specified by lines of the form:

primitive_type [surface] <primitive definition> [transformations] [texture mapping information]

Surface is the name of the surface to be associated with the primitive, if any. Texture mapping and transformations are discussed below. A list of available primitives follows. The surface parameter is optional, and is omitted in all the following descriptions.

blob thresh st r x y z [st r x y z ...]

Defines a blob consisting of a threshold equal to thresh, and a group of one or more metaballs defined with a strength, st, radius, r, and position (x, y, z).

box x1 y1 z1 x2 y2 z2

Create an axis-aligned box which has the specified opposite corners.

cone xbase ybase zbase xtop ytop ztop base_radius top_radius

Creates a (truncated) cone which extends from (xbase, ybase, zbase) to (xtop, ytop, ztop). The bottom of the cone will have radius base_radius, while the top will have radius top_radius.

cylinder xb yb zb xt yt zt radius

Creates a cylinder which extends from base (xb, yb, zb) to top (xt, yt, zt) and has the indicated radius.

heightfield file

Creates a heightfield defined by the altitude data stored in the named file. The height field is based on perturbations of the unit square in the z = 0 plane, and is rendered as a surface tessellated by right isosceles triangles. The binary data in file is stored as an initial integer giving the square root of number of data points in the file, followed by altitude (Z) values stored as floating-point numbers. The height field is rendered as a surface tessellated by triangles. Non-square height fields may be rendered by setting vertex heights to less than or equal to -1000. Triangles which have any vertex less than or equal in altitude to this value are not rendered.

plane xn yn zn x y z

Create a plane which passes through point (x, y, z) and has normal (xn, yn, zn).

poly x1 y1 z1 x2 y2 z2 x3 y3 z3 [x4 y4 z4 ...]

Creates a polygon with the specified vertices. The vertices should be given in a counterclockwise order as one faces the "top" of the polygon. The polygon may be non-convex, but non-planar polygons will not be rendered correctly. The number of vertices defining a polygon is limited only by available memory.

sphere radius x y z

Creates a sphere with the indicated radius centered at (x, y, z).

torus rmajor rminor x1 y1 z1 x2 y2 z2

Create a torus centered around (x1, y1, z1) with a minor radius rminor and a major radius rmajor. The up vector is (x2, y2, z2).

triangle x1 y1 z1 x2 y2 z2 x3 y3 z3

Creates a triangle with vertices (x1, y1, z1), (x2, y2, z2) and (x3, y3, z3). Vertices should be given in a counter-clockwise order as one is looking at the 'top' face of the triangle.

triangle p1x p1y p1z n1x n1y n1z p2x p2y p2z n2x n2y n2z p3x p3y p3z n3x n3y n3z

Defines a Phong-shaded triangle. Here, the first three floating-point numbers specify the first vertex, the second three specify the normal at that vertex, and so on. Again, vertices should be specified in counterclockwise order. Currently, all three vertex/normal pairs are stored for every triangle (as opposed to storing pointers to these vectors, which would reduce storage space in cases where more than one triangle shared a vertex). AGGREGATE OBJECTS

An aggregate is a collection of primitives, aggregate, and CSG objects. Aggregate objects only need to be defined once, but may be instantiated many times. Each instance may also be transformed and textured differently.

There are two types of aggregate objects, but they are defined in roughly the same way. Each is defined by specifying a keyword that defines the type of aggregate followed by a series of object instanciations and surface definitions, and terminated with the end keyword. The two types of aggregates are list and grid.

list ... end

Each object in the list is tested for intersection with the ray. The closest intersection is returned and the others are ignored. Any number of primitive objects may appear in the list.

grid xvox yvox zvox ... end

The region of space occupied by the grid is divided into a number of discrete box-shaped voxels. Each of the voxels contains a list of the objects that intersect the voxel. This limits the number of objects tested for intersection with the ray to only those mostly likely to be intersected. (xvox, yvox, zvox) defines the voxel space. CONSTRUCTIVE SOLID GEOMETRY

Constructive Solid Geometry (CSG) is the process of building solid objects from other solids. There are three CSG operators: union, intersection, and difference. Each of these operators is boolean and produces a single solid object by acting upon two other objects. By combining many different levels of CSG operators, very complex objects can be produced from relatively simple primitives or aggregates.

CSG objects are defined by surrounding the objects on which to be operated, as well as any other surface-binding commands.

union <object> <object> [<object> ...] end

Specify an object defined as the union of the given objects.

difference <object> <object> [<object> ...] end

Specify an object defined as the difference of the given objects.

intersect <object> <object> [<object> ...] end

Specify an object defined as the intersection of the given objects.

Currently only two objects in a CSG list are supported, but support for more is planned in a future release. NAMED OBJECTS

A name may be associated with any primitive, aggregate, or CSG object. An object that is named in this manner can then be instantiated. The keywords, name and object respectively, are used to perform this.

name objname <instance>

Associate objname with the given object. The specified object is not actually instantiated, it is only aliased with the given name.

object objname [<transformations> <textures>]

Instantiate a copy of the object associate with objname. Transformation and texture information can be applied to the object being instantiated. THE WORLD OBJECT AND OTHER INFORMATION

A special object named World is maintained internally by rayshade. Primitive definitions and object instantiations which do not appear inside an object definition block are added to this object. When performing ray tracing, rays are intersected with the objects that make up the World object.

For convenience, one may also define surfaces inside of an object-definition block. Surfaces defined in this manner are nevertheless globally available. In addition, object definitions may be nested. This facilitates the definition of objects through the use of recursive programs. TRANSFORMATIONS

Rayshade allows for the application of arbitrary linear transformations to primitives and compound objects. The specification of transformations occurs immediately following the specification of a primitive or instantiation of an object. Any number of transformations may be composed; the resulting total transformation is applied to the entity being transformed. Transformations are specified by:

translate x y z

Translate the object by (x, y, z).

rotate x y z theta

Rotate the object counter-clockwise about the vector (x, y, z) by theta degrees.

scale x y z

Scale the object by (x, y, z).

transform x1 y1 z1 x2 y2 z2 x3 y3 z3 [xd yd zd]

Transform the object by the column major matrix specified by the nine floating point numbers. Thus, a point (x, y, z) on the surface of the object is mapped to (x*x1 + y*y1 + z*z1, x*x2 + y*y2 + z*z2, x*x3 + y*y3 + z*z3). If it is given, (xd, yd, zd) specifies a translation vector. TEXTURE MAPPING

Rayshade provides a means of applying solid procedural textures to surfaces of primitives. This is accomplished by supplying texture mapping information immediately following the definition of a primitive, object, or instance of an object. This allows one to texture individual primitives, objects, and individual instances of objects at will. Texturing information is supplied via a number of lines of the following form:

texture texture_type [arguments] [transformations]

Texture_type is the name of the texture to apply. Arguments are any arguments that the specific texture type requires. If supplied, the indicated transformations will be applied to the texture. (More accurately, the inverse of the supplied transformation is applied to the point of intersection before it is passed to the texturing routines.)

Versions of Perlin's Noise() and DNoise() functions are used to generate values for most of the interesting textures. Currently, there are eleven textures available:

blotch blend_factor surface

This texture produces a mildly interesting blotchy looking surface. Blend_factor is used to control the interpolation between a point's default surface characteristics and the characteristics of the named surface. A value of 0 results in a roughly 50-50 mix of the two surfaces. Higher values result in greater instances of the 'default' surface type.

bump scale

Applies a random bump map to the surface being textured. The point of intersection is passed to DNoise(). The returned normalized vector is weighted by scale and added to the normal vector at the point of intersection.

checker surface

Applies a (3D) checkerboard texture to the object being textured. Every point that falls within an "even" cube will be shaded using the characteristics of the named surface. Every point that falls within an "odd" cube will retain its usual surface characteristics. Be warned that strange effects due to roundoff error are possible when the planar surface of an object lies in a plane of constant integral value in texture space.

cloud scale H lambda octaves cthresh lthresh tscale

This texture is a variant on Geoff Gardner's ellipsoid-texturing algorithm. It should be applied to unit spheres centered at the origin. These spheres may be transformed to form the appropriately shaped cloud or tree. The parameters are identical to those for the fbm texture (see below), plus the three threshold parameters to control the overall density of the cloud.

fbm offset scale H lambda octaves thresh [colourmap]

This texture generates a sample of discretized fractional Brownian motion (fBm) and uses it to modify the diffuse and ambient components of an object's colour. If no colourmap is named, the sample is used to scale the object's diffuse colour. If a colourmap name is given, a 256-entry colourmap is read from the named file, and the object is coloured using the values in this colourmap (see below). Scale is used to scale the output of the fractional Brownian motion function. Offset allows one to control the minimum value of the fBm function. H is related to the Holder constant used in the fBm (a value of 0.5 works well). Lambda is used to control the lacunarity, or spacing between successive frequencies, in the fBm (a value of 2.0 will suffice). Octaves specifies the number of octaves of Noise() to use in simulating the fBm (5 to 7 works well), and thresh is used to specify a lower bound on the output of fBm function. Any value lower than thresh is set to zero.

fbmbump offset scale H lambda octaves

This texture is similar to the fbm texture. Rather modifying the colour of a surface, fbmbump acts as a bump map.

gloss glossiness

Gives a reflective surface a glossy appearance. This texture perturbs an objects surface normal such that the normal "samples" a cone of unit height with radius 1 - glossiness. A value of 1 results in perfect mirror-like reflections while a value of 0 results in fuzzy reflections.

marble [colourmap]

This texture gives a surface a marble like appearance. If the name of a colourmap file is given, the marble will be coloured using the RGB values in the colourmap. If no colourmap name is given, the diffuse and ambient components of the object's surface are simply scaled. One may transform the texture to control the density of the marble veins.

sky scale H lambda octaves cthresh lthresh

Similar to the fbm texture. Rather than modifying the colour of a surface, this texture modulates its transparency. Chtresh is the value of the fbm function above which the surface is completely opaque. Below the vlaue of lthresh, the surface is completely transparent.

stripe <surface> size bump

Apply a raised stripe pattern to the surface. The surface properties used to colour the stripe are those of the given surface. The width of the stripe, as compared to the unit interval, is given by size. The magnitude of bump controls the extent to which the bump appears to be displaces from the rest of the surface. If negative, the stripe will appear to sing into the surface and if positive, it will appear to stand out of the surface.


This texture gives a wood-like appearance to a surface.

A colourmap is an ASCII file 256 lines in length, each line containing three space separated integers ranging from 0 to 255. The first number on the nth line specifies the red component of the nth entry in the colourmap, the second number the green component, and the third the blue. The values in the colourmap are normalized before being used in texturing functions. Textures which make use of colourmaps generally compute an index into the colourmap and use the corresponding entry to scale the ambient and diffuse components of a surface's colour.

It is important to note that more than one texture may be applied to an object at any time. In addition to being able to apply more than one texture directly (by supplying multiple "texturing information" lines for a single object), one may instantiate textured objects which, in turn, may be textured or contain instances of objects which are textured, and so on. IMAGE TEXTURING

This texturing allows you to use images to modify the characteristics of a surface.

component <component>

The named component will be modified. Possible components are: ambient, diffuse, specular, specpow, reflect, transp, and bump.

range high low

Specify the range of values to which the values in the image should be mapped. A value of 1 will be mapped high, 0 to low. Intermediate values will be linearly interpolated.


When given, pixel averaging will be performed in order too smooth the sampled image. If not specified, no averaging will occur.

textsurf <surface specification>

For use when modifying surface colours, this keyword specifies that the given surface should be used as the base to be modified when the alpha value in the image is non-zero. When alpha is zero, the object's unmodified default surface characteristics are retained.

tile un vn

Specify how the image should be tiled along the u and v axes. If positive, the value of un gives the number of times the image should be repeated along the u axis, starting from the origin of the texture, and positive vn gives the number of times it should be repeated along the v axis. If either value is zero, the image is repeated infinitely along the appropriate axis. ATMOSPHERIC EFFECTS

Rayshade has the capability of including several kinds of atmospheric effects when rendering an image. Currently, three such effects are available:

fog r g b r_thin g_thin b_thin

Add global exponential fog with the specified thinness and colour. Fog is simulated by blending the colour of the fog with the colour of each ray. The amount of fog colour blended into a ray colour is an exponential function of the distance from the ray origin to the point of intersection divided by the r_thin, g_thin, and b_thin values. If the distance divided by thinness is equal to 1, a ray's new colour will be half of the fog colour plus half its original colour.

fogdeck altitude offset scale chaoscale r g b r_thin g_thin b_thin

Add low-altitude fog, with transmissivity modulated by a chaotic function.

mist r g b r_thin g_thin b_thin zero scale

Add global low-altitude mist of the specified colour. The colour of a ray is modulated by a fog with density which varies linearly with the difference in altitude (Z coordinate) between the ray origin and the point of intersection. The three thin values specify the transmissivity (thinness) of the mist for each of the red, green and blue channels. The base altitude of the mist is given by zero, and the apparent height of the mist can be controlled by scale, which is used to scale the difference in altitude.


This section clarifies how antialiasing and sampling of extended light sources are accomplished. Two types of anti-aliasing are supported; adaptive subdivision and so called "jittered sampling".

Adaptive subdivision works by sampling each pixel at its corners. The contrast between these four samples is computed, and if too large, the pixel is subdivided into four equivalent sub-pixels and the process is repeated. The threshold contrast may be controlled via the -C option or the contrast command. There are separate thresholds for the red, green, and blue channels. If the contrast in any of the three is greater than the appropriate threshold value, the pixel is subdivided. The pixel-subdivision process is repeated until either the samples' contrast is less than the threshold or the maximum pixel subdivision level, specified via the -P option or the adaptive command, is reached. When the subdivision process is complete, a weighted average of the samples is taken as the colour of the pixel.

Jittered sampling works by dividing each pixel into a number of square regions and tracing a ray through some point in each region. The exact location in each region is chosen randomly. The number of regions into which a pixel is subdivided is specified through the use of the -S option. The integer following this option specifies the square root of the number of regions.

Each extended light source is, in effect, approximated by a square grid of light sources. The length of each side of the square is equal to the diameter of the extended source. Each array element, which is square in shape, is in turned sampled by randomly choosing a point within that element to which a ray is traced from the point of intersection. If the ray does not intersect any primitive object before it strikes a light source element, there is said to be no shadow cast by that portion of the light source. The fraction of the light emitted by an extended light source which reaches the point of intersection is the number of elements which are not blocked by intervening objects divided by the total number of elements. The fraction is used to scale the intensity (colour) of the light source, and this scaled intensity is then used in the various lighting calculations.

When jittered sampling is used, one shadow ray is traced to each extended source per shading calculation. The element to be sampled is determined by the region of the pixel through which the eye ray at the top of the ray tree passed.

When adaptive super sampling is used, the -S option or the samples command controls how may shadow rays are traced to each extended light source per shading calculation. Specifically, each extended source is approximated by a square array consisting of samples * samples elements. However, the corners of the array are skipped to save rendering time and to more closely approximate the circular projection of an extended light source. Because the corners are skipped, samples must be at least 3 if adaptive super sampling is being used.

Not that the meaning of the -S option (and the samples command) is different depending upon whether or not jittered sampling is being used.

While jittered sampling is generally slower than adaptive subdivision, it can be beneficial if the penumbrae cast by extended light sources take up a relatively large percentage of the entire image or if the image is especially prone to aliasing.


A very simple rayshade input file might be:

eyep -20 20 20

light 1 directional 1 1 1

surface red

ambient 0.2 0 0

diffuse 0.8 0 0

specular 0.5 0.5 0.5

specpow 32

reflect 0.8

surface green

ambient 0 0.2 0

diffuse 0 0.8 0

sphere red 8 0 0 -2

plane green 0 0 -10 0 0 1

Passing this input to rayshade will result in an image of a red reflective sphere sitting on a green ground plane. Note that in this case, default values for lookp, up, screen, fov, and background are assumed.

A more interesting example uses instantiation to place multiple copies of an object at various locations in world space:

eyep 10 10 10

fov 20

light 1 directional 0 1 1

surface red

ambient 0.2 0 0

diffuse 0.8 0 0

specular 0.5 0.5 0.5

specpow 32

reflect 0.8

surface green

ambient 0 0.2 0

diffuse 0 0.8 0

surface white

ambient 0.1 0.1 0.1

diffuse 0.8 0.8 0.8

specular 0.6 0.6 0.6

specpow 30

name bl list

sphere red 0.5 0.5 0.5 0

sphere white 0.5 0.5 -0.5 texture marble scale 0.5 0.5 0.5

sphere red 0.5 -0.5 -0.5 0

sphere green 0.5 -0.5 0.5 0


object bl translate 1 1 0

object bl translate 1 -1 0

object bl translate -1 -1 0

object bl translate -1 1 0

Here, an object named bl is defined to consist of four spheres, two of which are red and reflective. The object is stored as a simple list of the four spheres. The World object consists of four instances of this object, translated to place them in a regular pattern about the origin. Note that since the marbled sphere was textured in "sphere space" each instance of that particular sphere has exactly the same marble texture applied to it.

Of course, just as the object bl was instantiated as part of the World object, one may instantiate objects as part of any other object. For example, a series of objects such as:

#define ENDCAPS

name wheel list

sphere tire_colour 1 0 0 0 scale 1 0.2 1

sphere hub_colour 0.2 0 0 0


name axle list

object wheel translate 0 2 0

object wheel translate 0 -2 0

cylinder axle_colour 0.1 0 -2 0 0 2 0

#ifdef ENDCAPS

disc axle_colour 0.1 0 -2 0 0 -4 0

disc axle_colour 0.1 0 2 0 0 4 0



name truck list

box truck_colour -5 -2 -2 5 2 2 /* Trailer */

box truck_colour 4 -2 -2 8 2 0 /* Cab */

object axle translate -4 0 -2

object axle translate 4 0 -2


could be used to define a very primitive truck-like object.


Ray tracing is a computationally intensive process, and rendering complex scenes can take hours of CPU time, even on relatively powerful machines. There are, however, a number of ways of attempting to reduce the running time of the program.

The first and most obvious way is to reduce the number of rays which are traced. This is most simply accomplished by reducing the resolution of the image to be rendered by either using the -R command line option or the screen keyword. By default, a pixel will be subdivided a maximum of one time, giving a maximum of nine rays per pixel total.

Alternatively, the -C option or the cutoff and contrast commands may be used to decrease the number of instances in which pixels are subdivided. Using these options, one may indicate the maximum normalized contrast which is allowed before super sampling will occur. If the red, green or blue contrast between neighboring samples (taken at pixel corners) is greater than the maximum allowed, the pixel will be subdivided into four subpixels and the sampling process will recurse until the sub-pixel contrast is acceptable or the maximum subdivision level is reached.

The number of rays traced can also be lowered by making all surfaces non-reflecting and non-refracting or by setting maxdepth to a small number. If set to 0, no reflection or refraction rays will be traced. Lastly, using the -n option or the noshadow command will cause no shadow rays to be traced.

In addition, judicious use of the grid command can reduce rendering times substantially. However, if an object consists of a relatively small number of simple objects, it will likely take less time to simply check for intersection with each element of the object than to trace a ray through a grid.

The C preprocessor can be used to make the creation and managing of input files much easier. For example, one can create "libraries" of useful colours, objects, and viewing parameters by using #define and #include.


A program called rayview displays your rayshade image using GL primitives. Using this program can save lots of time since it only takes a few seconds to display the image as opposed to the amount of time that it would take to raytrace. This program is only available on the SGI workstations.

Another program called raypaint (another GL specific version called raypaintgl also exists specifically for SGI workstations) also allows you to preview a rayshade file by progressively rendering it in an X window. Although this is slow, it does allow you to get a general idea of how the file looks. The X version of raypaint only displays the image in gray scale but the GL version can display its images in colour.


Rayshade had its beginnings as an "introductory" public domain ray tracer written by Roman Kuchkuda. Vestiges of his code may be found in rayshade, particularly in the names of variables and the super quadric code. The first version of rayshade was written at Princeton University during 1987-88 by Craig Kolb, David C. Hoffman, and David P. Dobkin. Version 4.0 of rayshade was written by Craig Kolb and Rod Bogart during 1991. The Noise() and DNoise() routines which form the basis of many of the texturing functions were written by Robert Skinner and Ken Musgrave. The depth of field code appears courtesy of Rodney G. Bogart.


Rayshade performs no automatic hierarchy construction. The intelligent placement of objects in grids and/or lists is entirely the job of the modeler.

While transparent objects may be wholly contained in other transparent objects, rendering partially intersecting transparent objects with different indices of refraction is, for the most part, nonsensical.

Rayshade is capable of using large amounts of memory. In the environment in which it was developed (machines with at least 8 Megabytes of physical memory plus virtual memory), this has not been a problem, and scenes containing several billion primitives have been rendered. On smaller machines, however, memory size can be a limiting factor.

The "Total memory allocated" statistic is the total space allocated by calls to malloc. It is not the memory high-water mark. After the input file is processed, memory is only allocated when refraction occurs (to push media onto a stack) and when ray tracing height fields (to dynamically allocate triangles). The image produced will always be 24 bits deep.

Explicit or implicit specification of vectors of length less than epsilon (1.E-6) results in undefined behavior.


Complete documentation (PostScript format) and a straight ASCII quick reference guide for rayshade is stored in the directory /net/share/doc/rayshade on the Unix machines. Several example rayshade files are also stored here.

For complete information on the RLE file format, refer to the man page rle(5).

8.4 Turbo Assembler

The assembler is use to convert the source code into object code, which can be used by the linker to create an executable file. The normal format for CS 300 is :

TASM /la /zi file.asm

If TASM is typed with out any object files specified the following help screen is displayed:

Turbo Assembler Version 2.5 Copyright (c) 1988, 1991 Borland International

Syntax: TASM [options] source [,object] [,listing] [,xref]

/a,/s Alphabetic or Source-code segment ordering

/c Generate cross-reference in listing

/dSYM[=VAL] Define symbol SYM = 0, or = value VAL

/e,/r Emulated or Real floating-point instructions

/h,/? Display this help screen

/iPATH Search PATH for include files

/jCMD Jam in an assembler directive CMD (eg. /jIDEAL)

/kh# Hash table capacity # symbols

/l,/la Generate listing: l=normal listing, la=expanded listing

/ml,/mx,/mu Case sensitivity on symbols: ml=all, mx=globals, mu=none

/mv# Set maximum valid length for symbols

/m# Allow # multiple passes to resolve forward references

/n Suppress symbol tables in listing

/o,/op Generate overlay object code, Phar Lap-style 32-bit fixups

/p Check for code segment overrides in protected mode

/q Suppress OBJ records not needed for linking

/t Suppress messages if successful assembly

/w0,/w1,/w2 Set warning level: w0=none, w1=w2=warnings on

/w-xxx,/w+xxx Disable (-) or enable (+) warning xxx

/x Include false conditionals in listing

/z Display source line with error message

/zi,/zd Debug info: zi=full, zd=line numbers only

8.5 Turbo Linker

The linker is use to link one or more object files, created by turbo assembler or a compiler, to create an executable file. The normal fromat for CS 300 is :

TLINK /v file.obj [file2.obj] [...]

If TLINK is typed with out any object files specified the following help screen is displayed:

Turbo Link Version 4.0 Copyright (c) 1991 Borland International

Syntax: TLINK objfiles, exefile, mapfile, libfiles, deffile

@xxxx indicates use response file xxxx

Options: /m = map file with publics

/x = no map file at all

/i = initialize all segments

/l = include source line numbers

/L = specify library search paths

/s = detailed map of segments

/n = no default libraries

/d = warn if duplicate symbols in libraries

/c = lower case significant in symbols

/3 = enable 32-bit processing

/v = include full symbolic debug information

/e = ignore Extended Dictionary

/t = create COM file (same as /Tc)

/o = overlay switch

/P[=NNNNN] = pack code segments

/A=NNNN = set NewExe segment alignment factor

/ye = expanded memory swapping

/yx = extended memory swapping

/C = case sensitive exports and imports

/Txx = specify output file type

/Tdx = DOS image (default)

/Twx = Windows image

(third letter can be c=COM, e=EXE, d=DLL)


"FileMaker Pro is an electronic database manager ... [that] lets you rearrange the presentation of your information . . . . You can use the same information, such as names and addresses, in address lists, mailing labels, form letters, invoices and multi-page reports." [FileMaker Pro - Getting Started, Intro-9]


Create a new database by first clicking on the FileMaker icon and then clicking on the New icon in the startup screen shown as follows.

Filemaker Startup Screen

An existing database can be opened by clicking on Open in this dialogue box and then typing in the name of the file. Alternately, do not call FileMaker directly, simply click on the icon of the existing database. This will launch FileMaker and also open the database.


When you instruct FileMaker to create a new database you are automatically put in the Define Fields screen. The following illustration represents this screen. Enter the name for a field, click on the desired field type, then click on Create. The field that you just entered will be highlighted but carry on, type in the next field name, select its type, and click on Create again. When you have finished defining fields in this manner, click on Done. This will put you in 'browse' mode.

Define Fields Screen


When you exit Design, the Browse screen appears. The first time you encounter this, the cursor will be located at the first field of the first record. Type in the contents of fields, using the [Tab] key to move between fields. Select New Record from the Edit menu to create the next record. Use [Enter] (not [Return] ) to terminate the last entry. The following illustration represents the Browse screen with 'text' fields defined under the names 'first', 'second', and 'third'.

Browse Screen


Fields can be organized in a variety of ways to produce different reports. Each organization is referred to as a layout.. Click on Layout in the Select menu to design a report layout. The standard layout has:

a header - which will appear at the top of each printed page,

a body - which contains field contents, and

a footer - which appears at the bottom of each printed page.

Click on New Layout from the Edit menu to begin defining a layout. Now enter a name for the layout and click on one of the available layout types (standard, columnar, single page, labels, envelope, or blank). Click OK to proceed to the dialogue box which allows you to select what fields and what field order you desire in your report. When the list of fields appears, click on the field you wish to appear first, then click on the Move button in the center of the screen. Continue selecting fields in this manner, and click OK when done.

Layout Field Dialog Box

After defining fields for your layout you are returned to the layout screen. You will not see any field contents in this screen, only field labels. Select Rulers from the Layout menu to have vertical and horizontal rulers appear on the screen. This will make it easier to define the desired size of the fields.

Begin by adjusting the appearance of the header area by clicking and holding above and to the left of the first field (shown in the following illustration).

Layout Screen

Drag the mouse below and to the right of the last field. Square boxes appear around the fields selected. Now go to the format menu and choose the desired font style, size, and so on. Click on a blank portion of the screen to de-select field(s).

To adjust individual fields:

- click on the field to make the square boxes outline the field


- click and hold above and to the left of a field and then drag to enclose desired fields (for example - the label in the header record and the corresponding field in the body of the report)

- click and hold on the outlined field(s) then move it to where you want it placed (holding the [Shift] key down before you do this will constrain the motion to the direction you first begin when moving the mouse)

- to enlarge a field: click and drag the lower right square box

- to change a field label: click on the A icon in the tools palette on the left side of the screen, highlight the field name you want to change, then type in the desired field name

Return to Browse mode by clicking on Browse in the Select menu. Click on the layout name in the top left corner of the screen to call a pop-up menu that you can use to move between layouts. The following example illustrates two sample layouts.



The material for this manual has been compiled from man page information and from various other X Windows references, most notably "X Window System Programming" by Nabajyoit Barkakati. This manual is only intended as an introduction to programming in X Windows. Its purpose is to learn how to produce programs that will make use of X Windows features and functionality and to become familiar with the coding and design philosophies behind GUI and event-driven programming.


Applications with graphical user interfaces (GUI's) display output by calling routines from an underlying window system -- software which manages distinct screen regions (usually rectangular) called windows. The problem with existing interfaces, such as those used in early versions of workstations and on the Macintosh or MS Windows is that the basic design of the window systems are different, and the routines and the functionality they provided varied from one system to another. In other words, there was no common standard.

A solution to this problem was to design a new window system with a standard set of library routines and implement that system on all graphics workstations. Robert Scheifler, Jim Gettys, and their team members took this approach when they developed the X Window System (or X, as it is more commonly called) at MIT in 1984.

They assumed a few basic capabilities in the workstation: a bit-mapped graphics display, a keyboard, and a mouse. A dozen computer vendors announced a joint effort to support and standardize X in January 1987. Since then almost all workstation vendors have accepted X as the basis of the graphical user interface to their hardware. DESCRIPTION

X is a network-transparent windowing system based on the client-server model. It is actually a combination of many things -- the X protocol, X display server, X clients, and Xlib routines. The most popular version of X currently available on most workstations is X11R5.

The X server process, running on a workstation with bitmapped graphics display, manages regions of the screen known as windows where the output from X client applications appear. The X client, whether running locally or remotely, sends requests to the server using a communication channel. An X client on a system can display its output on any X server without regard to display hardware or the operating system.

Application developers do not work directly with the X protocol. They use the Xlib routines or one of the many X toolkits (ex. Xt Intrinsics or OSF Motif) that provide higher level objects, known as widgets, for building user interfaces.

X windows are arranged in a hierarchy. That is, the root window, or your desktop, is the top-level window. It is the parent and all windows within it are its children. The X window that opens when you run your application is called the main window of your application. It is a child of the root window. Inside your main application window you may have buttons or sliders or other sorts of objects. These too are windows and they are children of your main window. It is important to remember this as you write your X application because certain functions require that you specify parent and/or child windows.

For more information, it is suggested that you look in any of several XWindows texts such as the one mentioned above. PROGRAM DEVELOPMENT

Before you can program in any environment you must first become familiar with that environment. This goes for complex windowing environments, such as X Windows, MS Windows, the Macintosh, OS/2's Presentation Manager, or even the Amiga's windowing API. There are certain things that all these environments have in common though: a highly structured programming interface, communication facilities, and an interface to other system services. Unlike most other windowing systems, however, X is simply a graphical system and can be totally separate from the computing system. You can run, for example, a compute intensive program on a Cray and display the results locally through the use of your X display server.

A typical X window application program contains the following basic components:

1. Make a connection to a display server.

2. An X application you write may run on any number of different computers which support different display resolutions, supported colours, fonts, etc. A general X client will usually first load default resources available from the display that it is connected to and then define its own fonts and colours.

3. The geometrical components of the window such as size, shape, and position in a screen are specified.

4. The top-level window is then created -- it is important to note that it is only a data structure and not yet displayed on the screen of the server yet. Now you may set or change window manager properties, graphics context, and any ohter window attributes.

5. Make the top-level window visible.

6. You may want to create child windows under the main window. Child windows can be denoted by following steps similar to 3, 4, and 5 above.

7. Select which of the events occuring in the window should be processed by your program.

8. Enter the main processing module of your application program which is typically an event loop.

It is important to keep in mind that X programs are event-driven. This means that the main program loops indefinitely and reacts to events as they happen. These events, most commonly, are button presses or window movements. The application is written around the event processing, which is usually implemented as a large while statement containing one or more if-then-else or case statements.


Logging in to an X Window terminal is not very different from logging in through a regular ASCII based terminal. Usually there is window displayed on the X terminal which prompts for your username and password. On some X terminals it may just be a command line prompt, no different than a regular terminal.

On the DEC Stations available in CL 136, the program xdm prompts you for your username and password. It handles most of the startup details, which are:

1. Get a username and password.

2. Sets DISPLAY environment variable to correspond to the name of your terminal.

3. Reads information from the file .Xresources, if it exists in your home directory.

4. Executes the file .xsession from your home directory, if it exists, else it executes the system .xsession file.

If there is an error in the .xsession file then you will not be able to login. To fix this, you will have to login with a telnet session from a regular terminal and fix your .xsession file. You may also login in through xdm by pressing the F1 key after your password to load the system .xsession file.

The DISPLAY environment variable is set using the setenv command. This specifies the machine and the display number to send output to. For example:

setenv DISPLAY dec5kj:0

specifies that the output of any clients is to go to the machine dec5kj and to display 0. A computer could have more than one monitor attached so the second one would be display 1. RUNNING X CLIENTS

Most X Window applications have a -geometry option which lets you specify the size and location of the window. The general form for it is:

xprog WIDTHxHEIGHT[+/-]xoffset[+/-]yoffset

Depending on the application, WIDTH and HEIGHT are either in characters or pixels. The offset, if positive, specifies the distance from the left side or top of the screen. If it is negative, the offset is from the right side or bottom of the screen. For example:

xterm 80x30+100-500

will open an xterm client that is 80 columns by 30 lines. The window will be displayed at a location 100 pixels in from the left hand side of the screen and 500 pixels up from the bottom of the screen. ACCESS CONTROL

Since X Windows was designed to be able to run programs remotely, some kind of access control was needed to prevent any old X client from displaying to your screen. When xdm first runs, it selects a random number that is called a MAGIC-COOKIE. This number is communicated to the X server running on the X terminal and is stored in a file called .Xauthority in your home directory. From that point on, every X packet sent to the server must contain the cookie or the X server will ignore the packet.

Another form of access control is to use the xhost command to specify which hosts have access to your X server. For example:

xhost +erato

gives anyone on erato access to your X server. On the other hand:

xhost -erato

takes that access away. To disable or enable access to the world, just use the + or - without any host name. RESOURCES

X Window applications can have many command line options. Sometimes it is not practical to specify all of them on the command line, so X has defined the .Xresources file as a standard location for default parameters for most command line parameters.

The resource names for different applications are usually explained in the man pages for that application. A simple example is to set the cursor colour for your xterm sessions:

XTerm*cursorColor: gold

Default fonts, colours, displays, geometry's, titles, and most any other X option can be specified from the .Xresources file. CONFIGURING YOUR X WINDOW SESSION

Configuring X Windows can be very time consuming. There is a system wide .xsession file that contains configuration parameters suitable for most users. There is also a skeleton configuration file available in /net/share/skel/.xsession so that users can copy it and modify it for their own use. Typically X clients, such as xbiff or xclock, will be started from this file.

X is only a graphics protocol. The Window Manager can be any window manager the user wishes to install, whether it be twm, mwm (Motif), olwm (Open Look), or fvwm. The default window manager at the U of R is twm.

Detailed documentation on twm can be found from the man pages. The file .twmrc can be used to configure twm colours, fonts, and behaviour as described in the man page. Documentation for mwm is also on-line in the form of man pages. The file .mwmrc can be used to configure it in a manner similar to the twm configuration file.


To compile an X client is not very complex. Usually it is no more difficult than compiling regular programs.

Most X programs are written in C. As a matter of fact the entire X Windows system itself is written in C so it is quite portable to different environments. Since most X applications are also written in C they are also portable. However, other languages, such as Pascal, FORTRAN, and Icon are also used to write X applications because of X's standard programming interface.

To write an X application in C you must include a number of .h files. These are:

#include <X11/Xlib.h>

- Defines the basic structures that are needed by Xlib.

#include <X11/Xutil.h>

- Used by the utility function of Xlib.

#include <X11/Xresource.h>

- This is required if you use the resource manager functions (those that begin with Xrm).

You must link in the Xlib library routines when you compile your program. This is no different than linking in any other C libraries. For example:

cc -o xprog xprog.c -lX11

NOTE: To conserve the limited disk space on the DECstations, a subset of the X libraries have been moved to a common directory on the central server (Mercury). You will have to link these libraries in using the -L option. For example:

cc -o xprog xprog.c -lX11 -L/net/share/ULTRIX/depot/X11R5/lib

The rules for running your X client are the same as those for running any X client, such as xcalc or xv. You can manipulate the window as you could any other window and you can make use of any X facility such as remote servers, etc.


Following are a number of X Windows functions. To fully understand how the functions work, you should examine the sample program, xhello.c, in section 8.7.7. The descriptions below are brief and provide just enough information so that you can understand how they work. For more detailed information it is highly recommended that you read the man pages. The X functions and Xlib are heavily documented in the man pages and provide very useful cross-referencing. When using the man pages, be sure to use the proper capitalization of function or data-type names. X SERVER CONNECTION

The following data-types and functions set up the X display , ie. a connection to an X server. X was designed to work over a network, so the display can be any X-capable display.

Display *p_disp

- Define a pointer to a structure of type Display which contains information about the X server .


- Specify a connection to the X server (the display name), as a string, in the standard hostname:#.# syntax (ex. dec5kj.cs.uregina.ca:0.0)

- Return a pointer of type Display. GRAPHICS CONTEXTS:

A Graphics Context (GC) must be specified as an argument for each Xlib function that draws in a window. Through the GC, you specify the attributes such as colour, font, patterns, and line styles. Like other X resources, you have to create a GC, initialize it, and then use it.

To create a GC, you should follow the following the procedure:

Display *p_disp;

Window my_window;

XGCValues XGCv;

unsigned long mask;

GC my_GC;

/* set values in XGCv and set up mask */


my_GC = XCreateGC(p_disp, my_window, mask, &XGCv);

where XGCv is a structure of XGCValues type which allows you to provide attribute values of your choice. Mask indicates the valid fields in the XGCv structure to be affected. Right now it is enough to be able to create a GC with default settings:

my_GC = XCreateGC(p_disp, my_window, 0, 0);

It should be noted that the second argument in the above example is of type Window. It can also be of type Pixmap, which is a 2D array of memory. Both represent a raster of pixels. One is on-screen (a Window) and the other is off-screen (a Pixmap). Both are called drawable.

After a GC is created, you can modify its attributes by calling the XChangeGC() function. The attribtes are same as for XCreateGC():

XChangeGC(p_disp, my_GC, mask, &XGCv);

Manipulating the contents of the GC is an advanced subject that is beyond the scope of this lab. For more information, interested users can find full descriptions of XGCValues in any X reference book. The layout of the structure can be found in the file <X11/Xlib.h>. It contains attributes such as font, foreground and background colours, clipping masks, tile origins, dashed-line masks, etc.

Because the GC is a resource, it consumes system resources such as memory in the X server. It should destroyed (freed up) when it is no longer needed:


The XSizeHints data structure is used to give hints to the X window manager as to how you want the X window to look and feel. Quite often this structure is passed as a function parameter. It is heavily used in most X Windows applications

typedef struct {

long flags;

int x,y; /* suggested position of window */

int width, height; /* suggested width and height */

int min_width, min_height; /* min. width and height */

int max_width, max_height; /* max. width and height */

int width_inc, height_inc; /* width & height increment */

struct {

int x; /* numerator of aspect ratio */

int y; /* denominator of aspect ratio */

} min_aspect, max_aspect; /* min. & max. aspect ratio */

} XSizeHints; /* hints for X window manager */ SPECIFICATION OF FONTS

XFontStruct *fontstr

- Define a pointer to the X font structure.

XLoadQueryFont(p_disp, font)

- Load a font by specifying the connection to the X server (p_disp) and set the font (font) by specifying a font name.

- Returns a pointer of type XFontStruct.

- NOTE: You can use 2 separate functions: XLoadFont and XQueryFont, but this function saves some typing effort. SPECIFICATIONS OF COLOURS

The following functions and data-types set up and use the colour map. Note the American spelling of "color", so take care when typing them in. The colour map is an array of some length that holds 3 values at each index. These are the RGB values that are the component values for a particular colour. Black is all 0's, white is all 255's (assuming a 256 colour display), and so on. If the size of the colour map is 256, such as a VGA, then the range of the the index (pixel value) is from 0 to 255. The contents in an entry are the RGB values to be displayed on the screen for the given index, and can be manipulated by the following functions.

Colormap c_map

- Define a structure of type Colormap.

XColor color_struct

- Define a structure that holds a red, green, and blue value for a particular colour. This can be copied to and from an entry in Colormap.

- Color_struct.pixel contains the exact colour you should use where you want to use your desired colour.

DefaultColormap(p_disp, screen)

- Some server may have more than one screen. The argument screen specifies the screen number. If the screen is replaced by the macro DefaultScreen(p_disp), the current X window (ie. screen 0) is specified.

- Returns a structure of type Colormap (the default colour map, that is) on the specified screen.

XParseColor(p_disp, c_map, color_value, &color_struct)

- It takes a string specification of a colour, ex: color_value = "pink", searches the colour map c_map, and returns the corresponding RGB values. If "pink" does not exist in c_map, it returns 0.

XAllocColor(p_disp, c_map, &color_struct)

- Allocates a (read-only) colour map entry in c_map corresponding to the closest RGB values supported by the hardware, and returns the pixel value (ie. colour map index) and the content in color_struct is replaced by the RGB values actually used. A 0 is returned if it failed.

WhitePixel(p_disp, screen) and BlackPixel(p_disp, screen)

- Assign the default white or black colours -- this is usually done if the desired colour cannot be used, for whatever reason. SPECIFICATION OF WINDOW GEOMETRY

The following assorted functions will help you determine the geometry of your X windows and of the objects within each window. They will also let you set the properties of given windows, such as display parameters, borders, colours, etc.

XTextWidth(fontstr, text, strlen(text))

- Accept a pointer to an array of chars (a string) and return the length of that string in pixels -- this length, for example, can be used to set the width value in a structure of type XTextWidth.

DisplayWidth(p_disp, screen) and

DisplayHeight(p_disp, screen)

- These two functions get the height and width of the screen -- it lets us get values of screen dimensions in pixels so that we could properly set the geometry of windows.

XGeometry(p_disp, screen, user_geometry, default_geometry,

border_width, width_factor, height_factor, xadd, yadd,

&(hint.x), &(hint.y), &(hint.width), &(hint.height));

- This is the main function that sets window geometry. Notice that it uses many of the hints that you specify with the XSizeHints structure. It also uses the parameters that you specified on the command line or via some resource (if you want to, that is).

- This function returns a bitmask that is used to set position and size. WINDOW CREATION

XCreateSimpleWindow(p_disp, parent_screen, x, y, width,

height, border_width, border_color, background_color)

- This function creates a window on the current display (p_disp). The window is a child of the parent_screen, has the specified location (x and y) of the window and its size (height and width). The last three parameters are the width of the border and the colours of the background and border.

- The function returns the identification of the window.

XSetStandardProperties(p_disp, window_id, window_name,

icon_name, icon_pixmap, argv, argc, &hints)

- Set a set of properties for the window manager.

XMapWindow(p_disp, window_id)

- Map the window to make it visible. X EVENTS

The event-driven X programming model relies on events for everything from maintaining the contents of a window to receiving mouse and keyboard input from the user. X clients rely on events to pace the program and to do the user's bidding. THE XEVENT UNION

The event-handling loop gets events from xlib by calling XNextEvent, which waits for the next event from the X server. It also sends any waiting protocol requests to the server. For a description of the types of events that can be trapped by XNextEvent, please refer to the man pages.

XNextEvent(p_disp, &theEvent );

where theEvent is an XEvent type. The structure theEvent provides the type of event and other relevant information, which may vary from event to event, that is used to describe the event. Because of the diversity of events in X, the information necessary to describe one class of events (say, mouse events) differs significantly from that needed by another (say, keyboard events). Thus a number of different data structures are used to describe the events. It is also desirable to have a common format to report all events. A C union of these data structures is used to hold the information. The header file <X11/Xlib.h> defines this union, named XEvent, as follows:

typedef union _XEvent {

int type;

XAnyEvent xnay;

XKeyEvent xkey;

XButtonEvent xbutton;

XMotionEvent xmotion;

XExposeEvent Xexpose;

XGraphicsExposeEvent xgraphicsexpose;


} /* refer to xlib.h or a book for a complete description */

There are a total of 33 types of different events which can be broadly grouped into seven categories:

1) Mouse Events

2) Keyboard Events

3) Expose Events

4) Colormap Notification Events (notice, American spelling)

5) Interclient Communication Events

6) Window State Notification Events

7) Window Structure Control Events SELECTING EVENTS

When you write an X application, you have to select the events that you want the application to receive from the server. You can select events for each window in the application. The selection is usually made by calling the XSelectInput function as follows:

Display *p_disp; /* connection to X server */

Window w_id; /* window ID */

unsigned long event_mask; /* bit pattern to select

events */


XSelectInput( p_disp, w_id, event_mask);

where w_id identifies the window for which events are being selected. The event_mask parameter, which consists of a set of bit patterns, indicates the events being selected. You can construct the mask by the bitwise-OR of selected masks defined in the <X11/X.h> header file. For example, you may decide that you want to receive expose and ButtonPress events for a window. In this case, the event_mask will be as follows:

event_mask = ExposureMask | ButtonPressMask;

where the Expose event (ExposureMask) allows a previously obscured window, or part of window, to become visible, and ButtonPress (ButtonPressMask) traps the event of pressing a mouse button in a window. Other events, and their corresponding masks are:

ButtonRelease ButtonReleaseMask - A mouse button is released with the pointer in a window.

EnterNotify EnterWindowMask - Mouse pointer enters a window.

LeaveNotify LeaveWindowMask - Mouse pointer leaves a window.

MotionNotify ButtonMotionMask - The mouse is moved after being stopped.

KeyPress KeyPressMask - A key is pressed (when window has focus).

KeyRelease KeyReleaseMask - A key is released (when window has focus).

FocusIn FocusChangeMask - Window receives input focus (all subsequent keyboard events will come to this window).

FocusOut FocusChangeMask - Window looses input focus.

There are many more events. If you are curious as to what they are, please take a look at an X windows book or check out the header files or man pages. XANYEVENT

The simplest event data structure is XAnyEvent, which is defined as follows:

typedef struct {

int type; /* the event type */

unsigned long serial; /* number of last processed

event */

Bool send_event;/* True = event is from

SendEvent */

Display *display; /* identifies the reporting server */

Window window; /* window requesting the event */

} XAnyEvent;

The fields appearing in the XAnyEvent structure happen to be the first five entries in every events data structure. The type tells you the kind of event being reported. The X server keeps track of all the events, so serial is the sequence number of the last processed event. The send_event lets you simulate an event by using the XSendEvent function. Display and Window identify the server and the window that receives the event respectively.


XNextEvent(theDisplay, &theEvent);

if(theEvent.Xany.window == theMain)


X applications always handle events from mouse input, keyboard input, or both. In a graphical user environment, it is essential to have good control over the types of mouse events that occur, since this is the way in which the user interacts with the system. Keyboard events are also important for X applications.

Most workstations have 3 button mice, but X supports up to 5 button mice. The buttons are usually numbered from left to right, but the XSetPointerMapping function can be used to change this ordering.

The pointer refers to the graphical indication of the mouse's position in the display screen. The cursor is the actual graphical object that determines the appearance of the pointer in the screen.

The X server keeps track of the pointer, but the programmer can position it anywhere using the XWarpPointer function to forcibly move the pointer to a specified location on the screen. Similarly, acceleration and threshold can be set by the programmer to alter how fast the pointer moves as you move the mouse beyond the threshold limit. The functions XGetPointerControl and XChangePointerControl are used to change these parameters. The program xset, for example, uses these functions to alter mouse parameters.

For more information on these events, refer to the man pages. XQUERYPOINTER

Sometimes your application may have to determine the current position of the mouse pointer. This can be done by using the XQueryPointer function in the following manner:

Display *theDisplay; /* Identify X server */

Window w, /* Window of interest */

root, child; /* for return values */

int root_x, root_y, /* position in root */

win_x, win_y; /* position in w's frame */

unsigned int keys_buttons; /* info on mouse buttons */

if(!XQueryPointer(theDisplay, w, &root, &child, &root_x,

&root_y, &win_x, &win_y, &keys_buttons))


/* pointer not on screen where window w is ... */


Here, w is the window in whose coordinate frame you want to find the pointer position. XQueryPointer returns 0 if the pointer is not on the screen where window w is displayed. In this case, in the root variable, it returns the ID of the root window on the screen where the pointer appears.

If successful, it proviedes the information in a set of variables. You have to decalare these varaibles and pass their addresses as arguments to the function. In root and child, you get back the window ID of the root window and any visible subwindow of w that contains the pointer. If the pointer is in w but not any of its child windows, w will be set to None. The coordinates of the poiner with respect to the root window are returned in root_x and root_y while win_x and win_y are the coordinates of w's frame.

The keys_buttons variable is a bit mask that indicates which mouse button is pressed as well as which modifier keys are pressed at that moment. To decipher this information you need to perform a bitwise-AND of keys_buttons with the bitmask names defined in <X11/X.h>. For example, to see whether Button1 is pressed, you might write:

if(keys_buttons & Button1Mask)


/* ... button 1 was pressed ... */


These two events provide support for mouse clicks and double-clicks. If you just want something to happen when a mouse button is pressed, you'd just use ButtonPress. However, if you are relying on double-clicks or you require that the mouse button be released before action is taken, you must use the ButtonRelease event as well.

Here is an example of using these events:

XEvent theEvent;



XNextEvent(theDisplay, &theEvent);



case ButtonPress:

/* handle each button differently ... */



case Button1:



case Button2:



case Button3:





/* handle other events .... */


} /* while */

More detailed information on button events can be extracted from the XButtonEvent data structure. For more information, please refer to the man pages. KEYBOARD EVENTS

The mouse is important for the GUI part of the interface but the keyboard is indispensable for text entry. Whether it is annotating a figure or typing in a program listing, a program has to handle keyboard input to get the job done.

Like the mouse button events, the X server generates a KeyPress event when a key is pressed and a KeyRelease event when the key is released. All keys, including modifier keys, generate events. Some implemenations of X do not properly handle the KeyRelease event, so it is not a good idea to use it.

The keyboard has to rely on the concept of focus to determine which window receives its output. Typically, the window that contains the mouse pointer is the one that has focus. It is this window that will receive keyboard events. There are methods of giving focus to another window even though the mouse pointer is nowhere near it.

X uses the Inter-Client Communication Conventions Manual (ICCCM) for determining focus. It is the de-facto standard for designating focus in X applications. The manner in which it is implemented in different window managers may vary, but because all the conventions are standardized, source code will be portable.

Application programs set input focus by setting the input field of the XWMHints structure to True before calling XSetWMHints. For Example:

XWMHints xwmh;

/* Tell the WM to put the window in its normal state and to transfer input focus to our window when the user does so. */

xwmh.flags = (InputHint | StateHint);

xwmh.input = True;

xwmh.initial_state = NormalState;

XSetWMHints(theDisplay, theMain, &xwmh); XKEYEVENT DATA STRUCTURE

Once selected, KeyPress and KeyRelease events are reported in an XKeyEvent data structure that is defined in <X11/Xlib.h>. Please refer to the man page to see how it is defined.

You can access the fields of XKeyEvent through the xkey member of the XEvent union. For example, if the event is received in a variable named theEvent of type XEvent, refer to the keycode after a KeyPress event as theEvent.xkey.keycode.

The two most important fields in this data structure are state and keycode. The state indicates the state (pressed or released) of the modifier keys. The keycode is an integer (ASCII 8 - 255) that uniquely identifies the key. You have to manually translate this code to into an ASCII character before using it.

The X server decides what keycode to generate for a specific physical key. Each key, including the modifiers, has a unique code. These codes may be different from computer to computer.

The first step involved is translating the keycode to a symbolic name known as keysym. All keys and combinations of keys have unique keysyms, which are constants defined in <X11/keysym.h>. For example, pressing "A" results in a keysym of XK_a but <shift>"A" results in XK_A.

The second step is to convert the keysym to an ASCII text string that you can use for displaying. For most keys, this would be a string with a single character, but function keys, especially programmable ones, may generate a multicharacter string.

The function XLookupString is used to perform these two actions. Here is an example:

#include <X11/keysym.h>


XEvent theEvent;

char xlat[20]; /* room for string */

int nchar = 20;

int count; /* chars returned */

KeySym key; /* keysym on return */

XComposeStatus cs; /* Compose key status */


XNextEvent(p_disp, &theEvent);




case KeyPress:


count= XLookupString(&theEvent, xlat, nchar, &key, &cs);

/* add null byte to make 'xlat' a valid C string */

xlat[count] = '\0';

/* print out translated string ... */

printf("Keypress %s on window %x, subwindow %x\n",

xlat, theEvent.xkey.window, theEvent.xkey.subwindow);

/* keysym may be used like this ... */

if(key == XK_Q || key == XK_q)


/* user pressed q key ... EXIT! */






/* other events ... */


After you create your windows and setup up the event-handling code, to complete your application you need to arrange to display text and graphics in the windows. The Xlib library includes a number of drawing functions which can be grouped into three very broad categories:

- draw objects such as lines, rectangles, and circles

- draw and manipulate text

- display and manipulate images BASIC DRAWING FUNCTIONS

Xlib includes a great number of functions to draw points, rectangles, arcs, and polygons. By varying the arguments to the arc drawing function, you can also draw circles or ellipses.

The first three arguments to all drawing functions are the same:

- a pointer to the display:

ex) Display *p_disp;

- the id of a drawable window (a Window or Pixmap):

ex) Window my_window;

- a GC:

The simplest of graphics operations is to draw a point in a window or pixmap. You can draw a single point at some given coordinates (x,y) or you can draw multiple points at once.

Display *p_disp; /* connection to X server */

Window this_window; /* the drawable window */

GC this_GC; /* Graphics Context for drawing */

int x, y; /* point to be drawn */

int nPoints = 10; /* # of points for multiple draw */

XPoint pt[10]; /* array of multiple (x,y) points */

The following function draws a single point. The pixel at the location is set to the foreground colour specified in the GC. Please look in the file <X11/Xlib.h> for a description of the XGCValues data structure.

XDrawPoint(p_disp, this_window, this_GC, x, y);

The next function draws multiple points all with a single X protocol request. All the points are drawin using the same GC. This is a convenient way to draw a field of pixels or a bitmap without having to send one X packet for each pixel (which is an incredible waste of bandwidth). It uses the structure, XPoint, defined in <X11/Xlib.h>:

typedef struct {

short x, y; /* x and y coordinates */

} XPoint;

XPoint *pt;

int nPoint

The array pt was declared of this type to store all our coordinates, and nPoint specifies the number of points.. This array is used in the following function:

XDrawPoints(p_disp, this_window, this_GC, pt, nPoint,


The last argument tells the server how to interpret the coordinates of the points in the array, pt. It can be any of the following constants:

CoordModeOrigin - The coordinates are relative to the origin of the window or pixmap.

CoordModePrevious - Each point is given in coordinates relative to the previous point displayed. The very first point is assumed to be relative to the origin of the window. DRAWING LINES
There are 3 line drawing functions in Xlib. The first two are very similar to each other. The third one is a simple variation of the XDrawPoints() function. The X server draws end-caps for the lines drawn with XDrawLine() and XDrawSegments(). The server also draws end-caps at the very beginning and very end of the XDrawLines() function. The value of the end-cap can be changed by modifying the cap_style in the XGCValues structure.

Display *p_disp;

Window this_window;

GC this_GC;

The following function draws a simple line from (x1, y1) to (x2, y2).

XDrawLine(p_disp, this_window, this_GC, x1, y1, x2, y2);

This next function make use of the following data structure, defined in <X11/Xlib.h>:

typedef struct {

short x1, y1; /* start of segment */

short x2, y2; /* end of segment */

} XSegment;

XSegment lines[]; /* the start/end coords for the lines */

int numsegs; /* the number of line segments */

The purpose of this function is to draw multiple (and possibly disjoint) line segments.

XDrawSegments(p_disp, this_window, this_GC, lines,


The third function, XDrawLines(), has the same format and arguments as the XDrawPoints() function and is called in exactly the same way. In this function, however, the points are connected with a line. DRAWING AND FILLING RECTANGLES
There are several functions to draw rectangles.

Display *p_disp;

Window this_window;

GC this_GC;

int x, y;

int width, height;

XRectangle rects[]; /* array of rectangles */

int nrect; /* number of rectangles */

The following function is used for drawing a rectangle where (x, y) are the coordinates of the upper left corner, and the width and height values specify the width and height of the window.

XDrawRectangle(p_disp, this_window, this_GC, x, y,

width, height);

The following function is used to draw multiple rectangles. It makes use of the following structure which is defined in <X11/Xlib.h>:

typedef struct {

short x, y; /* upper left corners */

unsigned short width, height;

} XRectangle;

XDrawRectangles(p_disp, this_window, this_GC,

rects, nrects);

To fill rectangles you use the two functions XFillRectangle() and XFillRectangles(). They are called exactly in the same manner as their non-filling counterparts. DRAWING ARCS, CIRCLES, AND ELLIPSES
X Windows handles arcs, circles, and ellipses in the same manner. Drawing an arc involves specifying a bounding rectangle which is the smallest rectangle that completely encloses the the ellipse to which the arc belongs.

Display *p_disp;

Window this_window;

GC this_GC;

int x, y;

int width, height;

int start_angle, end_angle;

XArc arcs[]; /* the arcs ... */

int numarcs; /* how many arcs in the array */

The unique parameters for drawing arcs are the angles, which are measured in units of 1/64'th of a degree. The first angle, start_angle, measures where the arc begins starting from 0 degrees (the three o'clock line). The end_angle is the angular extent of the arc.

XDrawArc(p_disp, this_window, this_GC, x, y, width,

height, start_angle, end_angle);

Drawing multiple arcs is done using the following structure, defined in <X11/Xlib.h>:

typedef struct {

short x, y;

unsigned short width, height;

short angle1, angle2;

} XArc;

XDrawArcs(p_disp, this_window, this_GC, arcs, numarcs);

Drawing filled arcs is done using the XFillArc() and XFillArcs() functions, which are identical to their non-filling counterparts. The filling is modified by the arc_mode attribute in the XGCValues structures. If it is set to ArcPieSclice, the arc is filled as a wedge. If it is set to ArcChord, the chord is filled in. DRAWING POLYGONS
A polygon is a figure enclosed by multiple lines. To draw the outline of a polygon use the XDrawLines() function. To fill a polygon, use the XFillPoloygon() function.

int shape; /* Convex, NonConvex, or Complex */

int mode; /* CoordModeOrigin or CoordModePrevious */

XPoint points[]; /* vertices of polygon */

int nPoints; /* how many vertices */

XFillPolygon(p_disp, this_window, this_GC, points, nPoints,

shape, mode); DRAWING TEXT

X displays can show text in varying sizes and shapes. The X server draws characters using a rectangular bitmap for each character. The term font refers to a collection of such rectanguler bitmaps. FONT NAMES
Names of fonts are constructed in a standardized way that allows anyone to determine the attributes of a font by looking at its name. The font name includes information about the fonts designer, typeface, weight, style, and size. Other information that may or may not be included is the character set encoding to be used with the font, inter-character spacing (monospaced or proportional), and the screen resolution in pixels per inch at which the font was designed to be used. For example, you can identify a 14 point Helvetica bold font with the following name:


manufacturer: adobe

typeface: helvetica

weight: bold

style: upright

size: normal

point size: 14

tenth point size: 140

screen res.: 75dpi x 75dpi

spacing: proportional ( m = monospaced)

avg. width: 82 pixels

encoding: iso8859-1 (aka ISO Latin-1)

The Xlib functions that accept font names have been designed to handle incomplete font names. You can use asterisks as wildcard characters for parts of the name that can be arbitrary. For example, if you want Adobe's 14 point Helvetica bold font, all you have to specify is:


The program xlsfonts can be used to display font names. For example, typing:

xlsfonts "*helvetica*bold*140*"

will result in a list of fonts that match the given pattern. Similarly, you can use the xfd program to view a font in an X window. FONT LOADING
Fonts are resources residing on the server. Applications must load a font before using it. When the server successfully loads a font, it returns a resource ID that the application will use to refer to that font. Once you know the name of a font, you can use the following function to load a font:

Display *p_disp;

char fontname[] = "*helvetica-bold-r*140*";

Font helvb14; /* resource id */

helvb14 = XLoadFont(p_disp, fontname);

XLoadFont() will return a 0 if it fails, otherwise it results in some font handle resource id. When successful, you can start using the 14 point Helvetica bold font by setting the font attribute of a GC. To do this, use the following function as follows:

XSetFont(p_disp, this_GC, helvb14);

Since fonts are resident on the server they are server-resident resources and should be released when they are no longer needed. The following function can be used to accomplish this task:

A font is merely a set of bitmaps. When writing text output, the X server draws each character by painting pixels in a rectangle matching the size of that characters bitmap. Because you have to specify where the server should start drawing the characters, you need to know the size of each character's bitmap (or the maximum size of a bitmap at the very least). Xlib includes functions that let us obtain this information.

Display *p_disp;

Font helvb14;

XFontStruct *info_helvb14;

info_helvb14 = XQueryFont(p_disp, helvb14);

The structure XFontStruct is defined in <X11/Xlib.h>. It contains information about the font characteristics and attributes. The function XFreeFont(p_disp, info_helvb14) can be used in place of XUnloadFont() to unload the font and free the memory allocated by Xlib for the XFontStructure.

Similary, instead of calling XLoadFont() followed by XQueryFont(), you can call the XLoadQueryFont() function to load a font and get information with a single request to the server in the following manner:

info_helvb14 = XLoadQueryFont(p_disp, fontname);

Like XLoadFont(), XLoadQueryFont() will return a 0 if it fails, otherwise it just fills the XFontStruct data structure. TEXT OUTPUT
Xlib provides 3 functions for text output. Each function draws a number of characters on a single line. The first 2 functions draw all the characters using the font specified in the GC which you provide as an argument:

char string[]; /* string to be displayed */

int nchars; /* number of characters in string */

XDrawString(p_disp, this_window, this_GC, x, y,

string, nchars);

XDrawImageString(p_disp, this_window, this_GC,

x, y, string, nchars);

XDrawString() simply displays the string using the foreground pixel value (from the GC) for all the pixels corresponding to a 1 in the character bitmaps. XDrawImageString(), on the other hand, fills the pixels corresponding to 0 in each character's bitmap with the GC's background colour and those corresponding to 1 with the foreground colour.

The third function, XDrawText() is used to draw a line of text in which multiple fonts are used. It uses the following data structure defined in <X11/Xlib.h>:

typedef struct {

char *chars; /* pointer to string to be drawn */

int nchars; /* number of chars in string */

int delta; /* distance from end of last string */

Font font; /* font to use (None means use GC's) */

} XTextItem;

XTextItem text_chunks[]; /* array of strings */

int nChunks; /* number of XTextItem structs */

/* initialize "text_chunks" ... */


XDrawText(p_disp, this_window, this_GC, x, y,

text_chunks, nChunks); TEXT POSITIONING
When drawing text, you have to decide where to position the string. Unlike a text display terminal, the X server does not automatically handle carriage returns or linefeeds. You have explicitly position each line of text and draw it by calling a specific text drawing function. To help you determine the proper position for each line, Xlib provides some functions.

XFontStruct *p_font;

char text[];

int text_width;

text_width = XTextWidth(p_font, text, strlen(text));

XTextWidth() simply returns the width of a string in pixels. This width can be used for horizontal placement. For vertical spacing, use the sum of the ascent and descent fields of the XFontStruct structure pointed to by p_font.


The mouse can be used to enter information into a system. The different kinds of interaction that the device is capable of may enter different kinds of information into the system. A mouse movement will provide a different kind of information than, say, a button press. Both kinds of information may be important, but it all depends on context.

Say you wanted to draw a line -- you would require a couple of pieces of information, the two most important of which are the start and end coordinates. In this context, that of line-drawing, you would enter some mode that would perform line drawing. The system would now interpret your commands and actions as related to entering information for drawing a line or lines.

There are a couple of different methods you could use for drawing lines. You can enter the start and end points through the keyboard and have the system draw the line. You can enter the line start and end coordinates using the mouse and then the system will draw the line. You can even enter the start coordinate and have the system display the line in "real-time" by using a rubber-band effect.

We would select the line-drawing technique we wish to use and then wait for particular events that would provide us with enough information to draw the line. Depending on the method we chose, this would be button presses, mouse movements, and possibly even key presses.

In the event handling loop of the main program, suppose a menu button called "line" is selected, the corresponding handling routine (which was a dummy part in the previous lab) will be called. Conceptually, the system enters the "line" mode. In this mode a sequence of actions described above will be performed in order. This is typically handled by a sub-event loop in your "line" button handling routine. Before we design the sub-event loop for entering a line, a brief look at the concept of the state diagram will be very helpful. (You might want to refer to section 8.2.2 in your text book, "Introduction to Computer Graphics", for a further description of the state diagram.)

In your "line" button handling routine, say we wanted a system that would wait for a button to pressed. The location of that button press would mark the start of our line segment (it doesn't matter if the user moves the mouse while still holding down the button). When the button is pressed a second time, this marks the end of our line segment and we draw the line. This can be represented using the following state diagram:

It is a good idea to create such state diagrams for all the events of an X application that you are creating. Since the event loop is usually nothing more than a finite state machine, it is pretty easy to create a state diagram. The above diagram shows us what happens when the mouse buttons are pressed and released. Simply by looking at the diagram we realize that we will be required to create a variable to count the number of times the button has been released.

The above state diagram can be translated into an event loop in a striaghtforward manner:

line = 0;

lineDone = FALSE;



XNextEvent(theDisplay, &theEvent);



case ButtonPress:

/* Get the coordinates of the current

* pointer location and save it. You

* might use XQueryPointer(). */

lineState ++;


case ButtonRelease:

if(lineState == 2)

lineDone = TRUE;




Once you've entered two points, you are ready to draw the line, which is the subject of the next lab.


The following sample program uses a number of the functions described above. It is a complete X client which makes use of the command line parameters and values specified in the .Xresources file. The source code actually provides examples of how the functions are used and it can also be used as a skeleton program for most of your X clients.

This source code is also available via ftp in the directory "pub/405/X/xhello.c". Compilation and usage instructions are given in the comments.



This is a "simple" X Windows application that opens an X window and

displays some text in it. A user defined button is also defined.

There are also a number of command line options that make use of

X Windows features (type "xhello -h"). This program is intended as

a sample that you could base other X programs on. Enjoy!

Oh, by the way, this code is copyright by Nabajyoti Barkakati. It's

from the text "X Window System Programming".


1) To compile the program simply type:

cc -o xhello xhello.c -lX11

2) On the DECstations, the Xwindows libraries are located in an alternate

library (not the default) so that only 1 copy has to exist rather than

16. To link in the proper libraries, compile with the following:

cc -o xhello xhello.c -lX11 -L/net/ULTRIX/X11R5/lib

3) X programs tend to be really quite huge. It is a good idea to run the

'strip' command on them after you've compiled/debugged it so that it

consume fewer resources. The strip command removes symbol table

information and other misc. data from your program. Check out the

man page for more info.


#include <stdio.h>

#include <string.h>

#include <X11/Xlib.h> /* X includes ... must be present */

#include <X11/Xutil.h>

/* macro stuff ... */

#define max(x,y) (((x) > (y)) ? (x) : (y))

/* constants ... */

#define DEFAULT_MAIN_TEXT "Hello, World"

#define DEFAULT_EXIT_TEXT "Exit"

#define DEFAULT_BGCOLOR "white"

#define DEFAULT_FGCOLOR "black"


#define DEFAULT_FONT "fixed"

/* Tie in command line parameters with the the resources ... */

typedef struct XHELLO_PARAMS {

char *name;

char **p_value_string;



Setup up a number of the configuration options for the X window

behaviour, like colour, geometry, etc.


char *mbgcolor = DEFAULT_BGCOLOR,

*mfgcolor = DEFAULT_FGCOLOR,

*mfont = DEFAULT_FONT,

*ebgcolor = DEFAULT_BGCOLOR,

*efgcolor = DEFAULT_FGCOLOR,

*efont = DEFAULT_FONT,

*mgeom_rsrc = NULL,

*mtext_rsrc = NULL,

*etext_rsrc = NULL,

*display_name = NULL,

*mtext_cline = NULL,

*etext_cline = NULL,

*mgeom_cline = NULL,



*mgeom = NULL;


These are the resources you can specify in your .Xdefaults file or

whatever resource file you use. Ex: xhello*background

These options could also be set via the command line. Check it out.

Currently, we are setting up the resources with the options given

in the above variable definitions.


XHELLO_PARAMS resources [] = {

"background" , &mbgcolor,

"foreground" , &mfgcolor,

"font" , &mfont,

"geometry" , &mgeom_rsrc,

"text" , &mtext_rsrc,

"exit.background" , &ebgcolor,

"exit.foreground" , &efgcolor,

"exit.font" , &efont,

"exit.exit" , &etext_rsrc


int num_resources = sizeof(resources) / sizeof(XHELLO_PARAMS);


Here there be the command line parameters. Check them out. Some are

kind of nifty.


XHELLO_PARAMS options[] = {

"-display" , &display_name,

"-d" , &display_name,

"-geometry" , &mgeom_cline,

"-g" , &mgeom_cline,

"-mtext" , &mtext_cline,

"-m" , &mtext_cline,

"-etext" , &etext_cline,

"-e" , &etext_cline


int num_options = sizeof(options) / sizeof(XHELLO_PARAMS);

char *app_name = "xhello";

XFontStruct *mfontstruct, *efontstruct;

unsigned long mbgpix, mfgpix, ebgpix, efgpix;

unsigned int ewidth, eheight;

int ex, ey, extxt, eytxt;

XWMHints xwmh;

XSizeHints xsh;

Display *p_disp;

Window Main, Exit;

GC theGC,


XEvent theEvent;

int Done = 0;

char default_geometry[80];

void usage(void);




main(int argc, char **argv)


int i,j;

char *tmpstr;

Colormap default_cmap;

XColor color;

int bitmask;

XGCValues gcv;

XSetWindowAttributes xswa;

app_name = argv[0];

/* Determine which command line arguments to use ... */

for (i=1; i<argc; i += 2) {

for (j=0; j<num_options; j++) {

if (strcmp(options[j].name, argv[i]) == 0) {

*options[j].p_value_string = argv[i+1];




if (j >= num_options)



/* Try to open the X display ... */

if ((p_disp = XOpenDisplay(display_name)) == NULL) {

fprintf(stderr, "%s: can't open display name %s\n",

argv[0], XDisplayName(display_name));



/* Determine which resources to use ... */

for (i=0; i<num_resources; i++) {

if ((tmpstr = XGetDefault(p_disp, app_name,

resources[i].name)) != NULL)

*resources[i].p_value_string = tmpstr;




* Now that we've got both the command line options _and_ the

* resources, we can begin configuring everything ...



/* Load the font for the text message ... */

if ((mfontstruct = XLoadQueryFont(p_disp, mfont)) == NULL) {

fprintf(stderr, "%s: display %s cannot load font %s\n",

app_name, DisplayString(p_disp), mfont);



/* Load the font for the exit message ... */

if ((efontstruct = XLoadQueryFont(p_disp, efont)) == NULL) {

fprintf(stderr, "%s: display %s cannot load font %s\n",

app_name, DisplayString(p_disp), efont);



default_cmap = DefaultColormap(p_disp, DefaultScreen(p_disp));

if (XParseColor(p_disp, default_cmap, mbgcolor, &color) == 0 ||

XAllocColor(p_disp, default_cmap, &color) == 0)

mbgpix = WhitePixel(p_disp, DefaultScreen(p_disp));


mbgpix = color.pixel;

if (XParseColor(p_disp, default_cmap, mfgcolor, &color) == 0 ||

XAllocColor(p_disp, default_cmap, &color) == 0)

mfgpix = BlackPixel(p_disp, DefaultScreen(p_disp));


mfgpix = color.pixel;

if (XParseColor(p_disp, default_cmap, ebgcolor, &color) == 0 ||

XAllocColor(p_disp, default_cmap, &color) == 0)

ebgpix = WhitePixel(p_disp, DefaultScreen(p_disp));


ebgpix = color.pixel;

if (XParseColor(p_disp, default_cmap, efgcolor, &color) == 0 ||

XAllocColor(p_disp, default_cmap, &color) == 0)

efgpix = WhitePixel(p_disp, DefaultScreen(p_disp));


efgpix = color.pixel;

if (etext_cline != NULL)

extext = etext_cline;


if (etext_rsrc != NULL)

extext = etext_rsrc;

if (mtext_cline != NULL)

mtext = mtext_cline;


if (mtext_rsrc != NULL)

mtext = mtext_rsrc;

extxt = efontstruct->max_bounds.width / 2;

eytxt = efontstruct->max_bounds.ascent +


ewidth = extxt + XTextWidth(efontstruct, extext, strlen(extext)) + 4;

eheight = eytxt + 4;

xsh.flags = (PPosition | PSize | PMinSize);

xsh.height = mfontstruct->max_bounds.ascent +

mfontstruct->max_bounds.descent + eheight + 10;

xsh.min_height = xsh.height;

xsh.width = XTextWidth(mfontstruct, mtext, strlen(mtext)) + 2;

xsh.width = max(xsh.width, ewidth);

xsh.min_width = xsh.width;

xsh.x = (DisplayWidth(p_disp, DefaultScreen(p_disp)) - xsh.width)/2;

xsh.y = (DisplayHeight(p_disp, DefaultScreen(p_disp)) - xsh.height)/2;

sprintf(default_geometry, "%dx%d+%d+%d", xsh.width, xsh.height, xsh.x, xsh.y);

mgeom = default_geometry;

if (mgeom_cline != NULL)

mgeom = mgeom_cline;


if (mgeom_rsrc != NULL)

mgeom = mgeom_rsrc;

bitmask = XGeometry(p_disp, DefaultScreen(p_disp), mgeom,

default_geometry, DEFAULT_BDWIDTH,


mfontstruct->max_bounds.ascent +


1, 1, &(xsh.x), &(xsh.y), &(xsh.width), &(xsh.height));

if (bitmask & (XValue | YValue)) xsh.flags |= USPosition;

if (bitmask & (WidthValue | HeightValue)) xsh.flags |= USSize;

Main = XCreateSimpleWindow(p_disp, DefaultRootWindow(p_disp),

xsh.x, xsh.y, xsh.width, xsh.height,

DEFAULT_BDWIDTH, mfgpix, mbgpix);

XSetStandardProperties(p_disp, Main, app_name, app_name,

None, argv, argc, &xsh);

xwmh.flags = (InputHint | StateHint);

xwmh.input = False;

xwmh.initial_state = NormalState;

XSetWMHints(p_disp, Main, &xwmh);

gcv.font = mfontstruct->fid;

gcv.foreground = mfgpix;

gcv.background = mbgpix;

theGC = XCreateGC(p_disp, Main, (GCFont | GCForeground | GCBackground), &gcv);

xswa.colormap = DefaultColormap(p_disp, DefaultScreen(p_disp));

xswa.bit_gravity = CenterGravity;

XChangeWindowAttributes(p_disp, Main, (CWColormap | CWBitGravity), &xswa);

XSelectInput(p_disp, Main, ExposureMask);

XMapWindow(p_disp, Main);

ex = 1;

ey = 1;

Exit = XCreateSimpleWindow(p_disp, Main, ex, ey, ewidth, eheight,

DEFAULT_BDWIDTH, efgpix, ebgpix);

XSelectInput(p_disp, Exit, ExposureMask | ButtonPressMask);

XMapWindow(p_disp, Exit);

gcv.font = efontstruct->fid;

gcv.foreground = efgpix;

gcv.background = ebgpix;

exitGC = XCreateGC(p_disp, Exit, (GCFont | GCForeground | GCBackground),


while (!Done) {

XNextEvent(p_disp, &theEvent);

if (theEvent.xany.window == Main) {

if (theEvent.type == Expose && theEvent.xexpose.count == 0) {

int x, y, itemp;

unsigned int width, height, utemp;

Window wtemp;

if (XGetGeometry(p_disp, Main, &wtemp, &itemp, &itemp, &width, &height,

&utemp, &utemp) == 0) break;

x = (width - XTextWidth(mfontstruct, mtext, strlen(mtext))) / 2;

y = eheight + (height - eheight + mfontstruct->max_bounds.ascent -

mfontstruct->max_bounds.descent) / 2;

XClearWindow(p_disp, Main);

XDrawString(p_disp, Main, theGC, x, y, mtext, strlen(mtext));



if (theEvent.xany.window == Exit) {

switch(theEvent.type) {

case Expose:

if (theEvent.xexpose.count == 0) {

XClearWindow(p_disp, Exit);

XDrawString(p_disp, Exit, exitGC, extxt, eytxt,

extext, strlen(extext));



case ButtonPress:

Done = 1;




XFreeGC(p_disp, theGC);

XFreeGC(p_disp, exitGC);

XDestroyWindow(p_disp, Main);




void usage(void)


fprintf(stderr, "usage: %s [-display host:display] \

[-geometry geom] [-mtext text], [-etext text]\n", app_name);