Home Page

  About Blind Linux Access

  Project Time Table

  Project Proposal

  First Report

  Second Report

  Final Report

  BlindPenguin Help

  External Links

  Suggestions/Bug Reports

  Download Source Code

BlindPenguin Project

First Report

penguin.gif


Table of Contents

ACKNOWLEDGEMENTS

DECLARATION

1 INTRODUCTION

1.1 WHAT IS BLINDPENGUIN?

1.2 DESIRED FEATURES OF BLINDPENGUIN

1.2.1 Management of Configuration Files

1.2.2 Changing Sizes

1.2.3 Changing Hot Keys

1.2.4 Setting Targets

1.2.5 Help Facilities

1.3 STRUCTURE OF THIS DOCUMENT

1.3.1 Background Concepts

1.3.2 Analysis and First Prototype Implementation

1.3.3 Summary of Work Done

1.3.4 Discussion

1.3.5 Conclusions

1.3.6 References

1.3.7 Appendices

2 BACKGROUND CONCEPTS

2.1 INTRODUCTION TO X WINDOWS

2.2 INTRODUCTION TO XLIB

2.3 WHAT IS TCL/TK

2.4 THE HISTORY OF TCL

2.5 THE HISTORY OF TK

3 ANALYSIS AND FIRST PROTOTYPE IMPLEMENTATION

3.1 REQUIREMENT GATHERING AND REFINEMENT

3.2 QUICK DESIGN

3.3 BUILD PROTOTYPE

4 SUMMARY OF WORK DONE

4.1 XLIB PROGRAMMING

4.1.1 Modifications Made to Xzoom

4.2 TCL PROGRAMMING

5 DISCUSSION

5.1 PROTOTYPING

5.1.1 Requirement Gathering and Refinement

5.1.2 Quick Design

5.1.3 Build Prototype

5.1.4 Evaluation

5.1.5 Refine Prototype

5.1.6 Engineer Package

5.2 BENEFITS OF THE PROTOTYPING METHODOLOGY

5.3 USES OF PROTOTYPING

6 CONCLUSIONS

7 REFERENCES

8 APPENDICES

8.1 APPENDIX A - SOURCE CODE FOR XZOOM

8.2 APPENDIX B - SOURCE CODE FOR BLINDPENGUIN INTERFACE


Acknowledgements

The BlindPenguin project would not have been possible without the help of a number of individuals and organisations. The team would like to thank the following for their help.

 

  • Mícheál Ó Foghlú for his work as Project Supervisor.
  • Itai Nahshon as the author of the original Xzoom program.
  • Christophe Tronche for putting the Xlib Manual on the Web.
  • Paul A Farrell for his web site on handling signals.
  • WIT Computer Society for the use of their Linux server snet.

 

<< Back to Contents


Declaration

This work is submitted in partial fulfillment of the requirements of the degree, BSc in Commercial Software Development. We declare that the work submitted is our own except where indicated in the text.

 

Kieran O' Sullivan Date:14 th January 2000

Catherine Teahan Date:14 th January 2000

<< Back to Contents


1 Introduction

The growth of Linux over the past few years has seen a large number of programs being developed for X Windows, which is a GUI (Graphical User Interface) for Linux. However there are still relatively few screen magnification programs available for disabled users of X Windows, those that are available are primitive and not very user friendly.

 

1.1 What is BlindPenguin?

The BlindPenguin is a screen magnification package for the X Windows environment. The idea is to produce an easily configured package on the same lines as some of the commercial screen magnification packages available for Windows 9X and NT. However since disabled access is a right, this program will be distributed free with the source code under GNU General Public Licence.

 

BlindPenguin is based on the source code of a program called Xzoom. This program performs magnification very well but it would be extremely difficult for a novice to configure. To make it easier for new users a Tcl interface has been written which allows most of the configuration to be done through a GUI. In addition some of the tracking features of Xzoom have been improved to allow better screen navigation.

 

1.2 Desired Features of BlindPenguin

The main features that will be incorporated into BlindPenguin when it is finished are:

 

1.2.1 Management of Configuration Files

A configuration file will control BlindPenguin. This file will exist in a users home directory. Alterations may be made to this configuration file through the user interface. When these changes have been saved a HUP signal will be sent to the Xzoom program that will cause Xzoom to re-read the configuration file and resize itself accordingly.

 

1.2.2 Changing Sizes

The default size may not be the best for a particular user. BlindPenguin allows users to choose from a list of horizontal and vertical size settings.

 

1.2.3 Changing Hot Keys

Hot Keys are keystrokes that can be used for navigation and switching between sizes. If a user does not like a particular key combination they have the ability to change it using the interface.

 

1.2.4 Setting Targets

Targets allow users to have sections of the screen permanently magnified and displayed. This feature is useful for displaying things such as on screen clock.

 

1.2.5 Help Facilities

The help feature will guide a user through any aspect of the interface.

 

1.3 Structure of this Document

 

1.3.1 Background Concepts

There are a number of concepts that may be new to people reading this document. These include X Windows, Xlib Programming and Tcl Programming. This section attempts to explain these concepts in a manner that most people will understand.

 

1.3.2 Analysis and First Prototype Implementation

This section explains the how the problems was analyzed and how the first prototype was developed.

 

1.3.3 Summary of Work Done

The modifications made to Xzoom and the work done for the Tcl interface are explained here. Source code examples are given to illustrate this work.

 

1.3.4 Discussion

A detailed explanation of the prototyping methodology used to develop BlindPenguin is given here. This includes a description of all the phases of the methodology and an explanation as to why prototyping was chosen.

 

1.3.5 Conclusions

A number of conclusions about the way the project is progressing and discoveries that the team made while developing BlindPenguin are discussed here.

 

1.3.6 References

A complete list of all the material that the team used to help develop the project.

 

1.3.7 Appendices

The complete source code for Xzoom and the BlindPenguin Tcl interface.

<< Back to Contents

 
2 Background Concepts

 

2.1 Introduction to X Windows

In September 1987 Massachusetts Institute of Technology, MIT, released the first version of X Windows commonly referred to as X11. X Windows was the designed to provide Graphical User Interaction with a Unix or Unix like system on workstations. The fact that X11 was designed for workstations ensured that from the very start networking would be fundamental to the environment.

 

