BlindPenguin Project
Second Report
|
|
Table of Contents
ACKNOWLEDGEMENTS
DECLARATION
1 INTRODUCTION
1.1 What is
BlindPenguin?
1.2 Desired 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
2.4 The History of Tcl
2.5 The History of Tk
3 EVALUATION OF
BLINDPENGUIN
3.1 Evaluation of
Xzoom
3.2 The Addition of a
Configuration File
3.3 Signal Processing
Functionality
3.4 Mouse Tracking
3.5 Evaluation of
BlindPenguin Interface
4 ANALYSIS AND SECOND
PROTOTYPE IMPLEMENTATION
4.1
Refinement of First
Prototype
4.2 Quick Design
4.2.1 The
Configuration File
4.2.2 The Process ID File
4.2.3 Possible File
Errors
4.2.4 Why Use Signals
for Communications?
4.3 Build Prototype
5 SUMMERY OF WORK DONE
5.1
Modifications Made to
Xzoom
5.1.1 The
Configuration File
5.1.2 The Process ID File
5.1.3 Mouse Tracking
5.2 Tcl Programming
6 CONCLUSIONS
7 REFERENCES
8 APPENDIX A SOURCE
CODE FOR XZOOM
9 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: 8th March 2000
Catherine Teahan Date: 8th March 2000
<< Back to Contents
1 Introduction
The growth of Linux over the past few years has seen a large
number of programs being developed for X Windows, which is a GUI
(Graphical User Interface) for Linux. However there are still
relatively few screen magnification programs available for disabled
users of X Windows, those that are available are primitive and not
very user friendly.
1.1 What is BlindPenguin?
The BlindPenguin is a screen magnification package for the X
Windows environment. The idea is to produce an easily configured
package on the same lines as some of the commercial screen
magnification packages available for Windows 9X and NT. However since
disabled access is a right, this program will be distributed free
with the source code under GNU General Public Licence.
BlindPenguin is based on the source code of a program called
Xzoom. This program performs magnification very well but it would be
extremely difficult for a novice to configure. To make it easier for
new users a Tcl interface has been written which allows most of the
configuration to be done through a GUI. In addition some of the
tracking features of Xzoom have been improved to allow better screen
navigation.
BlindPenguin will consist of three prototypes. Each prototype is
accompanied by a report. The first prototype was described in the
first report and the second prototype is described in this report.
The team are presently finished the second prototype and are now
ready to plan the third prototype. The third prototype will be the
final version and the team intend that this prototype will contain
all the functionality outlined in the first report.
1.2 Desired
Features of BlindPenguin
The main features that will be incorporated into BlindPenguin when
it is finished are:
- 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.
- Changing Hot Keys - Hot Keys are keystrokes that can be
used for navigation and switching between sizes. If a user does
not like a particular key combination they have the ability to
change it using the interface.
- Setting Targets - Targets allow users to have sections
of the screen permanently magnified and displayed. This feature is
useful for displaying things such as on screen clock.
- 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.
- Evaluation - This evaluates the functionality of the
first prototype.
- Analysis and Second Prototype Implementation - This
section explains how the problems were analysed and how the second
prototype was developed.
- Summary of Work Done - The modifications made to Xzoom
and the work done for the Tcl interface are explained here. Source
code examples are given to illustrate this work.
- 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 toContents
2 Background Concepts
2.1 Introduction to X
Windows
In September 1987 Massachusetts Institute of Technology, MIT,
released the first version of X Windows commonly referred to as X11.
X Windows was the designed to provide Graphical User Interaction with
a Unix or Unix like system on workstations. The fact that X11 was
designed for workstations ensured that from the very start networking
would be fundamental to the environment.
Networking is key to the X Window philosophy, all applications,
which are written for this GUI, must be capable of running from any
machine on the network. As well as running in a transparent manner,
i.e. the user should not have to know that they are running on a
network, the X Windows environment must be able to handle issues such
as a slow network, communications failures between the X server and
applications running on it and perhaps most importantly the X Windows
system should be able to provide a user friendly interface through
consistent graphical metaphors.
These metaphors are provided using bitmap technology. In bitmapped
graphics each pixel or dot on the screen corresponds to one or more
bits in memory. A feature of the way that X Windows displays
graphics, is that it can have a number of virtual screens, which
allow a user to have data displayed on their station without it
actually being visible. When they want to see the data they simply
use a keystroke or mouse click to activate the virtual screen that
they are interested in.
Virtual screens as well as all the applications are controlled by
a window manager. The window manager decides where everything is put
and it allows users to launch programs. The manager must also allow
users to resize, move or kill windows as they wish. All this is
covered by a set of rules that have been predefined by the programmer
who wrote the window manager. For the most part programmers and users
do not really have to know these rules as they would be too
complicated for users, and programmers should not depend on having a
particular window manager. A program written for X Windows should run
on any window manager.
2.2 Introduction to Xlib
As part of the X Windows system Xlib was developed to allow the
system to be programmed relatively easily. Xlib is a collection of C
functions. These functions communicate directly with the X server to
allow a program to do whatever it needs to do. The main principle
behind X Windows was portability and this is implemented in all the
Xlib functions.
Applications written using the Xlib functions should compile and
run on any machine that has the libraries installed. One of the
strongest portability features of Xlib is that it uses its own
protocol when sending requests over the network to the X server.
Using this protocol the client generates a request and the server
then performs some action or sends some data. The server may perform
an action and not send back any information. This increases network
performance. An example of information being sent to the server from
a client is when an event occurs such as a window being closed down.
Xlib calls tend to be very low level to increase the efficiency of
programs. It is necessary to develop higher level functions which are
more transparent. The most well known toolkit is Tcl/Tk which was
written for the Tcl language. It allows a programmer to create all
the standard GUI objects using a very small amount of code. These
functions deal with the Xlib low level functions so there is no need
for the programmer to worry about them. This method is used for most
programs but in some cases where efficiency is an issue it is better
to use the low level Xlib function calls.
2.3 What is Tcl/Tk
Tcl stands for ''tool command language'' and is pronounced
''tickle.'' Tcl is actually two things: a language and a library.
First, Tcl is a simple textual language, intended primarily for
issuing commands to interactive programs such as text editors,
debuggers, illustrators, and shells. It has a simple syntax and is
also programmable, so Tcl users can write command procedures to
provide more powerful commands than those in the built-in set.
Second, Tcl is a library package that can be embedded in
application programs. The Tcl library consists of a parser for the
Tcl language, routines to implement the Tcl built-in commands, and
procedures that allow each application to extend Tcl with additional
commands specific to that application. The application program
generates Tcl commands and passes them to the Tcl parser for
execution. When the Tcl library receives commands it parses them into
component fields and executes built-in commands directly. For
commands implemented by the application, Tcl calls back to the
application to execute the commands.
An application program gains several advantages by using Tcl for
its command language. First, Tcl provides a standard syntax: once
users know Tcl, they will be able to issue commands easily to any
Tcl-based application. Second, Tcl provides programmability. All a
Tcl application needs to do is to implement a few
application-specific low-level commands. Tcl provides many utility
commands plus a general programming interface for building up complex
command procedures. By using Tcl, applications need not re-implement
these features. Third, extensions to Tcl, such as the Tk toolkit,
provide mechanisms for communicating between applications by sending
Tcl commands back and forth. The common Tcl language framework makes
it easier for applications to communicate with one another.
Tcl was designed with the philosophy that one should actually use
two or more languages when designing large software systems. One for
manipulating complex internal data structures, or where performance
is key, and another, such as Tcl, for writing smallish scripts that
tie together the other pieces, providing hooks for the user to
extend. For the Tcl script writer, ease of learning, ease of
programming and ease of gluing are more important than performance or
facilities for complex data structures and algorithms. Tk is an
extension to Tcl which provides the programmer with an interface to
the X11 windowing system.
2.4 The History of Tcl
The Tcl scripting language grew out of the work of John Ousterhout
on design tools for integrated circuits at the University of
California at Berkeley in the early 1980's. He had written several
interactive tools for IC design, such as Magic and Crystal. Each tool
needed to have a command language (in those days people tended to
invoke tools by typing commands; graphical user interfaces weren't
yet in widespread use). However, his primary interest was in the
tools, not their command languages. He didn't invest much effort in
the command languages and the languages ended up being weak.
In the fall of 1987, he got the idea of building an embeddable
command language. The idea was to spend extra effort to create a good
interpreted language, and furthermore to build it as a library
package that could be reused in many different applications. The
language interpreter would provide a set of relatively generic
facilities, such as variables, control structures, and procedures.
Each application that used the language would add its own features
into the language as extensions, so that the language could be used
to control the application. The name Tcl (Tool Command Language)
derived from this intended usage.
2.5 The History of Tk
One of his other interests at that time was graphical user
interfaces. As GUIs became more and more popular in the 1980s he had
noticed that the complexity of interactive software was rising
rapidly. The most interesting new developments seemed to require
large projects with enormous investments. As a professor with modest
resources, this worried him.
He concluded that the only hope was to reduce the resource
requirements by building large systems out of reusable components. If
most of the complexity of a system was in the components, it would
take quite a bit of work to develop the components, but this could be
done gradually over time, perhaps by several smaller groups working
together.
He also reasoned that component-based design would not work unless
there was a powerful and flexible mechanism for integrating the
components. These thoughts occurred shortly after he had begun
thinking about Tcl, and it occurred to him that an embeddable command
language such as Tcl might also be useful as a scripting language for
integrating components. He decided to test this theory by creating a
set of GUI components as a Tcl extension and using Tcl to assemble
the components into graphical user interfaces. This extension became
Tk.
<< Back to Contents
3 Evaluation of
BlindPenguin
3.1 Evaluation of Xzoom
Following the development of the first 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.2 The Addition of
a Configuration File
A configuration file was added to Xzoom to allow users to save
settings and access these settings when they ran the program in the
future. The configuration file allowed for the saving of size
settings only. Before the addition of this capability users had to
enter sizes at the command line or edit the source code and recompile
Xzoom if they wanted to change the size settings. The configuration
file was very successful and there were no implementation errors. If
a user did not have the configuration file then the default settings
were used, if they did have the file and wished to use command
parameters instead of the settings the program was designed to allow
them to do so. The only possibility for errors is where a user puts
a string in instead of a number which would result in an invalid size
but there are error checks in the program which would prevent this
from causing serious harm, all that would happen is that the size
would not be what the user wanted.
A more fundamental problem with the implementation of the
configuration file in the first prototype is that it is stored in the
same directory as the Xzoom program which means that any user of the
BlindPenguin wishing to save size settings would have to have write
access to this directory. This is not a desirable situation and in
most cases would be impossible due to security considerations as well
as administrative overhead. Apart from the security problems with
having a configuration file in the same directory as the program also
means that if one user prefers one size and another user prefers
another size the configuration file does not perform the function
which it should perform as both users would be constantly changing
it. Redesigning of the implementation of the configuration file
mechanism was necessary.
3.3 Signal
Processing Functionality
Xzoom was originally designed to function as a stand-alone
program. In order for it to work with the Tcl interface it was
necessary to give it the capability to process signals. Any signal
with the exception of the KILL signal can be caught by a program and
used to perform a function. Xzoom was modified to catch the HUP
signal because this is the standard UNIX way of telling a process to
re-read its configuration file.
The implementation of a signal listener is a standard UNIX
practice and there were no implementation problems with this
procedure. However it is possible for a program to receive a signal
at any time and there is the possibility of a signal being sent to
Xzoom before it has drawn its window. This would result in immediate
termination of the program but it is unlikely that a signal would be
sent to it at this point.
Part of the signal processing functionality involved writing the
process ID of Xzoom to a file. When the BlindPenguin Tcl interface
needs to send HUP signals to the Xzoom process, it reads the process
ID from this file. Like the configuration file the process ID file
was stored in the same directory as the Xzoom program this means that
if more than one user runs the BlindPenguin the process ID of the
previous user will be overwritten preventing the original user from
sending HUP signals to it with the Tcl interface. As with the
configuration file mechanism the process ID file mechanism has to be
redesigned to allow for multiple users.
3.4 Mouse Tracking
In order for any meaningful screen navigation a user has to be
able to use the mouse and have the screen magnification change
relative to the mouse position. The first prototype allows a user to
move the mouse within the Xzoom window and review the real screen.
This was an improvement on the original program that only allowed
screen navigation using the arrow keys. There were however a number
of problems with this form of screen navigation:
- The mouse had to be located in the BlindPenguin window in
order for any tracking to take place.
- BlindPenguin often recursively magnified itself.
- A user could not click on an application that was magnified
because the mouse was in the BlindPenguin window not in the
application window.
- On leaving the BlindPenguin window all mouse tracking ceased.
For these reasons the mouse tracking feature of BlindPenguin had
to be completely overhauled and redesigned.
3.5 Evaluation of
BlindPenguin Interface
A menu bar was successfully implemented which had three menu
items: File, Options and Help respectively. When any of these menu
items are clicked on a sub menu appears. The only options that were
implemented in this prototype were the About option and the Exit
option. When the About option is selected a message box appears to
inform users who created this system. There is an OK button in this
message box which when clicked causes the message box to disappear.
When the Exit option is selected the interface terminates.
There were a number of features that the interface had to perform
but this prototype did not implement these. These features are:
- Saving configuration files
- Sending HUP signals
- Reading process ID files
<< Back to Contents
4 Analysis and Second
Prototype Implementation
4.1 Refinement of
First Prototype
The results of the Evaluation demonstrated some of the problems
with the first prototype. The team decided on the following
refinements to be made to the BlindPenguin:
- Each user would have their own configuration, which would
retain whatever settings they chose to save in it.
- Each user would have a process ID file, which would be used by
the Tcl interface to send HUP signals to the copy of Xzoom that
the user was running.
- The Mouse would be tracked no matter where it went on the
screen and even if Xzoom was not the active window.
4.2 Quick Design
4.2.1 The
Configuration File
Like many Linux programs the BlindPenguin would have to have a
configuration file to allow users to save settings. The problem as
identified by the evaluation of the first prototype was that if the
BlindPenguin was to support multiple users then it would have to have
more than one configuration file, which a user could change to their
liking without affecting other users settings. There are a number of
ways that such a system could be implemented however it was decided
that the most effective would be to place a configuration file in the
users home directory. BlindPenguin would look for this file when it
started up and the BlindPenguin interface would write to it when a
user saved new settings.
The file was named ".BlindPenguin" this is a standard naming
scheme for a lot of files which are placed in a users home directory.
The '.' means that the file will only be listed when the ls -all
command is used and the name itself will instantly inform the user
about what the file is.
4.2.2 The Process ID File
The problem with the process ID file as discussed in the
evaluation is that it can only cater for one user running Xzoom at a
time. To allow for multiple users the process ID file had to be
implemented in the same way as the configuration file with each user
having their own process ID file in their own home directory. The
name chosen for the process ID file was ".BlindPenguin.pid" this
naming scheme was chosen for the same reasons as the configuration
file-naming scheme.
4.2.3 Possible File
Errors
The most common problem with this type of system is that of a user
accidentally deleting their configuration or process ID file. If a
user happens to delete the configuration file and they run the Xzoom
part of BlindPenguin, Xzoom will simply use the default settings. If
a user runs the BlindPenguin interface it will recreate the
configuration file when they save their settings.
Deleting a process ID file has no consequences for Xzoom but it
will mean that the BlindPenguin interface cannot send HUP signals to
Xzoom resulting in an error message to the user. Another problem
with the process ID file is that of Xzoom being killed and a user
attempting to configure it. In this case the settings will be saved
to the configuration file but the user will get an error message
saying that the process does not exist. To solve either of these
problems all the user has to do is to restart Xzoom.
4.2.4 Why Use
Signals for Communications?
There are a number of mechanisms which one process can use to talk
to another, e.g. pipes, messages and shared memory however the
simplest way in which one process can communicate with another
process is by sending signals to that other process. Since the
BlindPenguin interface does not need to know anything about the Xzoom
program sending it a signal is the most appropriate mechanism for
communications. The use of signals is only constrained by ownership
i.e. a user can only send signals to a process, which they are
running. If pipes were to be used then there would have to be a
parent child relationship between the Xzoom and the BlindPenguin
interface.
4.3 Build Prototype
When building the second prototype the team had to write a number
of programs for test purposes. These included a simple HUP signal
catcher in C that was used to test the BlindPenguin interface to be
sure that it was sending HUP signals to the correct process. The
program simply printed out a message every time it received a signal.
A simple version of the example program in the Xlib Programming
Manual (page 82 - 88) was used to test the XtAppTimeOut() and
XtAppNextEvent() functions. The source code for xeyes and xwininfo
were used for reference purposes.
Implementing the signal handling and the multi user functionality
of the second prototype was a relatively simple process. However the
mouse tracking functionality was an extremely complicated task. It
necessitated the changing of large parts of the Xzoom program and
re-writing of the signal handler because if conflicts between it and
the timing signals.
It was more difficult to send HUP signals in Tcl because there was
limited amount of information available to the team. The Tcl book
(Welch, 1997) did not provide any information on signal handling or
inter process communication. Learning how to send signals was done by
trial and error.
<< Back to Contents
5 Summery of Work Done
5.1 Modifications
Made to Xzoom
The first prototype was an improvement on the original Xzoom
program however as discussed in the evaluation section of this
document there were a number of design issues that had to be refined.
5.1.1 The
Configuration File
In the Analysis and Second Prototype Implementation phase it was
decided that each user should have their own configuration file
stored in their own home directory. The following modification were
made to Xzoom to allow this:
- A Global variable was declared to hold the values of the users
HOME directory.
- The strcat() function was used to concatenate the HOME
directory with the file name.
void ReadConfig(void)
{
/* This function was written by Kieran O' Sullivan and Catherine
Teahan It is designed to read the config file when the program
starts.*/
RCHomeDir=HomeDir;
strcat(RCHomeDir, "/.BlindPenguin");
strcpy(filename,RCHomeDir);
ConfigFile = fopen(filename,"r");
fscanf(ConfigFile,"%d\n%d", &magx, &magy);
fclose(ConfigFile);
}
5.1.2 The Process ID File
In the Analysis and Second Prototype Implementation phase it was
decided that each user should have their own process ID stored in
their own home directory. The implementation of this was very
similar to the implementation of the configuration file solution,
some of the same global variables were used. This function is called
very early in the life of Xzoom so it was decided to put the call to
getenv() here.
void CreatPIDFile()
{
/*
This function was written by Kieran O' Sullivan and Catherine Tean
It is designed to create a .BlindPenguin.pid file in the home 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);
}
5.1.3 Mouse Tracking
As discussed in the Evaluation phase the mouse tracking provided
by the first prototype was not satisfactory for anything more than
reviewing the screen. A great many modifications were necessary to
Xzoom to allow for the type of mouse tracking discussed in the
Analysis and Second Prototype phase:
- Most of the variables originally declared in main() had to be
declared as global so that GetAllEvents could access them.
- A timer had to be set using XtAppAddTimeOut() so that Xzoom
would run a function GetAllEvents() every 100 mili seconds.
- XtAppNextEvent() was added to catch these timer events.
- GetAllEvents() had to be written to alter the Xzoom window
when the mouse was moved and still handle the keystroke events.
- The original event loop had to be moved into the function
GetAllEvents().
- The signal processing function reset() had to be re-written
too because of conflicts with GetAllEvents().
- The width and height of the root window and the width and
height of the Xzoom window had to be read.
- Calculations verification had to be done using the root and
Xzoom window coordinates to prevent the generation of invalid
window positions.
5.1.3.1 Changes Made to Main
int main(int argc, char **argv)
{
XtPointer client_data;
.......
signal(SIGHUP, reset); /* This tells the program to run the
reset() function when it gets a HUP signal. */
.......
app_context = XtCreateApplicationContext();
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. */
........
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.*/
XtAppAddTimeOut(app_context, (unsigned long) MiliSeconds,
GetAllEvents, client_data);
XtAppNextEvent(app_context, report2);
}
5.1.3.2 Changes Made to reset()
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.*/
}
5.1.3.3 The GetAllEvents() Function
static void GetAllEvents(XtPointer data, XtIntervalId *id )
{
/* XtPointer data, and XtIntervalId *id are not */
XWindowAttributes win_attributes, root_win_attrib;
Window junkwin;
int junk, AbsUperX, AbsUperY, AbsUperRootX, AbsUperRootY;
unsigned long MiliSeconds;
MiliSeconds = (unsigned long) 100;
tmpx = 0; tmpy = 0;
XQueryPointer (dpy, win, &rep_root, &rep_child,
&rep_rootx, &rep_rooty, &dx, &dy,
&rep_mask);
/* For efficiancy reasons perform calcualtions only if mous moves
*/
if ((dx2 != dx) || (dy2 != dy))
{
if (!XGetWindowAttributes(dpy, win, &win_attributes))
{
printf("\nCan't get window attributes.\n");
}
if (XGetWindowAttributes(dpy, root_win,
&root_win_attrib))
{
(void) XTranslateCoordinates (dpy, root_win,
root_win_attrib.root,
-root_win_attrib.border_width,
-root_win_attrib.border_width,
&AbsUperRootX, &AbsUperRootY, &junkwin);
}
(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 UperX
and UperY cordinats to translate the mouse position into an absolute
position. The QueryPointer() function only gives a relative
position, relative 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
{
tmpx = dx + AbsUperX + 100;
if (tmpx >= root_win_attrib.width )
{
dx2 = dx;
}
else
{
xgrab = dx + AbsUperX;
dx2 = dx;
}
}
if ( dy < 0 )
{
ygrab = dy + AbsUperY;
dy2 = dy;
}
else
{
tmpy = dy + AbsUperY + 65;
if (tmpy >= root_win_attrib.height)
{
dy2 = dy;
}
else
{
ygrab = dy + AbsUperY;
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 rom
being reset to its default behaviour i.e. Termination of the 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 of problems with global and
local variables */
} /* End if (hup_sig == 1) */
XtAppAddTimeOut(app_context,(unsigned long) MiliSeconds,
GetAllEvents, data);
/* the rest of this function can be seen in Appendix A */
}
5.2 Tcl Programming
The BlindPenguin interface was designed in the first prototype so
the next stage for the team was to "glue" the interface to the screen
magnification system. This required learning how to write to files
and send HUP signals. The Tcl programming book (Welch, 1997) proved
once again to be very useful in providing good information on file
writing. However additional information was obtained on the Internet.
The first step here was to allow the user to set their preferred size
and to save this size. When the user selects a size in the Set Size
option a variable is passed down so that when the user clicks the
Save option this procedure reads this variable and writes it to a
".BlindPenguin" file. This ".BlindPenguin" file will now contain the
users setting. The ".BlindPenguin" file is saved in the users home
directory. This allows for multiple users. The code for writing to
files is as follows:
set mysize [open $configfile w]
puts $mysize $size
puts $mysize $size
close $mysize
In the above code the $configfile is the file that was
concatenated with both the ".BlindPenguin" file and the
".BlindPenguin.pid" file. The $size is the variable that was passed
down when the user selected the size.
Learning how to send signals was a bit more difficult. The team
studied a number of programs that were written that sent signals and
from these acquired the knowledge needed to send HUP signals. Sending
signals from the BlindPenguin interface to the Xzoom code allows the
two 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. The following
illustrates the lines of code needed to send HUP signals:
set signal 1
set myfile [open $pidfile r]
gets $myfile pids
close $myfile
eval exec [format "kill -%s" $signal] $pids
Following the implementation of the Set Size and Save options the
next step was to implement the Load Config and Save As options. The
team decided that the most appropriate solution would be to create
list boxes that would appear when either of these options were
selected. These list boxes would contain an entry field and a
scrollable list of all the files in the users directory. The entry
field allows the user to enter the name of the file that they wish to
Load or Save As. Both boxes are the same in each option. The code to
create the list box containing the list of files in the users
directory is:
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
bind .fs.files <Return> {openentry [.fs.files.entry get]}
}
proc list-out {} {
.fs.files insert end .
.fs.files insert end ..
foreach i [lsort [glob *]] {
.fs.files insert end $i
bind .fs.files <Return> {openentry [.fs.file.entry get]}
}
}
For this prototype the Load Config is the only one of the two that
is implemented. When the user enters the name of the file in the
entry field and hits return the contents of that file are copied into
the ".BlindPenguin" file. This file will contain the size that the
user selected from the Set Size option and saved. If the file name
entered does not exist on the list of files that are in the list box
an error message is written to the screen telling the user that the
file does not exist. The code for accepting the entry in the entry
field and copying the contents to the .BlindPenguin file is:
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
bind .fs.files <Return> {openentry [.fs.file.entry get]}
list-out
global entered
} 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 {
puts "file not found"
}}
Figure 5.2.1 List Box for Load Config
The above is a screen shot of the list box for the Load Config
option. The list box for the Save As option is the same except the
text Save As replaces the Open File text. However for this prototype
the user cannot select any of the files in the list of the list box.
They must type the file in the entry field. The team plan to have
this option available to the user by the next prototype.
<< Back to Contents
6 Conclusions
At present the project has met all the deadlines that the team
have set and those deadlines set by the college. The use of the
Prototyping methodology has proved very efficient both in terms of
understanding the exact nature of the project and in terms of getting
the work done.
The use of Tcl has also aided the process of prototyping as it has
allowed for the rapid development of a user interface.
The next stage of the project will be the re- evaluation of the
second prototype. This stage will most likely result in the
development of a more refined prototype which will incorporate more
of the features mentioned in the introduction.
All the team efforts will be from now on geared towards a final
implementation of the objectives set down in the first report for
BlindPenguin.
<< Back to Contents
7 References
Farrell Paul A web site
http://dune.mcs.kent.edu/~farrell/sys95/notes/examples/prog/signal/
This site contains information about Distributed Systems and IPC,
including source code.
Tronche Christophe web site.
http://www.tronche.com/gui/x/
Contains the full Xlib Manual in HTML format.
Nye A 1990.
Xlib Programming Manual
ISBN 0-937175-11-0
O'Reilly & Associates, Inc., Sebastopol, CA 95472
Walch B 1997.
Practical Programming in Tcl and Tk Second Edition.
ISBN 0-L3-6L6830-2
Prentice Hall PTR, Upper Saddle River, NJ 07458.
<< Back to Contents
8 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 256 /* default width */
#define HEIGHT 256 /* default height */
#define MAG 2 /* default magnification */
#define MAGX MAG /* horizontal magnification */
#define MAGY MAG /* vertical magnification */
int xgrab, ygrab; /* where do we take the picture from */
int magx = MAGX;
int magy = MAGY;
int flipxy = False; /* flip x and y */
int flipx = False; /* flip display about y axis */
int flipy = False; /* flip display about x axiz */
int xzoom_flag = False; /* next mag change only to magx */
int yzoom_flag = False; /* next mag change only to magy */
int width[2] = { 0, WIDTH };
int height[2] = { 0, HEIGHT };
#ifdef XSHM
XShmSegmentInfo shminfo[2]; /* Segment info. */
#endif
XImage *ximage[2]; /* Ximage struct. */
int created_images = False;
#define NDELAYS 5
int delays[NDELAYS] = { 200000, 100000, 50000, 10000, 0 };
int delay_index = 0;
int delay = 200000; /* 0.2 second between updates */
/* Kieran was here */
FILE * ConfigFile;
char filename[80];
char * HomeDir;
char * RCHomeDir;
char var[3];
/* PID File */
char PIDFileName[80];
FILE * PIDFile;
pid_t pid;
Window rep_root, rep_child;
XtAppContext app_context;
int rep_rootx, rep_rooty, hup_sig;
unsigned int rep_mask;
int dx, dy;
int dx2, dy2, tmpx, tmpy; /* used to stop un-necessary
pointer queries. */
/* 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;
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 */
XWindowAttributes win_attributes, root_win_attrib;
Window junkwin;
int junk, AbsUperX, AbsUperY, AbsUperRootX, AbsUperRootY;
unsigned long MiliSeconds;
MiliSeconds = (unsigned long) 100;
tmpx = 0;
tmpy = 0;
XQueryPointer (dpy, win, &rep_root, &rep_child,
&rep_rootx, &rep_rooty, &dx, &dy,
&rep_mask);
if ((dx2 != dx) || (dy2 != dy))
{
if (!XGetWindowAttributes(dpy, win, &win_attributes))
{
printf("\nCan't get window attributes.\n");
}
if (XGetWindowAttributes(dpy, root_win, &root_win_attrib))
{
(void) XTranslateCoordinates (dpy, root_win,
root_win_attrib.root,
-root_win_attrib.border_width,
-root_win_attrib.border_width,
&AbsUperRootX, &AbsUperRootY, &junkwin);
}
(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
{
tmpx = dx + AbsUperX + 100;
if (tmpx >= root_win_attrib.width )
{
dx2 = dx;
}
else
{
xgrab = dx + AbsUperX;
dx2 = dx;
}
}
if ( dy < 0 )
{
ygrab = dy + AbsUperY;
dy2 = dy;
}
else
{
tmpy = dy + AbsUperY + 65;
if (tmpy >= root_win_attrib.height)
{
dy2 = dy;
}
else
{
ygrab = dy + AbsUperY;
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);
printf("\n I read the config file \n");
printf("\n magx = %d magy = %d \n",magx,magy);
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) */
XtAppAddTimeOut(app_context, (unsigned long) MiliSeconds,
GetAllEvents, data);
/* this was also in mane */
while(unmapped?
(
XWindowEvent(dpy, win, (long)-1, &event), 1):
XCheckWindowEvent(dpy, win, (long)-1, &event))
{
switch(event.type)
{
case ConfigureNotify:
if(event.xconfigure.width != width[DST] ||
event.xconfigure.height != height[DST])
{
resize(event.xconfigure.width,
event.xconfigure.height);
}
break;
case ReparentNotify:
break; /* what do we do with it? */
case MapNotify:
unmapped = False;
break;
case UnmapNotify:
unmapped = True;
break;
case KeyRelease:
switch(XKeycodeToKeysym(dpy, event.xkey.keycode, 0)) {
case XK_Control_L:
case XK_Control_R:
scroll = 1;
break;
}
break;
case KeyPress:
switch(XKeycodeToKeysym(dpy, event.xkey.keycode, 0)) {
case XK_Control_L:
case XK_Control_R:
scroll = 10;
break;
case '+':
case '=':
if(!yzoom_flag) ++magx;
if(!xzoom_flag) ++magy;
xzoom_flag = yzoom_flag = False;
resize(width[DST], height[DST]);
set_title = True;
break;
case '-':
if(!yzoom_flag) --magx;
if(!xzoom_flag) --magy;
xzoom_flag = yzoom_flag = False;
if(magx < 1) magx = 1;
if(magy < 1) magy = 1;
resize(width[DST], height[DST]);
set_title = True;
break;
case XK_Left:
if(flipxy)
if(flipx)
ygrab += scroll;
else
ygrab -= scroll;
else
if(flipx)
xgrab += scroll;
else
xgrab -= scroll;
break;
case XK_Right:
if(flipxy)
if(flipx)
ygrab -= scroll;
else
ygrab += scroll;
else
if(flipx)
xgrab -= scroll;
else
xgrab += scroll;
break;
case XK_Up:
if(flipxy)
if(flipy)
xgrab -= scroll;
else
xgrab += scroll;
else
if(flipy)
ygrab += scroll;
else
ygrab -= scroll;
break;
case XK_Down:
if(flipxy)
if(flipy)
xgrab += scroll;
else
xgrab -= scroll;
else
if(flipy)
ygrab -= scroll;
else
ygrab += scroll;
break;
case 'x':
flipx = !flipx;
set_title = True;
break;
case 'y':
flipy = !flipy;
set_title = True;
break;
case 'z':
if(flipx^flipy^flipxy) {
flipx = !flipx;
flipy = !flipy;
}
flipxy = !flipxy;
resize(width[DST], height[DST]);
set_title = True;
break;
case 'w':
xzoom_flag = True;
yzoom_flag = False;
break;
case 'h':
yzoom_flag = True;
xzoom_flag = False;
break;
case 'd':
if(++delay_index >= NDELAYS)
delay_index = 0;
delay = delays[delay_index];
sprintf(title, "delay = %d ms", delay/1000);
XChangeProperty(dpy, win, XA_WM_NAME, XA_STRING, 8,
PropModeReplace,
(unsigned char *)title, strlen(title));
signal(SIGALRM, timeout_func);
alarm(2);
break;
case 'q':
exit(0);
break;
}
break;
case ButtonPress:
/*
#ifdef FRAME
xgrab = event.xbutton.x_root - width[SRC]/2;
ygrab = event.xbutton.y_root - height[SRC]/2;
#else
xgrab = event.xbutton.x_root;
ygrab = event.xbutton.y_root;
#endif
*/
/*DONT WORRY ABOUT BUTTON PRESSES
xgrab = event.xbutton.x_root;
ygrab = event.xbutton.y_root;
XDefineCursor(dpy, win, when_button);
buttonpressed = True;
*/
break;
case ButtonRelease:
/* DONT WORRY ABOUT BUTTON RELEASES
xgrab = event.xbutton.x_root - width[SRC]/2;
ygrab = event.xbutton.y_root - height[SRC]/2;
XDefineCursor(dpy, win, crosshair);
buttonpressed = False;
*/
break;
/* case MotionNotify:
Kieran was here again
xgrab = event.xmotion.x_root -
width[SRC];
ygrab = event.xmotion.y_root; - height[SRC]/8;
XDefineCursor(dpy, win, when_button);
break;
and stopped here */
}
/* trying XShmGetImage when part of the rect is
not on the screen will fail LOUDLY..
we have to veryfy this after anything that may
may modified xgrab or ygrab or the size of
the source ximage */
if(xgrab < 0)
xgrab = 0;
if(xgrab > WidthOfScreen(scr)-width[SRC])
xgrab = WidthOfScreen(scr)-width[SRC];
if(ygrab < 0)
ygrab = 0;
if(ygrab > HeightOfScreen(scr)-height[SRC])
ygrab = HeightOfScreen(scr)-height[SRC];
}
#ifdef XSHM
XShmGetImage(dpy, RootWindowOfScreen(scr), ximage[SRC],
xgrab, ygrab, AllPlanes);
#else
XGetSubImage(dpy, RootWindowOfScreen(scr),
xgrab, ygrab, width[SRC], height[SRC], AllPlanes,
ZPixmap, ximage[SRC], 0, 0);
#endif
/*
#ifdef FRAME
if(buttonpressed) { /* show the frame
DRAW_FRAME();
XSync(dpy, False);
}
#endif
*/
/* copy scaled lines from src to dst */
for(j = flipxy?width[SRC]:height[SRC]; --j >= 0; ) {
/* p1 point to begining of scanline j*magy in DST */
p1 = &ximage[DST]->data[ximage[DST]->xoffset +
j*magy*ximage[DST]->bytes_per_line ];
/* p2 point to begining of scanline j in SRC */
/* if flipy then line height[SRC]-1-j */
p2 = &ximage[SRC]->data[ximage[SRC]->xoffset +
(flipy?(height[SRC]-1-j):j)*ximage[SRC]->bytes_per_line
];
if(flipxy) {
int p2step = ximage[SRC]->bytes_per_line;
p2 = &ximage[SRC]->data[ximage[SRC]->xoffset
+ (flipy?j:(width[SRC]-1-j))];
if(flipx) {
p2 += p2step * (height[SRC]-1);
p2step = -p2step;
}
for(i = height[SRC]; --i >= 0;) {
c = *p1++ = *p2;
p2 += p2step;
for(k = magx; --k > 0; )
*p1++ = c;
}
}
else if(flipx) {
p2 += width[SRC];
for(i = width[SRC]; --i >= 0;) {
c = *p1++ = *--p2;
for(k = magx; --k > 0; )
*p1++ = c;
}
}
else {
for(i = width[SRC]; --i >= 0;) {
c = *p1++ = *p2++;
for(k = magx; --k > 0; )
*p1++ = c;
}
}
/* p1 point to begining of scanline j*magy in DST */
p1 = &ximage[DST]->data[ximage[DST]->xoffset +
j*magy*ximage[DST]->bytes_per_line ];
/* p2 points to begining of next line */
p2 = p1 + ximage[DST]->bytes_per_line;
/* duplicate that line as needed */
for(k = magy; --k > 0; ) {
#ifdef BCOPY
bcopy(p1, p2, width[DST]);
#else
memmove(p2, p1, width[DST]);
#endif
p2 += ximage[DST]->bytes_per_line;
}
}
#ifdef XSHM
XShmPutImage(dpy, win, gc, ximage[DST], 0, 0, 0, 0,
width[DST], height[DST], False);
#else
XPutImage(dpy, win, gc, ximage[DST], 0, 0, 0, 0, width[DST],
height[DST]);
#endif
if(set_title) {
if(magx == magy && !flipx && !flipy
&& !flipxy)
sprintf(title, "%s x%d", progname, magx);
else
sprintf(title, "%s X %s%d%s Y %s%d",
progname,
flipx?"-":"", magx,
flipxy?" <=>":";",
flipy?"-":"", magy);
XChangeProperty(dpy, win, XA_WM_NAME, XA_STRING, 8,
PropModeReplace,
(unsigned char *)title, strlen(title));
set_title = False;
}
/*
#ifdef TIMER
{
struct timeval current_time;
double DT;
gettimeofday(¤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
*/
}
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);
printf("\n I read the config file \n");
printf("\n magx = %d magy = %d \n",magx,magy);
}
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 */
// XtAppContext app_context;
XtPointer client_data;
unsigned long MiliSeconds = (unsigned long) 100;
/* 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) == 8) {
fprintf(stderr, "%s: can work only with 8 bits/pixel\n",
progname);
exit(1);
}
if(source_geom_mask & XNegative)
xgrab += WidthOfScreen(scr);
if(source_geom_mask & YNegative)
ygrab += HeightOfScreen(scr);
if(dest_geom_mask & XNegative)
xpos += WidthOfScreen(scr);
if(source_geom_mask & YNegative)
ypos += HeightOfScreen(scr);
/* printf("=%dx%d+%d+%d\n", width[DST], height[DST], xpos,
ypos); */
xswa.event_mask =
ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|PointerMotionMask;
xswa.event_mask |= StructureNotifyMask; /* resize etc.. */
xswa.event_mask |= KeyPressMask|KeyReleaseMask; /* commands
*/
xswa.background_pixel = BlackPixelOfScreen(scr);
win = XCreateWindow(dpy, RootWindowOfScreen(scr),
xpos, ypos, width[DST], height[DST], 0,
DefaultDepthOfScreen(scr), InputOutput,
DefaultVisualOfScreen(scr),
CWEventMask | CWBackPixel, &xswa);
XChangeProperty(dpy, win, XA_WM_ICON_NAME, XA_STRING, 8,
PropModeReplace,
(unsigned char *)progname, strlen(progname));
/*
XChangeProperty(dpy, win, XA_WM_NAME, XA_STRING, 8,
PropModeReplace,
(unsigned char *)progname, strlen(progname));
*/
set_title = True;
XMapWindow(dpy, win);
gcv.plane_mask = AllPlanes;
gcv.subwindow_mode = IncludeInferiors;
gcv.function = GXcopy;
gcv.foreground = WhitePixelOfScreen(scr);
gcv.background = BlackPixelOfScreen(scr);
gc = XCreateGC(dpy, RootWindowOfScreen(scr),
GCFunction|GCPlaneMask|GCSubwindowMode|GCForeground|GCBackground,
&gcv);
/*
#ifdef FRAME
gcv.foreground = AllPlanes;
gcv.plane_mask =
WhitePixelOfScreen(scr)^BlackPixelOfScreen(scr);
gcv.subwindow_mode = IncludeInferiors;
gcv.function = GXxor;
framegc = XCreateGC(dpy, RootWindowOfScreen(scr),
GCFunction|GCPlaneMask|GCSubwindowMode|GCForeground,
&gcv);
#endif
*/
/*
#ifdef TIMER
font = XLoadFont(dpy, "fixed");
#endif
*/
resize(width[DST], height[DST]);
/*
#ifdef FRAME
{
static char bitmap_data[] = { 0 };
static XColor col = { 0 };
Pixmap curs = XCreatePixmapFromBitmapData(dpy,
RootWindowOfScreen(scr), bitmap_data, 1, 1, 0, 0, 1);
when_button = XCreatePixmapCursor(dpy, curs, curs, &col,
&col, 0, 0);
}
#else
when_button = XCreateFontCursor(dpy, XC_ul_angle);
#endif
*/
when_button = XCreateFontCursor(dpy, XC_ul_angle);
crosshair = XCreateFontCursor(dpy, XC_crosshair);
XDefineCursor(dpy, win, crosshair);
/* Kieran was here */
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
9 Appendix B Source
Code for BlindPenguin Interface
#!/usr/X11/bin/wish
wm minsize . 300 50
wm title . "BlindPenguin"
set f [frame .menubar]
pack $f -fill x
menubutton $f.file -text File -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"}
$l add cascade -label "Set Colour.." -menu $l.2
set m2 [menu $l.2 -tearoff 1]
$m2 add check -label Red -variable Red -foreground red -command
{puts "The colour is set to red"}
$m2 add check -label Blue -variable Blue -foreground blue -command
{puts "The colour is set to blue"}
$m2 add check -label Green -variable Green -foreground green
-command {puts "The colour is set to green"}
$m2 add check -label White -variable White -foreground white
-command {puts "The colour is set to white"}
$l add command -label "Set Target"
$l add separator
$l add command -label "Hot Keys"
$p add command -label "Contents"
$p add separator
$p add command -label About -command about
proc set_size {var} {
global size
set size $var
}
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
}
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
# bind .fs.files <Return> {opfile}
list-out
global entered
} 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
# bind .fs.files <Return> {openentry [.fs.file.entry get]}
}
}
}
} else {
puts "file not found"
}
}
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
bind .fs.file.entry <Return> {openentry [.fs.file.entry
get]}
pack .fs.file -side top
}
proc list-out {} {
.fs.files insert end .
.fs.files insert end ..
foreach i [lsort [glob *]] {
.fs.files insert end $i
}
}
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
bind .fs.files <Return> {openentry $entry}
}
proc openfile {} {
global boxflag
set boxflag open
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
button .fs.okk -text "OK" -padx 5 -pady 5 -command {destroy .fs}
pack .fs.okk -side left -padx 5
list-box
}
proc saveas {} {
global boxflag
set boxflag savefile
toplevel .fs
wm title .fs "Save File:"
file-entry "Save File:"
button .fs.cancel -text "Cancel" -padx 5 -pady 5 -command {destroy
.fs}
pack .fs.cancel -side left -padx 5 -pady 5
button .fs.okk -text "OK" -padx 5 -pady 5 -command {destroy .fs}
pack .fs.okk -side left -padx 5
list-box
}
proc about {} {
toplevel .about -borderwidth 3
wm title .about "About BlindPenguin"
wm geometry .about 250x150
message .about.people -width 12c -text "BlindPenguin was
created by:
Catherine Teahan
&
Kieran O'Sullivan"
button .about.ok -text "OK" -command "destroy .about"
pack .about.people
pack .about.ok -padx 3m -pady 3m
}
<< Back to Contents
|