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

Final Report

penguin.gif


Table of Contents

ACKNOWLEDGEMENTS

DECLARATION

1 INTRODUCTION

1.1 What is BlindPenguin?

1.2 Features of BlindPenguin

1.3 Structure of this Document

2 BACKGROUND CONCEPTS

2.1 Introduction to X Windows

2.2 Introduction to Xlib

2.3 What is Tcl/Tk

3 Final Evaluation of BlindPenguin

3.1 Evaluation of Xzoom

3.2 Evaluation of BlindPenguin Interface

4 ANALYSIS AND FINAL IMPLEMENTATION

4.1 Refinement of Second Prototype

4.2 Quick Design

4.3 Build Final Version

5 TESTING OF NEW FEATURES

5.1 TESTING OF NEW XZOOM FEATURES

5.2 PORTABILITY TESTING

5.3 TESTING OF BLINDPENGUIN INTERFACE

5.4 TESTING OF COMMUNICATIONS

6 SUMMARY OF WORK DONE

6.1 Modifications Made to Xzoom

6.2 Modifications Made to BlindPenguin Interface

6.3 User manual

7 DISCUSSION

7.1 Comparing BlindPenguin and Xzoom

7.2 X Windows and Linux Access Programs

8 CONCLUSIONS

8.1 Project Documentation Completion

8.2 Features Which Were Not Implemented

8.3 Constraints

9 REFERENCES

9.1 Web sites

9.2 Books

10 Appendix A Source Code for Xzoom  

11 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 fulfilment 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: 2nd May 2000

Catherine Teahan Date: 2nd May 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 License.

 

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 Features of BlindPenguin

The main features of BlindPenguin are as follows:

 

  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.
  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.
  3. Keyboard Control - A user may if they wish use the keyboard to set the size.
  4. Tracking User Activity - Users can use two main types of input either the keyboard or the mouse. BlindPenguin tracks both these types of input.
  5. Help Facilities - The help feature will guide a user through any aspect of the interface.

 

1.3 Structure of this Document

 

  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.
  2. Final Evaluation of BlindPenguin - This evaluates the functionality of the second prototype.
  3. Analysis and Final Prototype Implementation - This section explains how the problems were analysed and how the final prototype was developed.
  4. Testing of New Features - This section discusses the testing which was carried out on all the features that were added to Xzoom from the inception of the BlindPenguin project.
  5. Summary of Work Done - The modifications made to Xzoom and the work done for the Tcl interface is explained here. Source code examples are given to illustrate this work.
  6. Conclusions - A number of conclusions about the way the project is progressing and discoveries that the team made while developing BlindPenguin are discussed here.
  7. References - A complete list of all the material that the team used to help develop the project.
  8. 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 principal 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 that 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 scriptwriter, 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.3.1 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.3.2 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 Final Evaluation of BlindPenguin

 

3.1 Evaluation of Xzoom

Following the development of the second prototype of Xzoom an evaluation process was undertaken to determine the success of the prototype. The evaluation was carried out on all the additional functionality, which was added to the original source code.

 

3.1.1 Tracking of User Activity and Visibility

The original Xzoom did not automatically track user activity, the second prototype implemented a mouse tracking facility, which enabled the user to instantly observe where they were on the screen or magnify a part of the screen simply by moving the mouse to that area. This tracking feature was very successful. However tracking a pointing device as it moves across a screen does not provide much assistance when it comes to typing in text. A separate tracking feature had to be developed that was aware of keystrokes and could track their position. In addition the keystroke tracking feature could not interfere with a users ability to track the pointer as a user should be free to choose which input device they wished to use.

 

An equally important tracking issue for Xzoom was the ability of users to go to other virtual screens and still have access to the magnification facility. To date none of the prototypes had implemented this facility when a user went to another virtual screen the Xzoom window was no longer visible. Another problem with the second prototype was that the Xzoom window became obscured whenever another window was placed over it, this effectively meant that it was useless.

 

3.1.2 The Portability of Xzoom

As with most X Windows programs Xzoom came with an Imakefile, which contains a number of rules that are used to create a Makefile. However the addition of the new functionality meant that the original Imakefile would no longer work correctly. If the BlindPenguin was to be run on other versions of Linux and Unix then this problem had to be solved.

 

3.2 Evaluation of BlindPenguin Interface

The second prototype implemented a number of features. An evaluation was carried out on all the additional functionality, which was added to the BlindPenguin interface, to determine its success.

 

3.2.1 Saving Configuration Files

This feature was successfully implemented and it involved writing to files. It provides the user with the ability to save their personal settings. When the Save option is selected in the File menu, the settings are written to a ".BlindPenguin" file. This ".BlindPenguin" file is saved in the users home directory. This also allows for multiple users.

 

3.2.2 Sending HUP Signals & Reading Process ID Files

This feature allows for the communication between the BlindPenguin interface and the Xzoom code. It was implemented successfully with the use of a file called ".BlindPenguin.pid", which is in the users home directory.

 

3.2.3 Loading Configuration Files

This option allows the user to load a configuration file that already exists in the users home directory. When selected, a box containing an entry field and a scrollable list of files appear. However, for this prototype, the entry field option was the only one that was implemented, so when the user types in a file name and hits return that file is opened providing it exists in the users home directory. If it does not exist in this directory an error message is written to the screen to inform the user that the file does not exist.

 

<< Back to Contents


4 Analysis and Final Implementation

 

4.1 Refinement of Second Prototype

The results of the Evaluation demonstrated some of the problems with the second prototype. The shortcomings of the second prototype of BlindPenguin discussed in the evaluation were corrected and implemented in the final prototype. The team made the following refinements to BlindPenguin:

 

  1. Xzoom would track keystrokes.
  2. The Xzoom window would always be visible.
  3. It would be possible to install Xzoom on any Linux system.
  4. Help files would be provided to guide the users.

 

4.2 Quick Design

 

4.2.1 Keystroke Tracking

Any screen magnification package must track both the mouse and keystrokes. When a user begins to type they do not want to have to keep moving the mouse in order to see what they are typing. As discussed in the evaluation of the second prototype Xzoom did not track keystrokes. The first approach considered to solve this problem was to use the XQueryTree() function and the StructureNotify event. When the Xzoom program first started it would query the X server to see how many windows were open. It would then start listening to the KeyStroke events of these windows. When a new window was open the a StructureNotify event would be sent to Xzoom and it would start listening to the events of the window, which had just been created. It soon became apparent that this was an extremely inefficient way to get the events of other windows as a window manager creates in excess of 60 windows most of which are not even mapped and are used by the window manager for its own purposes. For these reasons another method of tracking keystrokes on other windows had to be implemented.

 

The second approach was to get the events of the focus window (the window that is active) and listen to its events. As there is only one focus window at any point in time this was a far more efficient method of monitoring other windows for keystrokes. This approach was the one implemented for keystroke tracking.

 

4.2.2 Keeping Xzoom Visible

A fundamental requirement of a screen magnification program is that it is visible at all times. To do this it was necessary to make two alterations to Xzoom. The first was the addition of a XRaiseWindow() function, which would ensure that if Xzoom got covered by another window it would raise itself so that it was on top of this window. The window which obscured Xzoom would remain the focus window Xzoom would simply cover part of it.

 

A second visibility problem was the fact that most window managers' implement what is known as a virtual desktop, which allows people to keep related programs on different screens. When a user changes to another virtual desktop they can no longer see the windows on the desktop which they just left. To overcome this problem there is a window manager property setting which makes windows sticky, that means that they are visible on all desktops.

 