Networking is key to the X Window philosophy, all applications, which are written for this GUI, must be capable of running from any machine on the network. As well as running in a transparent manner, i.e. the user should not have to know that they are running on a network, the X Windows environment must be able to handle issues such as a slow network, communications failures between the X server and applications running on it and perhaps most importantly the X Windows system should be able to provide a user friendly interface through consistent graphical metaphors.

 

These metaphors are provided using bitmap technology. In bitmapped graphics each pixel or dot on the screen corresponds to one or more bits in memory. A feature of the way that X Windows displays graphics, is that it can have a number of virtual screens, which allow a user to have data displayed on their station without it actually being visible. When they want to see the data they simply use a keystroke or mouse click to activate the virtual screen that they are interested in.

 

Virtual screens as well as all the applications are controlled by a window manager. The window manager decides where everything is put and it allows users to launch programs. The manager must also allow users to resize, move or kill windows as they wish. All this is covered by a set of rules that have been predefined by the programmer who wrote the window manager. For the most part programmers and users do not really have to know these rules as they would be too complicated for users, and programmers should not depend on having a particular window manager. A program written for X Windows should run on any window manager.

 

2.2 Introduction to Xlib

As part of the X Windows system Xlib was developed to allow the system to be programmed relatively easily. Xlib is a collection of C functions. These functions communicate directly with the X server to allow a program to do whatever it needs to do. The main principle behind X Windows was portability and this is implemented in all the Xlib functions.

 

Applications written using the Xlib functions should compile and run on any machine that has the libraries installed. One of the strongest portability features of Xlib is that it uses its own protocol when sending requests over the network to the X server. Using this protocol the client generates a request and the server then performs some action or sends some data. The server may perform an action and not send back any information. This increases network performance. An example of information being sent to the server from a client is when an event occurs such as a window being closed down.

 

Xlib calls tend to be very low level to increase the efficiency of programs. It is necessary to develop higher level functions which are more transparent. The most well known toolkit is Tcl/Tk which was written for the Tcl language. It allows a programmer to create all the standard GUI objects using a very small amount of code. These functions deal with the Xlib low level functions so there is no need for the programmer to worry about them. This method is used for most programs but in some cases where efficiency is an issue it is better to use the low level Xlib function calls.

 

2.3 What is Tcl/Tk

Tcl stands for ''tool command language'' and is pronounced ''tickle.'' Tcl is actually two things: a language and a library. First, Tcl is a simple textual language, intended primarily for issuing commands to interactive programs such as text editors, debuggers, illustrators, and shells. It has a simple syntax and is also programmable, so Tcl users can write command procedures to provide more powerful commands than those in the built-in set.

 

Second, Tcl is a library package that can be embedded in application programs. The Tcl library consists of a parser for the Tcl language, routines to implement the Tcl built-in commands, and procedures that allow each application to extend Tcl with additional commands specific to that application. The application program generates Tcl commands and passes them to the Tcl parser for execution. When the Tcl library receives commands it parses them into component fields and executes built-in commands directly. For commands implemented by the application, Tcl calls back to the application to execute the commands.

 

An application program gains several advantages by using Tcl for its command language. First, Tcl provides a standard syntax: once users know Tcl, they will be able to issue commands easily to any Tcl-based application. Second, Tcl provides programmability. All a Tcl application needs to do is to implement a few application-specific low-level commands. Tcl provides many utility commands plus a general programming interface for building up complex command procedures. By using Tcl, applications need not re-implement these features. Third, extensions to Tcl, such as the Tk toolkit, provide mechanisms for communicating between applications by sending Tcl commands back and forth. The common Tcl language framework makes it easier for applications to communicate with one another.

 

Tcl was designed with the philosophy that one should actually use two or more languages when designing large software systems. One for manipulating complex internal data structures, or where performance is key, and another, such as Tcl, for writing smallish scripts that tie together the other pieces, providing hooks for the user to extend. For the Tcl script writer, ease of learning, ease of programming and ease of gluing are more important than performance or facilities for complex data structures and algorithms.

 

Tk is an extension to Tcl which provides the programmer with an interface to the X11 windowing system.

 

2.4 The History of Tcl

The Tcl scripting language grew out of the work of John Ousterhout on design tools for integrated circuits at the University of California at Berkeley in the early 1980's. He had written several interactive tools for IC design, such as Magic and Crystal. Each tool needed to have a command language (in those days people tended to invoke tools by typing commands; graphical user interfaces weren't yet in widespread use). However, his primary interest was in the tools, not their command languages. He didn't invest much effort in the command languages and the languages ended up being weak.

 

In the fall of 1987, he got the idea of building an embeddable command language. The idea was to spend extra effort to create a good interpreted language, and furthermore to build it as a library package that could be reused in many different applications. The language interpreter would provide a set of relatively generic facilities, such as variables, control structures, and procedures. Each application that used the language would add its own features into the language as extensions, so that the language could be used to control the application. The name Tcl (Tool Command Language) derived from this intended usage.

 

2.5 The History of Tk

One of his other interests at that time was graphical user interfaces. As GUIs became more and more popular in the 1980s he had noticed that the complexity of interactive software was rising rapidly. The most interesting new developments seemed to require large projects with enormous investments. As a professor with modest resources, this worried him.

 

He concluded that the only hope was to reduce the resource requirements by building large systems out of reusable components. If most of the complexity of a system was in the components, it would take quite a bit of work to develop the components, but this could be done gradually over time, perhaps by several smaller groups working together.

 

He also reasoned that component-based design would not work unless there was a powerful and flexible mechanism for integrating the components. These thoughts occurred shortly after he had begun thinking about Tcl, and it occurred to him that an embeddable command language such as Tcl might also be useful as a scripting language for integrating components. He decided to test this theory by creating a set of GUI components as a Tcl extension and using Tcl to assemble the components into graphical user interfaces. This extension became Tk.

 

<< Back to Contents


3 Analysis and First Prototype Implementation

 

3.1 Requirement Gathering and Refinement

The initial step in this stage involved the researching of all the requirements that would be needed to complete this project. Many meetings were held where both team members discussed what they wanted to do and how they were going to do it. The X Windows system was researched in dept by the use of books and the internet, especially in the area of screen magnification. The team found some primitive versions of screen magnification programs that were written for X Windows. This provided a starting point for the team and the code from these programs was studied. The team then decided on how they could improve these programs to make them more user friendly and useable. Based on this the requirements for the system are as following:

 

