BlindPenguin Project
Final Report
|
|
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:
- 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.
- 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.
- Keyboard Control - A user may if they wish use the
keyboard to set the size.
- Tracking User Activity - Users can use two main types
of input either the keyboard or the mouse. BlindPenguin tracks
both these types of input.
- Help Facilities - The help feature will guide a user
through any aspect of the interface.
1.3 Structure of this
Document
- 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.
- Final Evaluation of BlindPenguin - This evaluates the
functionality of the second prototype.
- Analysis and Final Prototype Implementation - This
section explains how the problems were analysed and how the final
prototype was developed.
- 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.
- 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.
- Conclusions - A number of conclusions about the way the
project is progressing and discoveries that the team made while
developing BlindPenguin are discussed here.
- References - A complete list of all the material that
the team used to help develop the project.
- 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:
- Xzoom would track keystrokes.
- The Xzoom window would always be visible.
- It would be possible to install Xzoom on any Linux system.
- 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
}
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:
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
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(¤t_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
|