4.2.3 Generic Installation

The team knew from the start that in order for Xzoom to be of any use to people it would have to be capable of being installed on any system. For this reason it was decided to use an Imakefile which could be used to generate a Makefile on the users machine.

 

4.2.4 Help Files

It was decided that the best format for help files was as HTML files that would be stored on the web. This decision was taken because most Linux users have access to the Internet and providing help in this manner also allows the project team to ensure that all help files are up-to-date.

 

4.3 Build Final Version

Building the final version of the BlindPenguin included testing of the previous features again to ensure that they worked with the new features. The new features also had to be tested to ensure that they worked. The final version was the hardest to implement because there was quality issues, which did not arise with the previous prototypes, which only had to work well enough to please the team.

 

Implementing the Keystroke tracking was the most technically difficult part of the project. There were also technical difficulties with ensuring that the Interface behaved in a manner consisting with the expectations of users.

 

The final task of the project was to create a useable help facility for the users. This was not a difficult task however it was time consuming and required the team to place themselves in the position of the users. Building the final version of BlindPenguin was a combination of technical work and documentation.

 

<< Back to Contents


5 Testing of New Features

This section discusses the testing which was carried out on all the features that were added to Xzoom from the inception of the BlindPenguin project. The tests were broken up into four areas, testing of the Xzoom modifications, portability testing, testing of the BlindPenguin Interface and testing of the communications between the interface and Xzoom. All the tests were carried out to determine if it was possible to break the features the team added.

 

5.1 Testing of New Xzoom Features

 

5.1.1 Testing of the Configuration and Process ID Files

These tests are also described in section 4.2.3 Possible File Errors of the second report. The most common problem with keeping essential files in the users home directory, is that of a user accidentally deleting these files. If a user happens to delete the configuration file and they run the Xzoom part of BlindPenguin, Xzoom will simply use the default settings. Deleting a process ID file has no consequences for Xzoom if the PID file is not present when Xzoom starts up it will simply re-create it.

 

5.1.2 Testing of Signal Handling

Sending signals to a process is a standard Unix procedure and there is very little that can go wrong as this is a very simple form of Inter-Process Communication. Continuous HUP signals were sent to ensure that Xzoom could handle them without being killed off. A modification had to be made to the signal handling feature to resolve conflicts between it and the timing signals which were being used to continuously query the pointer position.

 

5.1.3 Testing of Tracking Features

The pointer was moved to all four corners of the screen to ensure that no invalid screen locations were generated and to ensure that the section of the screen being magnified was the correct one. The pointer was also moved to another virtual console to ensure that this action did not generate invalid positions.

 

Keystroke tracking was tested by typing text into windows and also by typing text into some of the editors available on Linux such as edit. The cursor navigation keys and the backspace and return key were also tested. These tests proved that Xzoom followed the cursor reasonably well but there was a delay due to the speed of the laptop.

 

Virtual desktop tracking was tested simply by switching to different desktops and ensuring that Xzoom followed.

 

 

5.2 Portability Testing

The addition of an Imakefile means that Xzoom can be compiled on most Linux/Unix systems. This was tested by compiling it on emhain.wit.ie and on snet.wit.ie. However it was not possible to properly test Xzoom for portability as it does not run correctly over the X Windows emulator X-WinPro, which is available in the college.

 

 

5.3 Testing of BlindPenguin Interface

 

5.3.1 Testing the Load Config Option

Testing the Load Config option involved testing that the file that the user enters in the entry field is opened and also testing if the file that the user double clicks on in the list box is opened. To confirm that the correct file was opened, each time a file was selected or entered to be opened, the team checked to see if the contents of that particular file was written to the ".BlindPenguin" file. This option was tested extensively and did not fail once.

 

Another test in relation to the Load Config option, was to make sure that if a file that did not exist in the users home directory was entered to be opened, an error message would appear telling the user so. The team entered many bogus names and every time the error message appeared.

 

5.3.2 Testing the Save As Option

Testing the Save As option involved testing to see if the settings that the user set were saved when the user entered a file name in the entry field or selected a file name from the list box. To check this, the team opened up the file that the settings were saved to, to make sure that the magnification values were there. The ".BlindPenguin" file was also checked each time to see if the values were written to it. These tests passed each time.

 

A test had to be carried out as well to make sure that a warning message appeared each time the user is about to over write an existing file. This test also proved successful.

 

The final test that had to be carried out on the Save As option was a test to see that when a new file was entered to be saved, that this file would be added to the users home directory. The team tested this by checking the list box in both the Load Config and Save As option to check if the new file was there. This test was also successful.

 

 

5.4 Testing of Communications

 

5.4.1 Testing of Signal Handling

These tests were carried out to see if the BlindPenguin interface was capable of sending signals to the Xzoom program. These tests were carried out in the Second Report section 5.2. Sending signals from the BlindPenguin interface to the Xzoom program allows them to communicate. The programs reads the ".BlindPenguin.pid" file in the users home directory and sends a HUP signal to the process telling it to reread its configuration file.

 

5.4.2 Single User Mode

In order to ensure that BlindPenguin works properly it was necessary to boot Linux in single user mode and run the interface and Xzoom magnification. The tests proved that Xzoom and the Interface worked correctly in single user mode.

 

 

 

<< Back to Contents


6 Summary of Work Done

 

6.1 Modifications Made to Xzoom

 

6.1.1 Keystroke Tracking

The most difficult part of the project was getting Xzoom to track keystrokes that occurred on other windows the problem was mainly that there is no way of getting an actual cursor position in using X Windows. As stated in section 4 Quick Design. The approach taken was to get Xzoom to find the focus window and then simply listen to keystrokes for that window. When these keystrokes were caught Xzoom would move the magnification area to the right by a set pixel offset. When the left arrow, backspace or return keys were pressed then the magnified area would be moved to the left. If a user is at the bottom of a window and they have to press the down arrow key to see the next line of text they do not want the magnified area to move outside the window with the focus they want it to stay in the focus window. This necessitated getting the position of the focus window and its height and width when Xzoom gets to the bottom of a focus window it stops moving down. Similar constraints were implemented for the up, right and left arrow keys. As well as listening to the focus window Xzoom must also listen to its children if any. The code that implements keystroke tracking follows.

 

if (!unmapped) /* if the xzoom window is mapped */

{

XGetInputFocus(dpy, with_focas, ret);

}

 

if (*with_focas != *with_focas2)

{

XSelectInput(dpy, *with_focas, KeyPressMask|KeyReleaseMask);

 

if (!XGetWindowAttributes(dpy, *with_focas, &focas_attrib))

{

printf("\nCan't get window attributes.\n");

}

 

if (XGetWindowAttributes(dpy, *with_focas, &focas_attrib))

{

(void) XTranslateCoordinates (dpy, *with_focas,

focas_attrib.root,

-focas_attrib.border_width,

-focas_attrib.border_width,

&AbsUperFocasX, &AbsUperFocasY, &junkwin);

 

/* There is no need to get the attributes of the child as the child is contained within the focus window. */

 

} /* end if XGetWindowAttributes() */

 

if (!XQueryTree(dpy, *with_focas, &junkwin, &junkwin,

&focas_child, &nchildren))

{/* No children */}

 

else

{

 

while (child_i < nchildren)

{

XSelectInput(dpy, focas_child[child_i],

KeyPressMask|KeyReleaseMask);

child_i++;

} /* end while */

 

} /* end else */

 

*with_focas2 = *with_focas;

 

 

} /* end if (*with_focas != *with_focas2)*/

 