· Develop an X Windows screen magnification system which is user friendly, easy to install and configurable by end users.

· Develop a user friendly interface.

· The user can easily resize the screen to their preferred settings.

· The user can change the colour of the screen.

· The user can save their personal settings so that they do not have to change them each time that they load up the system.

· There will be tracking of both the mouse movements and keystrokes.

· There will be access to online help to using the system.

· This help will be easily understood.

 

From these requirements the programming languages that would be used was decided. Tcl/Tk was chosen to program the graphical user interface and C was chosen to program the screen magnification system.

 

3.2 Quick Design

After the requirements were decided upon the next stage for the team was to draw up a preliminary design of the system to aid in the building of the prototype. This drawing was designed to meet all the proposed requirements of the system and to make the graphical user interface as user friendly and accessible as possible. This design involves a simple menu bar with three menus, File, Options and Help respectively. Each of these menus have submenus which appear when then are clicked on by the mouse.

 

The File menu contains the following:

  • Load Config
  • Save Config
  • Save As…
  • Exit

 

The Options menu contains the following:

  • Set Size
  • Set Colour
  • Set Target
  • Hotkeys

 

The Help menu contains the following:

  • Contents
  • About

 

From these menus the user can set their personal settings and save them so that they do not have to change their settings each time that they load up the system.

 

3.3 Build Prototype

After the team drew up the initial design of the system they started work on their first prototype. Using this design, programming in Tcl/Tk began so as to develop the graphical user interface. This was a slow process, as no previous knowledge of Tcl/Tk was known by either of the team members. At the same time programming in X Windows began. Using the code that was available from other X Windows screen magnification systems the team was able to elaborate on this and began changing this code to suit the needs of BlindPenguin. The team is still working on this prototype and is planning to have it available in time for the second report.

<< Back to Contents


4 Summary of Work Done

 

4.1 Xlib Programming

When the team first decided to use Xzoom as a basis for their project, they realised that they would have to learn how to program using the Xlib low-level functions as this was the way in which Xzoom was written. This knowledge was acquired from three main sources, The Xlib Programmers Manual (Nye, 1990), the web (Tronche, 1997) and source code from a number of X Windows programs.

 

4.1.1 Modifications Made to Xzoom

Xzoom is a small program, which provides an extremely efficient screen magnification algorithms. However it does not track the mouse very well and it has a tendency to magnify itself which leads to a recursive magnification of the screen until all the user can see is a few pixels. For these reasons it was decided that some modifications would have to be made to the source code to allow for better tracking of the pointer.

 

This was achieved by getting Xzoom to listen for PointerMotion events. When the pointer moves within the Xzoom window then Xzoom is aware of its motion. The code to get Xzoom to listen to PointerMotion events is as follows.

 

xswa.event_mask =

ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|PointerMotionMask;

 

Once Xzoom was listening for pointer motion events the next step was to get it to scroll the screen relative to the pointers movement. This was achieved by adding another case statement to those already processed by the event loop. An offset was used to generate a relative position for the mouse so that Xzoom did not magnify itself. The code to do this is as follows.

 

case MotionNotify:

 

xgrab = event.xmotion.x_root - width[SRC];

ygrab = event.xmotion.y_root; - height[SRC]/8;

XDefineCursor(dpy, win, when_button);

break;

 

When a user changes the size and saves it to the configuration file using the BlindPenguin interface, a HUP signal is sent to the Xzoom telling it to re-read the configuration file. Functions called ReadConfig() and reset() had to be added to read a configuration file and handle signals, namely the HUP signal, which is sent by the BlindPenguin interface. Paul Farrell web site was very helpful with example C code showing how to catch HUP signals. The code for ReadConfig() and reset() functions is as follows.

 

void ReadConfig(void)

{

 

/*

This function was written by Kieran O' Sullivan and Catherine Teahan.

It is designed to read the config file when the program starts.

This function is also called by reset() when a HUP signal is

recieved from the Tcl interface to BlindPenguin.

*/

strcpy(filename,".BlindPenguin");

ConfigFile = fopen(filename,"r");

 

fscanf(ConfigFile,"%d\n%d", &magx, &magy);

 

fclose(ConfigFile);

 

}

 

void reset(int signum)

{

/*

This function was written by Kieran O' Sullivan and Catherine Tean

It is designed to re-read the config file if it recieves a HUP

signal from the Tcl interface to BlindPenguin.

*/

 

signal(SIGHUP, reset); /* This code stops the HUP signal from being

reset to its default behaviour i.e.

termination of program. Otherwise more

than one HUP signal would cause the

program to terminate */

 

ReadConfig();

 

resize(width[DST], height[DST]);

set_title = True;

}

 

The ReadConfig() function is called before any command line arguments are processed. If the user specifies a size at the command line this will over-write the configuration which was read from file. For example, if the configuration file set the magnification to four and the user used the command line to set it to eight then the magnification would be set to eight. This setting would not be written to the configuration file.

 

The program starts to listen to HUP signals just before it enters the event loop in main(). There would be no point listening for HUP signals before this point as the program would not have finished configuring itself.

 

Appendix A Contains the complete source code of Xzoom with all the changes clearly marked.

 

4.2 Tcl Programming

During the initial design of the graphical user interface, the team found it necessary that a scripting language should be used to program the interface. In general, scripting languages are easier and faster to code in than the more structured, compiled languages such as C and C++. Also scripting languages are sometimes considered good "glue" languages for tying several programs together. Tcl was chosen as the scripting language to be used.

 

Tcl was new to both team members so a learning process began. The college library had only a few books on the language so the team had to obtain knowledge from the internet as well. It was difficult to acquire basic beginners manuals on the internet so the team had to depend on the a standard Tcl book (Walch , 1997) for their information.

 

Tcl was found to be very like the C programming language, but less complicated. The learning process was slow as it was new to the team. It was found, however, that it required only one line of code to create a window. The code to create this window is:

 

#!/usr/X11/bin/wish

 

This is one of the many advantages of using Tcl; the programs require less lines of code than structured language programs.

 

The main objective for the project for the time being was to create a menu bar with three different menus: file, options and help respectively. These menus will allow the user to set their settings and to save their settings. This interface had to be as user friendly as possible as this was one of requirements of the system. As a result this initial interface has been created for BlindPenguin.

 

 

Screen Shot of BlindPenguin Interface

Figure 4.1 Interface for BlindPenguin

 