if (*with_focas != win)

{

if(XCheckWindowEvent(dpy, *with_focas,

KeyPressMask|KeyReleaseMask, &event))

{

switch(event.type)

{

case KeyPress:

 

break;

case KeyRelease:

 

/*

This code is based on the assumption that a user will move the pinter that they wish to type in and then they will not move it again until they wish to leave. The statements are designed to catch the navigation keys. The behaviour is based on standard text editors and is not as flexible as it should be. The position of the mouse when it enters the window is used as a starting point for the calculations of cursor positions.

To make the tracking a little more comfortable the magnified area only scrolls after 3 keystrokes.

*/

 

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

{

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

dx2 = dx;

}

 

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

{

 

case XK_Left:

 

if ( xgrab - 10 > AbsUperFocasX)

{

xgrab -= 10;

dx2 = dx;

}

 

if (xgrab < 0)

{

xgrab = 0;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_Right:

 

if ( xgrab + 10 < AbsUperFocasX +

focas_attrib.width )

{

xgrab += 10;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_Down:

if ( ygrab + 10 < AbsUperFocasY +

focas_attrib.height )

{

ygrab += 10;

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

break;

 

case XK_Up:

 

if ( ygrab - 10 > AbsUperFocasY)

{

ygrab -= 10;

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

if (ygrab < 0)

{

ygrab = 0;

dy2 = dy;

}

 

break;

 

case XK_Return:

if ( ygrab + 10 < AbsUperFocasY +

focas_attrib.height )

{

ygrab += 10;

xgrab = AbsUperFocasX + 10;

 

/* When Return is pressed the usual result is to start on a new line. AbsUperFocasX holds the position of the start of the line and 10 pixels is a reasonable offset to make things look a bit better.

*/

 

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_BackSpace:

 

if ( xgrab - 10 > AbsUperFocasX)

{

xgrab -= 8;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

xgrab += scroll;

break;

 

case XK_Tab:

 

if ( xgrab + 20 < AbsUperFocasX +

focas_attrib.width)

{

xgrab += 15;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

 

 

break;

 

default:

/* This catches ordinary keystrokes */

 

if ( xgrab + 10 < AbsUperFocasX +

focas_attrib.width)

{

if (key_strokes > 2)

{

xgrab += 8;

key_strokes = 0;

}

key_strokes++;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

} /* end switch(XKeycodeToKeysym(dpy,

event.xkey.keycode, 0)) */

 

break;

 

default:

break;

} /* end switch event.type */

} /* end if XCheckWindowEvent for focus */

 

for (child_i =0; child_i < (int) nchildren; child_i++)

{

if(XCheckWindowEvent(dpy, focas_child[child_i],

KeyPressMask|KeyReleaseMask,

&event))

{

switch(event.type)

{

case KeyPress:

break;

case KeyRelease:

 

/*

This code is based on the assumption that a user will move the pinter that they wish to type in and then they will not move it again until they wish to leave. The statements are designed to catch the navigation keys. The behaviour is based on standard text editors and is not as flexible as it should be. The position of the mouse when it enters the window is used as a starting point for the calculations of cursor positions.

*/

 

 

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

{

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

dx2 = dx;

}

 

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

{

 

case XK_Left:

 

if ( xgrab - 10 > AbsUperFocasX)

{

xgrab -= 10;

dx2 = dx;

}

 

if (xgrab < 0)

{

xgrab = 0;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_Right:

 

if ( xgrab + 10 < AbsUperFocasX +

focas_attrib.width)

{

xgrab += 10;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

break;

 

case XK_Down:

if ( ygrab + 10 < AbsUperFocasY +

focas_attrib.height )

{

ygrab += 10;

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

break;

 

case XK_Up:

 

if ( ygrab - 10 > AbsUperFocasY)

{

ygrab -= 10;

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

if (ygrab < 0)

{

ygrab = 0;

dy2 = dy;

}

 

 

break;

 

case XK_Return:

if ( ygrab + 10 < AbsUperFocasY +

focas_attrib.height)

{

ygrab += 10;

xgrab = AbsUperFocasX + 10;

 

/* When Return is pressed the usual result is to start on a new line. AbsUperFocasX holds the position of the start of the line and 10 pixels is a reasonable offset to make things look a bit better.

*/

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_BackSpace:

 

if ( xgrab - 10 > AbsUperFocasX)

{

xgrab -= 8;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

xgrab += scroll;

break;

 

case XK_Tab:

 

if ( xgrab + 20 < AbsUperFocasX +

focas_attrib.width)

{

xgrab += 15;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

 

 

break;

 

default:

/* This catches ordinary keystrokes */

if ( xgrab + 10 < AbsUperFocasX +

focas_attrib.width)

{

if (key_strokes > 2)

{

xgrab += 8;

key_strokes = 0;

}

key_strokes++;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

} /* end switch(XKeycodeToKeysym(dpy,

event.xkey.keycode, 0)) */

 

 

break;

 

 

default:

break;

} /* end switch */

} /* end if XCheckWindowEvent for child */

} /* end of for child_i */

} /* end if *with_focas != win */

 

 

6.1.2 Ensuring That Xzoom is Always Visible

To ensure that Xzoom was always visible it was necessary to do two things. The first was to ensure that if Xzoom was covered by another window it would raise itself to the top of the window stack (all windows in X can be stacked on top of other windows). The following function is called every time the GetAllEvents() function is executed. It was originally called only when VisibalityNotify events were sent to Xzoom but this proved unreleighable so it was placed in the GetAllEvents() function. The variables dpy and win represent the connection to the X Windows server and the Xzoom window respectively.

 

XRaiseWindow(dpy, win);

 

The second step that had to be taken to ensure that Xzoom was always visible was to make it sticky. This means that when a user moves to another virtual desktopthe Xzoom moves with them. This is a window manager specific setting and can only be set for the FVWM it was done by editing the .fvwmrc file and setting the Xzoom window to be sticky.

 

6.1.3 Portability Modifications

 

Essential to the success of BlindPenguin is portability as every Linux/Unix system will have slightly different configurations when it comes to the location of libraries, environment variable settings and directory structure it is not possible to write a script that is guaranteed to compile an application correctly for every system. To overcome this problem most programs come complete with a configuration script, which is run before the program is compiled. The standard for X Windows programs is that they come with an Imakefile.

 

The Imakefile for Xzoom was written using the xwininfo Imakefile (MIT) as a guideline and after consulting the Imakefile FAQ (http://www.primate.wisc.edu/software/imake-stuff/imake-faq.html. Issuing the command xmkmf in the same directory as the Imakefile will result in the generation of a Makefile which will be configured correctly for the local settings of the particular machine on which it was created.

 

Compiling the BlindPenguin is then simply a matter of typing make. The make program reads the Makefile and generates a binary image if there is none or if the source file is newer than the binary. Make install will install the BlindPenguin binary into the same directory as all the other X Windows applications. Make man will install the man file. The following is the rules used in the Imakefile the Makefile that is generated from this is seven hundred lines and is not shown.

 

# Imakefile for BlindPenguin magnification software

# Created by Kieran O' Sullivan and Catherine Teahan

 

XCOMM $Consortium: Imakefile,v 1.9 92/11/20 13:24:14 rws Exp $

DEPLIBS = $(DEPXMULIB) $(DEPXLIB)

LOCAL_LIBRARIES = $(XMULIB) $(XLIB)

SRCS = xzoom.c

OBJS = xzoom.o

 

ComplexProgramTarget(BlindPenguin)

 

6.2 Modifications Made to BlindPenguin Interface

 

6.2.1 Load Configuration File

For the second prototype this option was only half implemented, so to begin the third prototype this option was completely implemented. This involved allowing the user to open a file in their home directory from the scrollable list of files. This can be achieved by double clicking with the mouse on the file that the user wishes to open. The code for allowing the user to load a configuration file by double clicking on it is:

 

bind .fs.file.entry <Button> {openentry [.fs.file.entry get]}

 

In Tcl, the 'bind' command associates a Tcl command with an event, like in the case above, the <Button> causes the procedure openentry to be activated when the user double clicks on a file. When a file is selected, its contents are written to the ". BlindPenguin" file, in exactly the same way as when a file is opened by entering a file name in the entry field.

 

If the file the user enters in the entry box does not exist, a message box will appear telling the user so. In the second prototype, this was implemented so that an error message would be written to the screen, however for the purpose of this project an error message box was best. The message box has an OK button and when it is clicked it will bring the user back to the Load Config box so the user can make another choice. The code to implement the message box is:

 

proc loaderr {} {

toplevel .load -borderwidth 3

 

wm title .load "ERROR!"

wm geometry .load 130x150

 

message .load.err -justify left -text "The File You Are Trying To Open Does Not Exist. Please Try Again."

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

pack .load.err

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

}

 

6.2.2 Save As Option

The Save As option is implemented in a very similar way to the Load Config option. See Report 2 section 5.2. When the user selects the Save As option, a box appears containing an entry field and a box with a scrollable list of files. These files are the complete list of all the files in the users home directory. The user has the choice of entering the file they wish to save in the entry field and hitting return, or the user can double click on a file in the scrollable list of files. If the file selected or entered to open already exists in the users home directory, a message box will appear warning the user about this. This message box has two buttons, OK and Cancel respectively. If the user wants to continue and overwrite this file the OK button is clicked, and if the user does not wish to overwrite that file the Cancel button is clicked. The Cancel button will bring the user back to the Save As box to select or enter a different file. See figure 6.2.2.1 below. The code to implement this is:

 

proc save-entry {entry} {

 

global listent

set listent $entry

global env

global size

 

if [file exists $entry] {

if [file isfile $entry] {

 

{saveerror}

}

} elseif {[file exists $entry] != 1} {

if {[file isfile $entry] !=1} {

 

set mysize [open $entry w]

 

puts $mysize $size

puts $mysize $size

 

destroy .fs

}

savefile $size

}

}

The code to call the message box with the Save As file warning is:

 

proc saveerror {} {

destroy .fs

global size

toplevel .saveent -borderwidth 3

 

wm title .saveent "Warning!"

wm geometry .saveent 150x150

 

message .saveent.put -justify left -text "This file already exists.\

Do you want to replace the existing file?"

 

button .saveent.ok -text "OK" -command {savefile1 $size}

button .saveent.cancel -text "Cancel" -command "destroy .saveent"

 

pack .saveent.put

pack .saveent.ok .saveent.cancel -side left -pady 10 -padx 10

}

 

f61.gif

Figure 6.1 Save As Warning Message Box

 

Inside this procedure is a command called 'savefile1'. When the user clicks OK, this command is activated. It causes the program to save the settings by calling the save procedure. Therefore, the users settings will be saved to the name they selected.

 

However, if the file the user enters to be saved does not exist in the users home directory, BlindPenguin will create that file. This is implemented by:

 

set mysize [open $entry w]

 

puts $mysize $size

puts $mysize $size

 

The 'puts' command allows the users settings to be entered into this file, which is then saved.

 

 

6.2.3 Redesign of Options Menu

 

The options menu had to be redesigned because some of the features the team intended to incorporate could not be implemented in time. The options menu has been reduced to just one option: Set Size. The other three options Set Colour, Set Target and Hotkeys had to be removed. The options menu now looks like this:

 

 

f61.gif

Figure 6.2 Redesign of Options Menu

 

6.2.4 Help Menu

 

In the previous prototypes, the Help Menu had two options; Contents and About. In the final prototype the Help menu has been adjusted. It now contains two submenus Help Topics and About. The Help Topics contains a submenu with help features on each of the following:

 

  • File Menu
  • Options Menu
  • Magnification Help
  • System Requirements
  • Installation of BlindPenguin

 

To implement these new submenus, a new cascade menu had to be added to the Help Menu. The code for the new submenu is:

 

$p add cascade -label "Help Topics" -menu $p.3

set m3 [menu $p.3 -tearoff 1]

$m3 add cascade -label "General Help" -menu $p.3.1

set o [menu $p.3.1 -tearoff 1]

$m3 add separator

$o add command -label "System Requirements" -command {WebHelp}

$o add command -label "Installation" -command {WebHelp1}

$m3 add command -label "File Menu" -command {WebHelp2}

$m3 add command -label "Options Menu" -command {WebHelp3}

$m3 add command -label "Magnification Help" -command {WebHelp4}

$p add separator

$p add command -label About -command about

 

f61.gif

Figure 6.3 New Help Menu

 

Each of the above help files are written in hypertext mark up language (HTML). The following line is all Tcl needs to run these HTML files on Netscape:

 

exec sh -c "netscape -install http://…………"

 

The About menu has been reported in Report 1 section 4.2.

 

6.3 User manual

Any system, which wishes to be considered user friendly, must have a manual, which gives the user information on how to use the package. The team have provided an online help facility which serves as a user manual. A written manual would be of little use to a person who may not be able to read standard type face.

 

<< Back to Contents


7 Discussion

This section compares the level of access BlindPenguin offers to a partly sighted user of X Windows to the access offered by the original Xzoom program. The comparison is based on the five general principles of design laid down by Macaulay in his book Human Computer Interaction for Software Designers pages 50 - 54. Some of the other projects that are currently being developed to offer access to blind and partly sighted users of X Windows and Linux, are also detailed in this section of the report.

 

7.1 Comparing BlindPenguin and Xzoom

 

7.1.1 Naturalness

In the context of Human Computer Interaction (HCI), naturalness is taken to mean that a user should not have to alter the way that they work in order to use a particular piece of software. In the context of the BlindPenguin project this principle has been taken to mean that a user should be able to perform all the tasks that a fully sighted user could perform.

 

Xzoom, as it was originally written, did not provide the user with a natural interface. The only way to navigate the screen was by using the arrow keys. Whenever the cursor or pointer moved off the screen the user would have to stop what they were doing, switch to the Xzoom window and scroll around the screen until they found the correct position.

 

BlindPenguin tracks on screen activity and always ensures that the area being magnified is the area that the user is interested in. This was achieved by implementing features that tracked the mouse position and keystrokes. See Second Report section 5.1.3, and Final Report sections 4.2.1 and 6.1.1.

 

The BlindPenguin interface conforms to the principle of naturalness because it is designed in a way that is natural to both fully sighted and impaired users. It contains a simple and easy to use menu bar which has three menus. The user can interact with the menus by making selections with the mouse or by using the arrow keys and return keys to activate the menu options. See First Report section 3.2 and 4.2, and Second Report section 5.2.

 

7.1.2 Consistency

A program should behave in a consistent manner and have a consistent appearance. This principle states that a program should always behave in the same manner and it should have a consistent appearance relative to other programs that a user may come into contact with.

 

The principle of consistency is very hard to apply to Xzoom or to the modifications made to it. Consistency can be applied to the BlindPenguin Interface. The BlindPenguin interface does not change throughout the program and is consistent with other interfaces. The simple menu bar consists of three menus. The options in these menus are straightforward and consistent with other programs.

 

7.1.3 Non-Redundancy

Non-redundancy simply put means that a user should not have to enter data or default values, which can be set by a program and saved for future use.

 

Xzoom allowed a user to magnify the screen to any size they wished however it was not possible to save these settings once the user shut down the system. To remedy this the user either had to change the default setting in the source code and recompile the entire program.

 

BlindPenguin uses a configuration file where the users preferred size can be saved and is used every time the program is started. See First Report section(s) 1.2.1,4.1.1, and Second Report section(s) 3.2,4.2.1.

 

The interface also conforms to the principle of non-redundancy. In the Load Config and Save As options in the File Menu, there are two scrollable lists of files that contain a complete list of all the files in the users home directory. To open a file or to save a file as, the user can double click on the file name in the list of files. This prevents the user from having to memorise the files in their home directory. See Second Report section 5.2, and Final Report sections 6.2.1 and section 6.2.2.

 

7.1.4 Supportiveness

The principle of supportiveness is concerned with how helpful are the instructions given with a program (help files) and the feedback that is provided by the program.

 

The supportiveness of the original Xzoom program was very poor when it cane to feedback as previously mentioned in regards to tracking. However, a man file was provided with the program and a standard usage function prints out the correct arguments to use when activating Xzoom from the command line.

 

The BlindPenguin interface conforms to the principle of supportiveness by the addition of the Help Menu. The Help Menu contains help on each option in the BlindPenguin interface. It also includes help on the system requirements and installation of BlindPenguin. See Final Report section 6.2.4

 

7.1.5 Flexibility

A flexible system is one that caters for the different users who will be operating it. A truly flexible system should be capable of supporting both novice and expert users.

 

Xzoom as it was originally designed was not very flexible in terms of usage. It was not aware of who a user was or what they would require. The use of command line arguments to configure it excluded novice users.

 

The modifications made to Xzoom for the BlindPenguin project meant that it was aware of who a user was and it was capable of allowing every user to set and keep their own configurations. See First Report sections 1.2.1 and 4.1.1, and Second Report sections 3.2 and 4.2.1

 

The BlindPenguin interface supports the principle of flexibility because its' easy to use menus allow for all users, both novice and expert, to use the program.

 

7.2 X Windows and Linux Access Programs

 

7.2.1 SVGATextMode

This program is useful for improving the visibility of the normal text screen that Linux provides. The normal screen that Linux provides shows 80 characters across by 25 vertically. This can be changed (and the quality of those characters improved) using SVGATextMode. The program allows full access to the possible modes of an SVGA graphics card. For example, the text can be made larger so that only 50 by 15 characters appear on the screen. There is not any easy way to zoom in on sections of a screen, but the user can resize when needed. This program does not affect the screen size of X Windows. This program is used by Linux users with sight difficulties and is distributed with most Linux distributions. However it is not specifically designed to be used by users with a sight disability.

 

SVGATextMode can be downloaded at:

http://metalab.unc.edu/pub/micro/pc-stuff/Linux/utils/console/!INDEX.html.

 

It was developed by Marco Paganini.

 

7.2.2 Puff

This is specifically oriented towards visually impaired users. It provides such features as a box around the pointer, which makes it easier to locate. Other interesting features of puff are that, if correctly set up, it is able to select and magnify portions of the screen as they are updated. However, there seem to be interactions between puff and the window manager, which could make it difficult to use. Keystrokes tracking does not work very well in puff because of its dependency on the window manager.

 

Puff can be downloaded at:

http://snet.wit.ie/BlindPenguin/.

 

It was developed by Ken Chin-Purcell.

 

7.2.3 Emacspeak

Emacspeak is the software side of a speech interface to Linux. Any other character based program, such as a WWW browser, or telnet or another editor can potentially be used within emacspeak. Emacspeak is included within the Debian Linux distribution and is included as contributed software within the Slakware distribution. This means that it is available on many of the CDROM distributions of Linux. Emacspeak does not work in the X Windows environment it is limited to the console.

 

Emacspeak can be downloaded at: http://www.cs.cornell.edu/Info/People/raman/emacspeak/emacspeak.html.

 

It was developed by T. V. Raman.

 

7.2.4 BRLTTY

BRLTTY is a program for running a serial port Braille terminal. A Braille TTY is simply a device that represents the onscreen characters as Braille on a specialised output device. BRLTTY does not work for X Windows. To use it a Braille output device will be needed.

 

BRLTTY can be downloaded at: http://metalab.unc.edu/pub/Linux/system/access/!INDEX.html.

It was developed by Nikhil Nair.

 

The above mentioned packages demonstrate that there is an active blind Linux user community. X Windows is not very well covered by most of the solutions available for Linux users with sight problems. Most of the solutions available for X Windows are only stop gap measures, which provide assistance rather than full access. The information on the above mentioned packages was sourced from the Linux Access-HOWTO which was written by Michael De La Rue and can be downloaded at http://www.linuxdoc.org/HOWTO/Access-HOWTO.html. The document covers most of the Linux access issues not just those related to sight difficulties. The other source of information was the blind Linux users group http://leb.net/blinux/.

 

<< Back to Contents


8 Conclusions

 

8.1 Project Documentation Completion

The BlindPenguin project team have completed three reports each detailing the progress of the prototypes and final version. All deadlines with regards to the documentation were met. Each report was a description of a particular prototype and followed the principles of the prototyping methodology, which is detailed in the first report section 5.1.

 

8.2 Features Which Were Not Implemented

There were a few feature which the team proposed to implement but could not complete due to the constraints outlined below. These features were setting targets, changing colours and allowing users to set hotkeys.

 

8.3 Constraints

When developing the BlindPenguin the team encountered a number of constraints. The main constraints were time and access to the X Server software that was available in the college. This software was installed in all rooms but it only worked correctly in one room. This limited the productiveness of the team because this room was not always available due to classes and night courses time tabled in this room. The team had access to one laptop between the two members, this alleviated the problem some what but there was still times when only one member of the team could work on the project.

 

A second constraint was the lack of reading material available to he team, both in the areas of Xlib and Tcl/Tk programming. The college library only had two books on Tcl/Tk and one on Xlib, which were sometimes in use by other students.

 

The team consisted of two members, which was a constraint because one of the team members suffered from influenza during the year and was unable to work on the project for two weeks.

 

<< Back to Contents


9 References

 

9.1 Web sites

 

Imake Frequently Asked Questions, DuBois Paul,

http://www.primate.wisc.edu/software/imake-stuff/imake-faq.html, 1-5-1997

 

Linux Access-HOWTO De La Rue Michael,

http://www.linuxdoc.org/HOWTO/Access-HOWTO.html, 28-3-1997

 

Source Code for sending HUP signals, Farrell Paul A

http://dune.mcs.kent.edu/~farrell/sys95/notes/examples/prog/signal/

 

Contains the full Xlib Manual in HTML format, Tronche Christophe

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

 

This site contains some sample chapters from the book Tcl/Tk for Programmers, Zimmer J A.

http://www.mapfree.com/sbf/tcl/book/select/Html/Contents.html

 

9.2 Books

 

Nye A 1990.

Xlib Programming Manual

ISBN 0-937175-11-0

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

 

Macaulay A 1995.

Human Computer Interaction for Software Designers

ISBN 1-85032-177-9

International Thompson Publishing Inc., London, UK

 

Welch 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


10 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>

 

#include <X11/Intrinsic.h> /* for the XtAppContext stuff

Kieran O' Sullivan Catherine Teahan */

 

#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, root_win;

int root_scr; /* Used by BlindPenguin Needed for getting root window */

 

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 380 /* default width */

#define HEIGHT 156 /* 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[80];

char * HomeDir;

char * RCHomeDir;

char var[3];

 

 

/* PID File */

 

char PIDFileName[80];

FILE * PIDFile;

pid_t pid;

 

Window rep_root, rep_child;

// XtIntervalId *TimerID;

XtAppContext app_context;

XtPointer client_data;

XWindowAttributes win_attributes, focas_attrib;

unsigned long MiliSeconds = (unsigned long) 80;

int rep_rootx, rep_rooty, hup_sig;

unsigned int rep_mask;

int dx, dy;

int dx2, dy2; /* used to stop un-necessary

pointer queries. */

Window with_focas[];

Window with_focas2[];

Window *focas_child;

int AbsUperFocasX, AbsUperFocasY;

unsigned int nchildren;

int ret[];

int key_strokes = 0;

/* and stopped here */

 

/* these were all at the start of main */

XSetWindowAttributes xswa;

int i, j, k;

char c;

char *p1, *p2;

XEvent event;

XEvent * report2;

XEvent focus_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;

 

/* they stop 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 reset(int signum);

 

void ReadConfig();

 

static void GetAllEvents ();

 

static void GetAllEvents(XtPointer data, XtIntervalId *id )

{

 

/* XtPointer data, is not used it comes form client_data */

 

 

Window junkwin;

 

int junk, AbsUperX, AbsUperY;

int child_i = 0;

 

XRaiseWindow(dpy, win); /* raise window because the focus

window may be covering it. */

 

 

XQueryPointer (dpy, win, &rep_root, &rep_child,

&rep_rootx, &rep_rooty, &dx, &dy, &rep_mask);

 

 

if (!unmapped) /* if the xzoom window is mapped */

{

XGetInputFocus(dpy, with_focas, ret);

}

 

if (*with_focas != *with_focas2)

{

XSelectInput(dpy, *with_focas, KeyPressMask|KeyReleaseMask);

 

if (!XGetWindowAttributes(dpy, *with_focas, &focas_attrib))

{

printf("\nCan't get window attributes.\n");

}

 

if (XGetWindowAttributes(dpy, *with_focas, &focas_attrib))

{

(void) XTranslateCoordinates (dpy, *with_focas, focas_attrib.root,

-focas_attrib.border_width,

-focas_attrib.border_width,

&AbsUperFocasX, &AbsUperFocasY, &junkwin);

 

/* There is no need to get the attributes of the child as the chile is contained

within the focas window. */

 

} /* end if XGetWindowAttributes() */

 

if (!XQueryTree(dpy, *with_focas, &junkwin, &junkwin, &focas_child, &nchildren))

{/* No children */}

 

else

{

 

while (child_i < nchildren)

{

XSelectInput(dpy, focas_child[child_i], KeyPressMask|KeyReleaseMask);

child_i++;

} /* end while */

 

} /* end else */

 

*with_focas2 = *with_focas;

 

 

} /* end if (*with_focas != *with_focas2)*/

 

if (*with_focas != win)

{

if(XCheckWindowEvent(dpy, *with_focas, KeyPressMask|KeyReleaseMask, &event))

{

switch(event.type)

{

case KeyPress:

 

break;

case KeyRelease:

 

/*

This code is based on the assumption that a user will move the pinter that they wish to type in

and then they will not move it again until they wish to leave. The statements are designed to

catch the navigation keys. The behaviour is based on standard text editors and is not as flexible

as it should be. The position of the mouse when it enters the window is used as a starting point

for the calculations of cursor positions.

To make the tracking a little more comfortable the magnified area only scrolls

after 3 key strokes.

*/

 

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

{

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

dx2 = dx;

}

 

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

{

 

case XK_Left:

 

if ( xgrab - 10 > AbsUperFocasX)

{

xgrab -= 10;

dx2 = dx;

}

 

if (xgrab < 0)

{

xgrab = 0;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_Right:

 

if ( xgrab + 10 < AbsUperFocasX + focas_attrib.width )

{

xgrab += 10;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_Down:

if ( ygrab + 10 < AbsUperFocasY + focas_attrib.height )

{

ygrab += 10;

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

break;

 

case XK_Up:

 

if ( ygrab - 10 > AbsUperFocasY)

{

ygrab -= 10;

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

if (ygrab < 0)

{

ygrab = 0;

dy2 = dy;

}

 

 

break;

 

case XK_Return:

if ( ygrab + 10 < AbsUperFocasY + focas_attrib.height )

{

ygrab += 10;

xgrab = AbsUperFocasX + 10; /* When Return is pressed the usual result is

to start on a new line. AbsUperFocasX holds

the position of the start of the line and 10

pixals is a reasonable offset to make things

look a bit better.

*/

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_BackSpace:

 

if ( xgrab - 10 > AbsUperFocasX)

{

xgrab -= 8;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

xgrab += scroll;

break;

 

case XK_Tab:

 

if ( xgrab + 20 < AbsUperFocasX + focas_attrib.width)

{

xgrab += 15;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

 

 

break;

 

default:

/* This catches ordinary keystrokes */

 

if ( xgrab + 10 < AbsUperFocasX + focas_attrib.width)

{

if (key_strokes > 2)

{

xgrab += 8;

key_strokes = 0;

}

key_strokes++;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

} /* end switch(XKeycodeToKeysym(dpy, event.xkey.keycode, 0)) */

 

break;

 

default:

break;

} /* end switch event.type */

} /* end if XCheckWindowEvent for focus */

 

for (child_i =0; child_i < (int) nchildren; child_i++)

{

if(XCheckWindowEvent(dpy, focas_child[child_i], KeyPressMask|KeyReleaseMask, &event))

{

switch(event.type)

{

case KeyPress:

break;

case KeyRelease:

 

/*

This code is based on the assumption that a user will move the pinter that they wish to type in

and then they will not move it again until they wish to leave. The statements are designed to

catch the navigation keys. The behaviour is based on standard text editors and is not as flexible

as it should be. The position of the mouse when it enters the window is used as a starting point

for the calculations of cursor positions.

*/

 

 

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

{

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

dx2 = dx;

}

 

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

{

 

case XK_Left:

 

if ( xgrab - 10 > AbsUperFocasX)

{

xgrab -= 10;

dx2 = dx;

}

 

if (xgrab < 0)

{

xgrab = 0;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_Right:

 

if ( xgrab + 10 < AbsUperFocasX + focas_attrib.width)

{

xgrab += 10;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

break;

 

case XK_Down:

if ( ygrab + 10 < AbsUperFocasY + focas_attrib.height )

{

ygrab += 10;

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

break;

 

case XK_Up:

 

if ( ygrab - 10 > AbsUperFocasY)

{

ygrab -= 10;

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

if (ygrab < 0)

{

ygrab = 0;

dy2 = dy;

}

 

 

break;

 

case XK_Return:

if ( ygrab + 10 < AbsUperFocasY + focas_attrib.height)

{

ygrab += 10;

xgrab = AbsUperFocasX + 10; /* When Return is pressed the usual result is

to start on a new line. AbsUperFocasX holds

the position of the start of the line and 10

pixals is a reasonable offset to make things

look a bit better.

*/

dx2 = dx;

}

 

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

{

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

dy2 = dy;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

case XK_BackSpace:

 

if ( xgrab - 10 > AbsUperFocasX)

{

xgrab -= 8;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

xgrab += scroll;

break;

 

case XK_Tab:

 

if ( xgrab + 20 < AbsUperFocasX + focas_attrib.width)

{

xgrab += 15;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

 

 

break;

 

default:

/* This catches ordinary keystrokes */

if ( xgrab + 10 < AbsUperFocasX + focas_attrib.width)

{

if (key_strokes > 2)

{

xgrab += 8;

key_strokes = 0;

}

key_strokes++;

}

 

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

{

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

dx2 = dx;

}

 

break;

 

} /* end switch(XKeycodeToKeysym(dpy, event.xkey.keycode, 0)) */

 

 

break;

 

 

default:

break;

} /* end switch */

} /* end if XCheckWindowEvent for child */

} /* end of for child_i */

} /* end if *with_focas != win */

 

 

if ((dx2 != dx) || (dy2 != dy))

{

if (!XGetWindowAttributes(dpy, win, &win_attributes))

{

printf("\nCan't get window attributes.\n");

}

 

(void) XTranslateCoordinates (dpy, win, win_attributes.root,

-win_attributes.border_width,

-win_attributes.border_width,

&AbsUperX, &AbsUperY, &junkwin);

 

 

 

/*

The next set of if statements use the Absolute value of the

UperX and UperY cordinats to translate the mouse position into

an absolute position. The QueryPointer() function only gives a

relitave position, relitave to the window that is using the

QueryPointer(). The height and width of the root wincow

(window manager) are used to prevent accessing areas of the screen

that do not exist.

*/

 

if ( dx < 0 )

{

xgrab = dx + AbsUperX;

dx2 = dx;

}

 

else

{

xgrab = dx + AbsUperX;

dx2 = dx;

}

 

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

{

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

dx2 = dx;

}

 

 

if ( dy < 0 )

{

ygrab = dy + AbsUperY;

dy2 = dy;

}

else

{

ygrab = dy + AbsUperY;

dy2 = dy;

}

 

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

{

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

dy2 = dy;

}

 

} /* End if ((dx2 != dx) || (dy2 != dy)) */

 

 

if (hup_sig == 1)

{

hup_sig = 0;

ConfigFile = fopen(filename,"r");

 

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

 

fclose(ConfigFile);

 

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

set_title = True;

 

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.

 

The code was originally in the reset() function but

when the timer was added it had to be moved

because the program was going back to the

GetAllEvents() function before it had a chance to

read the config file. */

 

} /* End if (hup_sig == 1) */

 

/* this was also in mane */

 

while(unmapped?

(

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

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

{

 

switch(event.type)

{

 

case VisibilityNotify:

XRaiseWindow(dpy, win);

/* This was added to make sure that xzoom

is not obscured by any other window */

break;

 

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 */

} /* end switch */

 

 

/* 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];

} /* end while */

 

#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;

} /* end for i */

} /* end if flipxy */

 

else if(flipx)

{

p2 += width[SRC];

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

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

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

*p1++ = c;

} /* end for i */

 

} /* end else if flibx */

else

{

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

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

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

*p1++ = c;

}

} /* end else */

 

/* 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;

} /* end for k */

 

} /* end for j which started on line 787 */

 

#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;

} /* end if set_title */

/*

#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

*/

 

XtAppAddTimeOut(app_context, (unsigned long) MiliSeconds, GetAllEvents, data);

}

 

 

void ReadConfig(void)

{

 

/*

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

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.

*/

 

RCHomeDir=HomeDir;

strcat(RCHomeDir, "/.BlindPenguin");

strcpy(filename,RCHomeDir);

ConfigFile = fopen(filename,"r");

 

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

 

fclose(ConfigFile);

}

 

 

void reset(int signum)

{

hup_sig = 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.

XtRemoveTimeOut (*TimerID); */

 

}

 

void CreatPIDFile()

{

/*

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

It is designed to creat a .BlindPenguin.pid file in the hume dir of

the user running xzoom. The file will be used by the Tcl interface

when sending HUP signals.

*/

char PIDHomeDir[80];

pid = getpid();

 

strcpy (var, "HOME");

HomeDir = getenv(var); /* Get the value for the environment variable HOME */

strcpy(PIDHomeDir,HomeDir);

strcat(PIDHomeDir, "/.BlindPenguin.pid");

strcpy(PIDFileName,PIDHomeDir);

PIDFile = fopen(PIDFileName,"w");

fprintf(PIDFile,"%d\n",pid);

fclose(PIDFile);

}

 

 

int main(int argc, char **argv)

{

 

/* Kieran was here */

 

/* and stopped here */

 

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

if(progname)

++progname;

else

progname = argv[0];

 

/* Kieran was here */

 

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

reset() function when it gets a HUP

signal. */

 

/* and stopped here */

 

app_context = XtCreateApplicationContext(); /* Added by Kieran O' Sullivan */

 

CreatPIDFile(); /* This function creates a PIDFile which is

read by the Tcl interface when it wishes

to send a HUP signal to xzoom */

 

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) < 4) {

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| VisibilityChangeMask;

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 */

 

root_win = RootWindow(dpy, root_scr); /* Gets the root window i.e. the window manager

the height and width of the window manager is

used to prevent the attempts to access areas of

the screen that don't exist.

*/

 

 

/* and finished here */

 

 

XtAppAddTimeOut(app_context, (unsigned long) MiliSeconds, GetAllEvents, client_data);

 

XtAppNextEvent(app_context, report2);

 

 

}

 

<< Back to Contents


11 Appendix B Source Code for BlindPenguin Interface

 

#!/usr/X11/bin/wish

 

wm minsize . 300 50

wm title . "BlindPenguin"

 

#This will create the menubar and options

set f [frame .menubar]

pack $f -fill x

 

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

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

 

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

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

 

menubutton $f.help -text Help -underline 0 -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 $size}

$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 $l.l

set m1 [menu $l.l -tearoff 1]

$m1 add check -label "X 2" -variable X2 -command {set_size "2"}

$m1 add check -label "X 3" -variable X3 -command {set_size "3"}

$m1 add check -label "X 4" -variable X4 -command {set_size "4"}

$m1 add check -label "X 5" -variable X5 -command {set_size "5"}

$m1 add check -label "X 6" -variable X6 -command {set_size "6"}

 

$p add cascade -label "Help Topics" -menu $p.3

set m3 [menu $p.3 -tearoff 1]

 

$m3 add cascade -label "General Help" -menu $p.3.1

set o [menu $p.3.1 -tearoff 1]

$m3 add separator

 

$o add command -label "System Requirements" -command {WebHelp}

$o add command -label "Installation" -command {WebHelp1}

 

$m3 add command -label "File Menu" -command {WebHelp2}

$m3 add command -label "Options Menu" -command {WebHelp3}

$m3 add command -label "Magnification Help" -command {WebHelp4}

 

$p add separator

$p add command -label About -command about

###############################################################

#This procedure passes down the size that the user will select

 

proc set_size {var} {

global size

set size $var

}

 

#This is where the size is saved to file

proc savefile {size} {

global env

set configfile $env(HOME)

set pidfile $env(HOME)

set signal 1

 

append configfile "/.BlindPenguin"

append pidfile "/.BlindPenguin.pid"

 

set mysize [open $configfile w]

 

puts $mysize $size

puts $mysize $size

 

close $mysize

 

set myfile [open $pidfile r]

gets $myfile pids

close $myfile

eval exec [format "kill -%s" $signal] $pids

 

destroy .saveent

}

 

#This is where the size is saved to file when the user is over writing a

#file

 

proc savefile1 {size} {

 

destroy .saveent

global env

 

set configfile $env(HOME)

set pidfile $env(HOME)

set signal 1

 

append configfile "/.BlindPenguin"

append pidfile "/.BlindPenguin.pid"

 

set mysize [open $configfile w]

 

puts $mysize $size

puts $mysize $size

 

close $mysize

 

set myfile [open $pidfile r]

gets $myfile pids

close $myfile

eval exec [format "kill -%s" $signal] $pids

}

#This procedure is called when a file is entered in the Load Config option

 

proc openentry {entry} {

global ent

set ent $entry

 

if [file exists $entry] {

 

if [file isdirectory $entry] {

cd $entry

destroy .fs.files

listbox .fs.files -relief raised -borderwidth 3 \

-yscrollcommand ".fs.scroll set"

 

pack .fs.files -side left

 

list-out

 

} elseif [file exists $entry] {

 

if [file isfile $entry] {

 

global env

set myfile $ent

set myfile2 $env(HOME)

 

append myfile2 "/.BlindPenguin"

 

set InFile [open $myfile r]

set OutFile [open $myfile2 w]

while {-1 != [gets $InFile Line]} {

puts $OutFile $Line

}

}

 

destroy .fs

}

} else {loaderr}

}

 

#This procedure puts the entry field in the Load Config box and when the

#user hits return after entering in a file to be opened it calls the

#openentry procedure

 

proc file-entry {type} {

frame .fs.file

label .fs.file.label -text $type

entry .fs.file.entry -width 20 -relief sunken -bd 2 -textvariable entered

pack .fs.file.label .fs.file.entry -side left -padx 1m -pady 2m

global entry

focus .fs.file.entry

bind .fs.file.entry <Return> {openentry [.fs.file.entry get]}

 

pack .fs.file -side top

}

 

#This procedure puts the entry field in the Save As box and when the user

#hits return after entering in a file it calls the save-entry procedure

 

proc file-entry1 {type} {

frame .fs.file

label .fs.file.label -text $type

entry .fs.file.entry -width 20 -relief sunken -bd 2 -textvariable entered

pack .fs.file.label .fs.file.entry -side left -padx 1m -pady 2m

 

global entry

bind .fs.file.entry <Return> {save-entry [.fs.file.entry get]}

 

pack .fs.file -side top

}

 

#This procedure gives a list of all the files in the users home direcory

 

proc list-out {} {

.fs.files insert end .

.fs.files insert end ..

foreach i [lsort [glob *]] {

.fs.files insert end $i

}

}

 

#This procedure saves the file name that the user enters in the entry field

 

proc save-entry {entry} {

 

global listent

set listent $entry

global env

global size

 

if [file exists $entry] {

 

if [file isfile $entry] {

 

{saveerror}

}

} elseif {[file exists $entry] != 1} {

 

if {[file isfile $entry] !=1} {

 

set mysize [open $entry w]

 

puts $mysize $size

puts $mysize $size

 

destroy .fs

}

 

savefile $size

 

}

}

 

#This is the list box for the Load Config option

 

proc list-box {} {

listbox .fs.files -relief raised -borderwidth 4 \

-yscrollcommand ".fs.scroll set"

pack .fs.files -side left

scrollbar .fs.scroll -command ".fs.files yview"

pack .fs.scroll -side right -fill y

list-out

global boxg

global entry

focus .fs.files

#bind .fs.files <ButtonPress-1><Return> [list ListTransferSel %W $]

bind .fs.files <ButtonPress-1><Button> {openentry [.fs.files get active]}

}

 

#This is the listbox for the Save As option

 

proc list-box1 {} {

listbox .fs.files -relief raised -borderwidth 4 \

-yscrollcommand ".fs.scroll set"

pack .fs.files -side left

scrollbar .fs.scroll -command ".fs.files yview"

pack .fs.scroll -side right -fill y

list-out

global boxg

global entry

focus .fs.files

bind .fs.files <ButtonPress-1><Button> {saveerror}

}

 

#This is the box for the open option

 

proc openfile {} {

toplevel .fs

wm title .fs "Select File:"

file-entry "Open File:"

button .fs.cancel -text "Cancel" -padx 5 -pady 5 -command {destroy .fs}

pack .fs.cancel -side left -padx 5

 

list-box

}

 

#This is the box for the save as option

 

proc saveas {} {

toplevel .fs

wm title .fs "Save File:"

file-entry1 "Save File:"

button .fs.cancel -text "Cancel" -padx 5 -pady 5 -command {destroy .fs}

pack .fs.cancel -side left -padx 5 -pady 5

 

list-box1

}

 

#This is the about box giving credit where owed to the team

 

proc about {} {

toplevel .about -borderwidth 3

wm title .about "About BlindPenguin"

 

wm geometry .about 300x275

 

message .about.members -justify left -text "BlindPenguin was completed in partial\

fulfilment of the requirements of the degree, BSc. in Commercial Software Development, in\

Waterford Institute of Technology.

 

The students working on this project are:

 

Kieran O'Sullivan

Catherine Teahan

 

 

Dated: 15th April 2000"

 

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

 

pack .about.members

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

}

 

#These following procedures bring the user to the help files that are

#written in html

 

proc WebHelp {} {

exec sh -c "netscape -install http://snet.wit.ie/BlindPenguin/help/systemrequirements.html"

}

 

proc WebHelp1 {} {

exec sh -c "netscape -install http://snet.wit.ie/BlindPenguin/help/installation.html"

 

}

 

proc WebHelp2 {} {

exec sh -c "netscape -install http://snet.wit.ie/BlindPenguin/help/help.html"

 

}

 

proc WebHelp3 {} {

exec sh -c "netscape -install http://snet.wit.ie/BlindPenguin/help/options.html"

}

 

proc WebHelp4 {} {

exec sh -c "netscape -install http://snet.wit.ie/BlindPenguin/help/magnification.html"

 

}

 

#This procedure warns the user that they are about to overwrite a file

 

proc saveerror {} {

destroy .fs

global size

toplevel .saveent -borderwidth 3

 

wm title .saveent "Warning!"

wm geometry .saveent 150x150

 

message .saveent.put -justify left -text "This file already exists.\

Do you want to replace the existing file?"

 

button .saveent.ok -text "OK" -command {savefile1 $size}

button .saveent.cancel -text "Cancel" -command "destroy .saveent"

 

 

pack .saveent.put

pack .saveent.ok .saveent.cancel -side left -pady 10 -padx 10

}

 

#This procedure tells the user that the file they are opening does not exist

 

proc loaderr {} {

toplevel .load -borderwidth 3

 

wm title .load "ERROR!"

wm geometry .load 130x150

 

message .load.err -justify left -text "The File You Are Trying To Open Does\

Not Exist. Please Try Again."

 

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

pack .load.err

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

}

 

<< Back to Contents