Above is a screen shot of the menu bar created for BlindPenguin. Each of the menus have sub menus which appear when clicked on.

 

 

Screen Shot of BlindPenguin Interface

Figure 4.2 File Menu

 

Above is a screen shot of the File menu and all the options available to the user in this menu. Exit is the only option that is working at the moment. When Exit is selected the program terminates. The code for Exit is as follows:

 

$m add command -label "Exit" -command exit

 

 

Screen Shot of BlindPenguin Interface

Figure 4.3 Options Menu

 

Above is a screen shot of the Options menu. This menu allows the user to select their preferred settings. The code to create the Options menu and the user settings is:

 

menubutton $f.opt -text Options -menu $f.opt.m

set l [menu $f.opt.m -tearoff 1]

 

$l add cascade -label "Set Size" -menu $m.opt

set m1 [menu $m.opt -tearoff 0]

 

$l add command -label "Set Colour"

$l add command -label "Set Target"

$l add separator

$l add command -label "Hot Keys"

 

The arrow after the set size option indicates that it is a cascaded menu. When this option is selected another submenu will appear with a number of different size options for the user to choose from.

 

 

Screen Shot of BlindPenguin Interface

Figure 4.4 Help Menu

 

Above is a screen shot of the Help menu. This is where the user will acquire any help that they will need when using the system. The About box contains the name of the system and the team members involved in the creation of this system. The code for the About box is as follows:

 

#The about box

proc about {} {

toplevel .about -borderwidth 3

wm title .about "About BlindPenguin"

 

wm geometry .about 250x150

message .about.people -width 12c -text "BlindPenguin was created by:

 

Catherine Teahan

&

Kieran O'Sullivan"

 

 

button .about.ok -text "OK" -command "destroy .about"

 

pack .about.people

pack .about.ok -padx 3m -pady 3m

}

 

Appendix B Contains the complete source code of the BlindPenguin interface.

 

<< Back to Contents


5 Discussion

 

5.1 Prototyping

BlindPenguin will be developed using a prototyping methodology. This methodology is an iterative process involving six stages:

1. Requirement Gathering and Refinement.

2. Quick Design.

3. Build Prototype.

4. Evaluation.

5. Refine Prototype.

6. Engineer Package.

Incremental prototyping approaches build a system in small steps or components, each of which can:

  • work on its own .
  • be coded, tested and implemented independently.

 

5.1.1 Requirement Gathering and Refinement

This stage involves researching the user needs and also the needs of the operating system and software been used. This information can be obtained through numerous discussions with the user and through the research of books written on the operating system and the software that is to be used. These requirements will then be modified or refined to meet the needs of the users.

 

5.1.2 Quick Design

Using the requirements, a preliminary design of the system will be drawn up to aid in the building of a prototype.

 

5.1.3 Build Prototype

After the design of the system is drawn up, development on the first prototype begins. This prototype does not have to meet all the specified requirements, as it is only the initial prototype.

 

5.1.4 Evaluation

In this stage the previous prototype will be tested to see how well it conforms to the requirements. The results will be evaluated and necessary changes will be made.

 

5.1.5 Refine Prototype

Following the evaluation, any modifications or refinements that need to be made to the prototype will be implemented.

 

5.1.6 Engineer Package

When the team is satisfied that all requirements have been met and implemented the final package is delivered.

 

5.2 Benefits of the Prototyping Methodology

 

The project team gains an early understanding of the system and becomes productive early.

  • Tangible progress is delivered quickly.
  • Participation by the functional user is well defined and is part of all project phases.
  • The system can be fine-tuned long before the end of the project.
  • Prototype tests act as visible milestones, providing checkpoints on the project team's progress.

 

5.3 Uses of Prototyping

  • The principle use is to help users and developers understand the requirements for the system.
  • It can be used for back to back testing.

<< Back to Contents


6 Conclusions

At present the project has met all the deadlines that the team have set and those deadlines set by the college. The use of the Prototyping methodology has proved very efficient both in terms of understanding the exact nature of the project and in terms of getting the work done.

 

The use of Tcl has also aided the process of prototyping as it has allowed for the rapid development of a user interface. Originally the team considered using low-level Xlib functions to achieve this, however this approach would not have enabled the rapid development of a user interface.

 

The next stage of the project will be the Evaluation of the work done. This stage will most likely result in the development of a more refined prototype which will incorporate more of the features mentioned in the introduction.

<< Back to Contents


 7 References

 

Farrell Paul A web site http://dune.mcs.kent.edu/~farrell/sys95/notes/examples/prog/signal/

This site contains information about Distributed Systems and IPC, including source code.

 

Tronche Christophe web site.

http://www.tronche.com/gui/x/

Contains the full Xlib Manual in HTML format.

 

 

Nye A 1990.

Xlib Programming Manual

ISBN 0-937175-11-0

O'Reilly & Associates, Inc., Sebastopol, CA 95472

 

Walch B 1997.

Practical Programming in Tcl and Tk Second Edition.

ISBN 0-L3-6L6830-2

Prentice Hall PTR, Upper Saddle River, NJ 07458.

<< Back to Contents


8 Appendices

 

8.1 Appendix A - Source Code for Xzoom

 

/* This program is distributed with no warranty.

 

Source files for this program may be distributed freely.

Modifications to this file are okay as long as:

a. This copyright notice and comment are preserved and

left at the top of the file.

b. The man page is fixed to reflect the change.

c. The author of this change adds his name and change

description to the list of changes below.

Executable files may be distributed with sources, or with

exact location where the source code can be obtained.

 

Changelist:

 

------ -----------

Itai Nahshon Version 0.1, Nov. 21 1995

Itai Nahshon Version 0.2, Apr. 17 1996

include <sys/types.h>

Use memmove() instead of memcopy()

Optional macro to replace call to usleep().

*/

 

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/signal.h>

 

#include <X11/Xlib.h>

#include <X11/Xatom.h>

#include <X11/Xutil.h>

 

 

#ifdef XSHM

#include <sys/ipc.h>

#include <sys/shm.h>

#include <X11/extensions/XShm.h>

#endif

 

#include <X11/cursorfont.h>

#include <X11/keysym.h>

 

/*

#ifdef TIMER

#include <sys/time.h>

#include <unistd.h>

#endif

*/

 

Display *dpy;

Screen *scr;

Window win;

 

GC gc;

/*

#ifdef FRAME

GC framegc;

#endif

*/

 

/*

#ifdef TIMER

Font font;

struct timeval old_time;

#endif

*/

 

Cursor when_button;

Cursor crosshair;

 

char *progname;

int set_title;

 

#define SRC 0 /* index for source image */

#define DST 1 /* index for dest image */

 

#define WIDTH 256 /* default width */

#define HEIGHT 256 /* default height */

 

#define MAG 2 /* default magnification */

#define MAGX MAG /* horizontal magnification */

#define MAGY MAG /* vertical magnification */

 

int xgrab, ygrab; /* where do we take the picture from */

 

int magx = MAGX;

int magy = MAGY;

 

int flipxy = False; /* flip x and y */

int flipx = False; /* flip display about y axis */

int flipy = False; /* flip display about x axiz */

 

int xzoom_flag = False; /* next mag change only to magx */

int yzoom_flag = False; /* next mag change only to magy */

 

int width[2] = { 0, WIDTH };

int height[2] = { 0, HEIGHT };

 

#ifdef XSHM

XShmSegmentInfo shminfo[2]; /* Segment info. */

#endif

XImage *ximage[2]; /* Ximage struct. */

 

int created_images = False;

 

#define NDELAYS 5

 

int delays[NDELAYS] = { 200000, 100000, 50000, 10000, 0 };

int delay_index = 0;

int delay = 200000; /* 0.2 second between updates */

 

/* Kieran was here */

 

FILE * ConfigFile;

char filename[13];

 

/* and stopped here */

 

void timeout_func(int signum) {

set_title = True;

}

 

/*

#ifdef FRAME

#define DRAW_FRAME() \

XDrawRectangle(dpy, RootWindowOfScreen(scr), framegc, xgrab, ygrab, width[SRC]-1, height[SRC]-1)

#endif

*/

 

void allocate_images(void) {

int i;

#ifndef XSHM

char *data;

#endif

 

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

 

#ifdef XSHM

ximage[i] = XShmCreateImage(dpy,

DefaultVisualOfScreen(scr),

DefaultDepthOfScreen(scr),

ZPixmap, NULL, &shminfo[i],

width[i], height[i]);

 

if(ximage[i] == NULL) {

perror("XShmCreateImage");

exit(-1);

}

 

shminfo[i].shmid = shmget(IPC_PRIVATE,

(unsigned int)(ximage[i]->bytes_per_line * ximage[i]->height),

IPC_CREAT | 0777);

 

if(shminfo[i].shmid < 0) {

perror("shmget");

exit(-1);

}

 

shminfo[i].shmaddr = (char *)shmat(shminfo[i].shmid, 0, 0);

 

if (shminfo[i].shmaddr == ((char *) -1)) {

perror("shmat");

exit(-1);

}

 

#ifdef DEBUG

fprintf(stderr, "new shared memory segment at 0x%08x size %d\n",

shminfo[i].shmaddr, ximage[i]->bytes_per_line * ximage[i]->height);

#endif

 

ximage[i]->data = shminfo[i].shmaddr;

shminfo[i].readOnly = False;

 

XShmAttach(dpy, &shminfo[i]);

XSync(dpy, False);

 

shmctl(shminfo[i].shmid, IPC_RMID, 0);

#else

data = malloc(width[i] * height[i]);

 

ximage[i] = XCreateImage(dpy,

DefaultVisualOfScreen(scr),

DefaultDepthOfScreen(scr),

ZPixmap, 0, data,

width[i], height[i], 8, width[i]);

 

if(ximage[i] == NULL) {

perror("XCreateImage");

exit(-1);

}

 

#endif XSHM

}

created_images = True;

}

 

void destroy_images(void) {

int i;

 

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

#ifdef XSHM

XShmDetach(dpy, &shminfo[i]); /* ask X11 to detach shared segment */

shmdt(shminfo[i].shmaddr); /* detach it ourselves */

#else

free(ximage[i]->data);

#endif

ximage[i]->data = NULL; /* remove refrence to that address */

XDestroyImage(ximage[i]); /* and destroy image */

}

}

 

void Usage(void) {

fprintf(stderr, "Usage: %s [ args ]\n"

"Command line args:\n"

"-display displayname\n"

"-mag magnification [ magnification ]\n"

"-geometry geometry\n"

"-source geometry\n"

"-x\n"

"-y\n"

"-xy\n\n"

"Window commands:\n"

"+: Zoom in\n"

"-: Zoom out\n"

"x: Flip right and left\n"

"y: Flip top and bottom\n"

"z: Rotate 90 degrees counter-clockwize\n"

"w: Next '+' or '-' only change width scaling\n"

"h: Next '+' or '-' only change height scaling\n"

"d: Change delay between frames\n"

"q: Quit\n"

"Arrow keys: Scroll in direction of arrow\n"

"Mouse button drag: Set top-left corner of viewed area\n",

progname);

exit(1);

}

 

/* resize is called with the dest size.

we call it then manification changes or when

actual window size is changed */

 

void resize(int new_width, int new_height)

{

if(created_images)

destroy_images(); /* we can get rid of these */

 

/* find new dimensions for source */

 

if(flipxy) {

height[SRC] = (new_width+magx-1) / magx;

width[SRC] = (new_height+magy-1) / magy;

printf("\nKieran: flibxy if statement in resize() was ture \n");

}

else {

width[SRC] = (new_width+magx-1) / magx;

height[SRC] = (new_height+magy-1) / magy;

}

 

if(width[SRC] > WidthOfScreen(scr))

width[SRC] = WidthOfScreen(scr);

 

if(height[SRC] > HeightOfScreen(scr))

height[SRC] = HeightOfScreen(scr);

 

/* temporary, the dest image may be larger than the

actual window */

if(flipxy) {

width[DST] = magx * height[SRC];

height[DST] = magy * width[SRC];

}

else {

width[DST] = magx * width[SRC];

height[DST] = magy * height[SRC];

}

 

allocate_images(); /* allocate new images */

 

/* remember actual window size */

if(width[DST] > new_width)

width[DST] = new_width;

if(height[DST] > new_height)

height[DST] = new_height;

 

}

 

void ReadConfig(void)

{

 

/*

This function was written by Kieran O' Sullivan and Catherine Teahan. It is designed to read the config file when the program starts.

This function is also called by reset() when a HUP signal is

recieved from the Tcl interface to BlindPenguin.

*/

 

 

strcpy(filename,".BlindPenguin");

ConfigFile = fopen(filename,"r");

 

fscanf(ConfigFile,"%d\n%d", &magx, &magy);

 

fclose(ConfigFile);

 

}

 

 

void reset(int signum)

{

/*

This function was written by Kieran O' Sullivan and Catherine Teahan

It is designed to re-read the config file if it recieves a HUP

signal from the Tcl interface to BlindPenguin.

*/

 

signal(SIGHUP, reset); /* This code stops the HUP signal from being

reset to its default behaviour i.e.

termination of program. Otherwise more

than one HUP signal would cause the

program to terminate */

 

ReadConfig();

 

resize(width[DST], height[DST]);

set_title = True;

}

 

 

 

int main(int argc, char **argv)

{

XSetWindowAttributes xswa;

int i, j, k;

char c;

char *p1, *p2;

XEvent event;

int buttonpressed = False;

int unmapped = True;

int scroll = 1;

char title[80];

XGCValues gcv;

char *dpyname = NULL;

int source_geom_mask = NoValue,

dest_geom_mask = NoValue,

copy_from_src_mask;

int xpos = 0, ypos = 0;

 

 

progname = strrchr(argv[0], '/');

if(progname)

++progname;

else

progname = argv[0];

 

ReadConfig(); /* if there are no command line args then

the config file settings will be used. */

 

 

/* parse command line options */

while(--argc > 0) {

++argv;

 

if(argv[0][0] == '=') {

dest_geom_mask = XParseGeometry(argv[0],

&xpos, &ypos,

&width[DST], &height[DST]);

continue;

}

 

if(!strcmp(argv[0], "-mag")) {

++argv; --argc;

 

magx = argc > 0 ? atoi(argv[0]) : -1;

 

if(magx <= 0)

Usage();

 

 

magy = argc > 1 ? atoi(argv[1]) : -1;

 

if(magy <= 0)

magy = magx;

else {

++argv; --argc;

}

 

continue;

}

 

if(!strcmp(argv[0], "-x")) {

flipx = True;

continue;

}

 

if(!strcmp(argv[0], "-y")) {

flipy = True;

continue;

}

 

if(!strcmp(argv[0], "-z") ||

!strcmp(argv[0], "-xy")) {

flipxy = True;

continue;

}

 

if(!strcmp(argv[0], "-source")) {

++argv; --argc;

 

if(argc < 1)

Usage();

 

source_geom_mask = XParseGeometry(argv[0],

&xgrab, &ygrab,

&width[SRC], &height[SRC]);

 

continue;

}

 

if(!strcmp(argv[0], "-dest") ||

!strcmp(argv[0], "-geometry")) {

++argv; --argc;

 

if(argc < 1)

Usage();

 

dest_geom_mask = XParseGeometry(argv[0],

&xpos, &ypos,

&width[DST], &height[DST]);

 

continue;

}

 

if(!strcmp(argv[0], "-d") ||

!strcmp(argv[0], "-display")) {

 

++argv; --argc;

 

if(argc < 1)

Usage();

 

dpyname = argv[0];

continue;

}

 

if(!strcmp(argv[0], "-delay")) {

 

++argv; --argc;

 

if(argc < 1)

Usage();

 

if(sscanf(argv[0], "%u", &delay) != 1)

Usage();

 

delay *= 1000;

 

continue;

}

 

Usage();

}

/* open connection to X server */

 

if (!(dpy = XOpenDisplay(dpyname))) {

perror("Cannot open display");

exit(-1);

}

 

/* Now, see if we have to calculate width[DST] and height[DST]

from the SRC parameters */

 

copy_from_src_mask = NoValue;

 

if(source_geom_mask & WidthValue) {

if(flipxy) {

height[DST] = magy * width[SRC];

copy_from_src_mask |= HeightValue;

 

}

else {

width[DST] = magx * width[SRC];

copy_from_src_mask |= WidthValue;

}

}

 

if(source_geom_mask & HeightValue) {

if(flipxy) {

width[DST] = magx * height[SRC];

copy_from_src_mask |= WidthValue;

}

else {

height[DST] = magy * height[SRC];

copy_from_src_mask |= HeightValue;

}

}

 

if(copy_from_src_mask & dest_geom_mask) {

fprintf(stderr, "Conflicting dimensions between source and dest geometry\n");

Usage();

}

 

scr = DefaultScreenOfDisplay(dpy);

 

if(DefaultDepthOfScreen(scr) == 8) {

fprintf(stderr, "%s: can work only with 8 bits/pixel\n", progname);

exit(1);

}

 

if(source_geom_mask & XNegative)

xgrab += WidthOfScreen(scr);

 

if(source_geom_mask & YNegative)

ygrab += HeightOfScreen(scr);

 

if(dest_geom_mask & XNegative)

xpos += WidthOfScreen(scr);

 

if(source_geom_mask & YNegative)

ypos += HeightOfScreen(scr);

 

/* printf("=%dx%d+%d+%d\n", width[DST], height[DST], xpos, ypos); */

 

xswa.event_mask = ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|PointerMotionMask;

xswa.event_mask |= StructureNotifyMask; /* resize etc.. */

xswa.event_mask |= KeyPressMask|KeyReleaseMask; /* commands */

xswa.background_pixel = BlackPixelOfScreen(scr);

 

win = XCreateWindow(dpy, RootWindowOfScreen(scr),

xpos, ypos, width[DST], height[DST], 0,

DefaultDepthOfScreen(scr), InputOutput,

DefaultVisualOfScreen(scr),

CWEventMask | CWBackPixel, &xswa);

 

XChangeProperty(dpy, win, XA_WM_ICON_NAME, XA_STRING, 8,

PropModeReplace,

(unsigned char *)progname, strlen(progname));

 

/*

XChangeProperty(dpy, win, XA_WM_NAME, XA_STRING, 8,

PropModeReplace,

(unsigned char *)progname, strlen(progname));

*/

 

set_title = True;

 

XMapWindow(dpy, win);

 

gcv.plane_mask = AllPlanes;

gcv.subwindow_mode = IncludeInferiors;

gcv.function = GXcopy;

gcv.foreground = WhitePixelOfScreen(scr);

gcv.background = BlackPixelOfScreen(scr);

gc = XCreateGC(dpy, RootWindowOfScreen(scr),

GCFunction|GCPlaneMask|GCSubwindowMode|GCForeground|GCBackground,

&gcv);

 

/*

#ifdef FRAME

gcv.foreground = AllPlanes;

gcv.plane_mask = WhitePixelOfScreen(scr)^BlackPixelOfScreen(scr);

gcv.subwindow_mode = IncludeInferiors;

gcv.function = GXxor;

framegc = XCreateGC(dpy, RootWindowOfScreen(scr),

GCFunction|GCPlaneMask|GCSubwindowMode|GCForeground,

&gcv);

#endif

*/

 

/*

#ifdef TIMER

font = XLoadFont(dpy, "fixed");

#endif

*/

resize(width[DST], height[DST]);

/*

#ifdef FRAME

 

{

static char bitmap_data[] = { 0 };

static XColor col = { 0 };

Pixmap curs = XCreatePixmapFromBitmapData(dpy,

RootWindowOfScreen(scr), bitmap_data, 1, 1, 0, 0, 1);

 

when_button = XCreatePixmapCursor(dpy, curs, curs, &col, &col, 0, 0);

}

#else

when_button = XCreateFontCursor(dpy, XC_ul_angle);

#endif

*/

when_button = XCreateFontCursor(dpy, XC_ul_angle);

 

crosshair = XCreateFontCursor(dpy, XC_crosshair);

 

XDefineCursor(dpy, win, crosshair);

 

/* Kieran was here */

signal(SIGHUP, reset); /* This tells the program to run the

reset() function when it gets a HUP

signal. */

/* and finished here */

 

for(;;) {

 

while(unmapped?

(XWindowEvent(dpy, win, (long)-1, &event), 1):

XCheckWindowEvent(dpy, win, (long)-1, &event)) {

 

switch(event.type) {

case ConfigureNotify:

if(event.xconfigure.width != width[DST] ||

event.xconfigure.height != height[DST]) {

 

resize(event.xconfigure.width, event.xconfigure.height);

}

break;

case ReparentNotify:

break; /* what do we do with it? */

 

case MapNotify:

unmapped = False;

break;

 

case UnmapNotify:

unmapped = True;

break;

 

case KeyRelease:

switch(XKeycodeToKeysym(dpy, event.xkey.keycode, 0)) {

case XK_Control_L:

case XK_Control_R:

scroll = 1;

break;

}

break;

 

case KeyPress:

switch(XKeycodeToKeysym(dpy, event.xkey.keycode, 0)) {

case XK_Control_L:

case XK_Control_R:

scroll = 10;

break;

 

case '+':

case '=':

if(!yzoom_flag) ++magx;

if(!xzoom_flag) ++magy;

xzoom_flag = yzoom_flag = False;

resize(width[DST], height[DST]);

set_title = True;

break;

 

case '-':

if(!yzoom_flag) --magx;

if(!xzoom_flag) --magy;

xzoom_flag = yzoom_flag = False;

if(magx < 1) magx = 1;

if(magy < 1) magy = 1;

resize(width[DST], height[DST]);

set_title = True;

break;

 

case XK_Left:

if(flipxy)

if(flipx)

ygrab += scroll;

else

ygrab -= scroll;

else

if(flipx)

xgrab += scroll;

else

xgrab -= scroll;

break;

 

case XK_Right:

if(flipxy)

if(flipx)

ygrab -= scroll;

else

ygrab += scroll;

else

if(flipx)

xgrab -= scroll;

else

xgrab += scroll;

break;

 

case XK_Up:

if(flipxy)

if(flipy)

xgrab -= scroll;

else

xgrab += scroll;

else

if(flipy)

ygrab += scroll;

else

ygrab -= scroll;

break;

 

case XK_Down:

if(flipxy)

if(flipy)

xgrab += scroll;

else

xgrab -= scroll;

else

if(flipy)

ygrab -= scroll;

else

ygrab += scroll;

break;

 

case 'x':

flipx = !flipx;

set_title = True;

break;

 

case 'y':

flipy = !flipy;

set_title = True;

break;

 

case 'z':

if(flipx^flipy^flipxy) {

flipx = !flipx;

flipy = !flipy;

}

flipxy = !flipxy;

resize(width[DST], height[DST]);

set_title = True;

break;

 

case 'w':

xzoom_flag = True;

yzoom_flag = False;

break;

 

case 'h':

yzoom_flag = True;

xzoom_flag = False;

break;

 

case 'd':

if(++delay_index >= NDELAYS)

delay_index = 0;

delay = delays[delay_index];

sprintf(title, "delay = %d ms", delay/1000);

XChangeProperty(dpy, win, XA_WM_NAME, XA_STRING, 8,

PropModeReplace,

(unsigned char *)title, strlen(title));

signal(SIGALRM, timeout_func);

alarm(2);

break;

 

case 'q':

exit(0);

break;

}

 

break;

 

case ButtonPress:

/*

#ifdef FRAME

xgrab = event.xbutton.x_root - width[SRC]/2;

ygrab = event.xbutton.y_root - height[SRC]/2;

#else

xgrab = event.xbutton.x_root;

ygrab = event.xbutton.y_root;

#endif

*/

 

/*DONT WORRY ABOUT BUTTON PRESSES

xgrab = event.xbutton.x_root;

ygrab = event.xbutton.y_root;

XDefineCursor(dpy, win, when_button);

buttonpressed = True;

*/

break;

 

case ButtonRelease:

/* DONT WORRY ABOUT BUTTON RELEASES

xgrab = event.xbutton.x_root - width[SRC]/2;

ygrab = event.xbutton.y_root - height[SRC]/2;

XDefineCursor(dpy, win, crosshair);

buttonpressed = False;

*/

break;

 

case MotionNotify:

/* Kieran was here again */

xgrab = event.xmotion.x_root - width[SRC];

ygrab = event.xmotion.y_root; - height[SRC]/8;

XDefineCursor(dpy, win, when_button);

break;

/* and stopped here */

}

 

/* trying XShmGetImage when part of the rect is

not on the screen will fail LOUDLY..

we have to veryfy this after anything that may

may modified xgrab or ygrab or the size of

the source ximage */

 

if(xgrab < 0)

xgrab = 0;

 

if(xgrab > WidthOfScreen(scr)-width[SRC])

xgrab = WidthOfScreen(scr)-width[SRC];

 

if(ygrab < 0)

ygrab = 0;

 

if(ygrab > HeightOfScreen(scr)-height[SRC])

ygrab = HeightOfScreen(scr)-height[SRC];

}

 

#ifdef XSHM

XShmGetImage(dpy, RootWindowOfScreen(scr), ximage[SRC],

xgrab, ygrab, AllPlanes);

#else

XGetSubImage(dpy, RootWindowOfScreen(scr),

xgrab, ygrab, width[SRC], height[SRC], AllPlanes,

ZPixmap, ximage[SRC], 0, 0);

#endif

/*

#ifdef FRAME

if(buttonpressed) { /* show the frame

DRAW_FRAME();

XSync(dpy, False);

}

#endif

*/

 

/* copy scaled lines from src to dst */

for(j = flipxy?width[SRC]:height[SRC]; --j >= 0; ) {

/* p1 point to begining of scanline j*magy in DST */

p1 = &ximage[DST]->data[ximage[DST]->xoffset +

j*magy*ximage[DST]->bytes_per_line ];

/* p2 point to begining of scanline j in SRC */

/* if flipy then line height[SRC]-1-j */

p2 = &ximage[SRC]->data[ximage[SRC]->xoffset +

(flipy?(height[SRC]-1-j):j)*ximage[SRC]->bytes_per_line ];

 

if(flipxy) {

int p2step = ximage[SRC]->bytes_per_line;

p2 = &ximage[SRC]->data[ximage[SRC]->xoffset + (flipy?j:(width[SRC]-1-j))];

 

if(flipx) {

p2 += p2step * (height[SRC]-1);

p2step = -p2step;

}

 

for(i = height[SRC]; --i >= 0;) {

c = *p1++ = *p2;

p2 += p2step;

for(k = magx; --k > 0; )

*p1++ = c;

}

}

else if(flipx) {

p2 += width[SRC];

for(i = width[SRC]; --i >= 0;) {

c = *p1++ = *--p2;

for(k = magx; --k > 0; )

*p1++ = c;

}

}

else {

for(i = width[SRC]; --i >= 0;) {

c = *p1++ = *p2++;

for(k = magx; --k > 0; )

*p1++ = c;

}

}

 

/* p1 point to begining of scanline j*magy in DST */

p1 = &ximage[DST]->data[ximage[DST]->xoffset +

j*magy*ximage[DST]->bytes_per_line ];

/* p2 points to begining of next line */

p2 = p1 + ximage[DST]->bytes_per_line;

/* duplicate that line as needed */

for(k = magy; --k > 0; ) {

#ifdef BCOPY

bcopy(p1, p2, width[DST]);

#else

memmove(p2, p1, width[DST]);

#endif

p2 += ximage[DST]->bytes_per_line;

}

}

 

#ifdef XSHM

XShmPutImage(dpy, win, gc, ximage[DST], 0, 0, 0, 0, width[DST], height[DST], False);

#else

XPutImage(dpy, win, gc, ximage[DST], 0, 0, 0, 0, width[DST], height[DST]);

#endif

if(set_title) {

if(magx == magy && !flipx && !flipy && !flipxy)

sprintf(title, "%s x%d", progname, magx);

else

sprintf(title, "%s X %s%d%s Y %s%d",

progname,

flipx?"-":"", magx,

flipxy?" <=>":";",

flipy?"-":"", magy);

XChangeProperty(dpy, win, XA_WM_NAME, XA_STRING, 8,

PropModeReplace,

(unsigned char *)title, strlen(title));

set_title = False;

}

/*

#ifdef TIMER

{

struct timeval current_time;

double DT;

 

gettimeofday(&current_time, NULL);

DT = current_time.tv_sec - old_time.tv_sec;

DT += 1e-6*(current_time.tv_usec - old_time.tv_usec);

sprintf(title, "DT=%6.3f", DT);

XDrawString(dpy, win, gc, 20, 20, title, strlen(title));

old_time = current_time;

}

#endif

*/

XSync(dpy, 0);

 

#ifdef NO_USLEEP

#define usleep(_t) \

{ \

struct timeval timeout; \

timeout.tv_sec = 0; \

timeout.tv_usec = _t; \

select(0, NULL, NULL, NULL, &timeout); \

}

#endif

 

if(!buttonpressed && delay > 0)

usleep(delay);

/*

#ifdef FRAME

if(buttonpressed) /* erase the frame

DRAW_FRAME();

#endif

*/

}

}

 

 

<< Back to Contents


8.2 Appendix B - Source Code for BlindPenguin Interface

 

#!/usr/X11/bin/wish

 

wm minsize . 300 50

wm title . "BlindPenguin"

 

set f [frame .menubar]

pack $f -fill x

 

menubutton $f.file -text File -menu $f.file.m

set m [menu $f.file.m -tearoff 1]

 

menubutton $f.opt -text Options -menu $f.opt.m

set l [menu $f.opt.m -tearoff 1]

 

menubutton $f.help -text Help -menu $f.help.m

set p [menu $f.help.m -tearoff 1]

 

pack $f.file $f.opt $f.help -side left

 

$m add command -label "Load Config" -command {openfile}

$m add separator

$m add command -label "Save Config" -command savefile

$m add command -label "Save As.." -command {saveas}

$m add separator

$m add command -label "Exit" -command exit

 

$l add cascade -label "Set Size" -menu $m.opt

set m1 [menu $m.opt -tearoff 0]

$m1 add check -label "X 10" -variable foo -command {puts "foo = $foo"}

$m1 add check -label "X 20" -variable foo -command {puts "foo = $foo"}

$m1 add check -label "X 30" -variable foo -command {puts "foo = $foo"}

$m1 add check -label "X 40" -variable foo -command {puts "foo = $foo"}

$m1 add check -label "X 50" -variable foo -command {puts "foo = $foo"}

 

$l add command -label "Set Colour"

$l add command -label "Set Target"

$l add separator

$l add command -label "Hot Keys"

 

$p add command -label "Contents"

$p add separator

$p add command -label About -command about

 

 

#The about box

proc about {} {

toplevel .about -borderwidth 3

wm title .about "About BlindPenguin"

 

wm geometry .about 250x150

message .about.people -width 12c -text "BlindPenguin was created by:

 

Catherine Teahan

&

Kieran O'Sullivan"

 

 

button .about.ok -text "OK" -command "destroy .about"

 

pack .about.people

pack .about.ok -padx 3m -pady 3m

}

 

<< Back to Contents