[comp.sys.atari.st] Changing Rez: What we _really_ want to know

dmb@TIS.COM (David M. Baggett) (12/31/89)

A bit of summary for those just tuning in:

I asked if you could change rez without the Bios.  Atari's Derek Mui
said "No."

Then Neil Forsyth asked if you could restore the desktop resolution
from within a function installed at GEM process termination vector 0x102.

Allan said:

    Yes.  I wrote an off-the-cuff article "all about terminate handlers" 
    on GEnie, then asked John Townsend to post it here.  I don't know if he
    has, but among other things, it says you can make BIOS and XBIOS calls
    but not GEMDOS calls.

Then Ken said:

    Neil asks if there are magic negative-offset line A variables which can be
    stuffed with appropriate values to fool GEM if an application changes res.

    The answer is: No.

    [AES has no published variables so you can't tell it about res changes]

    Derek Mui is very wise in the ways of the AES, and when he says there
    is no way, legal or illegal... believe him!

And now (me):

We believe!  We believe!

OK, OK, we know you can't tell AES and GEM about the rez change.  But
suppose we _don't care_ about GEM and AES and we just want the VDI/line A
to work correctly.  My problem isn't with GEM or AES, but rather with the
VT52 emulator.  Here's the scenario:

	1) Run program from (medium rez) GULAM.
	2) In program, change rez to low.  AES is screwed.  I don't care.
	3) Process termination occurs.  I switch back to medium rez
	   _without the Bios_.
	4) GULAM thinks the screen is only 40 columns wide.  Bummer.

Although Allan says you can call the Bios from within a routine installed
as process termination handler (at 0x102), I sure couldn't.  Adding the
single line

	Setscreen(-1L, -1L, 1);

causes my program to crash horribly.

The point is, I just want to be able to duplicate the functionality of
Setscreen without calling the Bios.  Either that, or I'd like to find
out what the secret is to calling Bios routines at process termination.

Ideas?

Thanks,
Dave Baggett
(dmb@TIS.COM)

P.S.> Could some kind soul please repost the "all about terminate handlers" 
	article here?  Thanks.

bro@eunomia.rice.edu (Douglas Monk) (01/04/90)

Thought I might drop in my 2 cents:

I typically write routines dostart() and doexit(exitval) in programs
which run in low res but for which I want to use 80 columns:

dostart saves the current resolution and if it is low res, changes
to high res and plays with the palette (and whatever other changes I
need, like turning on the cursor, etc.). Thus dostart contains a call
Setscreen(-1L, -1L, 1) as needed - and it doesn't crash.

doexit restores the original palette and resolution, and then calls
exit(exitval). Thus doexit contains a call Setscreen(-1L, -1L, 0) as
needed - and it doesn't crash.

I just call doexit then wherever I would be calling exit otherwise.
If the system crashes, I might be screwed up, but the worst would be to have
the parent wind up in the wrong resolution with funny colors, and that can
be fixed. But my programs do not crash :-) of course.

I use a program like this from my auto folder - but only use bios and xbios
calls in it.

Doug Monk

Disclaimer: These views are mine, not necessarily my organization's.

apratt@atari.UUCP (Allan Pratt) (01/04/90)

dmb@TIS.COM (David M. Baggett) writes:
>Although Allan says you can call the Bios from within a routine installed
>as process termination handler (at 0x102), I sure couldn't.  Adding the
>single line
>	Setscreen(-1L, -1L, 1);
>causes my program to crash horribly.

Well, this ought to work.  I don't know why it wouldn't.  Are you sure
it was just that line? I use it all the time. I have modified a ray
tracer so it shows its progress; it switches rez all over the place,
and switches back (successfully!) if terminated with ^C or something.

============================================
Opinions expressed above do not necessarily	-- Allan Pratt, Atari Corp.
reflect those of Atari Corp. or anyone else.	  ...ames!atari!apratt

roeder@sbsvax.UUCP (Edgar Roeder) (01/04/90)

In article <8912301917.AA19200@TIS.COM>, dmb@TIS.COM (David M. Baggett) writes:
> P.S.> Could some kind soul please repost the "all about terminate handlers" 
> 	article here?  Thanks.

Sorry, i don't have this document, but i have prepared another one. The file is
in Info-format, suitable for reading with GNU-emacs, but it should be readable
as text too.

Hope this helps!

	- Edgar

---------------------- cut here ------------------------
Info file: etv_term,    -*-Text-*-
produced by texinfo-format-buffer
from file: etv_term.txi

This file documents the TOS terminate handler etv_term and what you can
do with it.




File: etv_term  Node: Intro, Prev: Concept Index, Up: Top, Next: Top


The Terminate handler
*********************

Some  people on the  net   asked for documentation  about the  terminate
vector.  Here is what i have collected over the past few years.  Some of
the  details required a   lot of experimenting,  but  all  the presented
solutions work with all TOS versions (although undocumented features are
used).

        - Edgar


File: etv_term  Node: Top, Prev: Intro, Up: (dir), Next: Actions

When is the Terminate Handler called ?
======================================

The logical vector #0x102 "etv_term" at address 0x400 is called by
`Pterm()' before a program is terminated.  This can have several
causes.  Either

  1. an explicit call to

        * the gemdos functions `Pterm0()', `Pterm()' or `Ptermres()' or

        * `trap #2' with argument 0 in register D0 or

  2. another program (a resident monitor like templmon or a shell or an
     accessory) directly calls one of the above functions or

  3. `Pterm()' (in early TOS versions `Pterm0()') is called
     from one of the exception routines (after some bombs or mushrooms
     have been drawn) or

  4. `Pterm(-32)' is called when ^C is pressed during screen-I/O

Since in cases 2--4 above the user program normally has not
initiated the program termination, the terminate vector is the last
(and only) chance to do some cleanup work before execution is
finished.

* Menu:

* Actions::             What can a terminate handler do
* Call::                How the handler is called
* Installation::        How to install a handler
* Concept Index::


File: etv_term  Node: Actions, Prev: Top, Up: Top, Next: Call

What you want to do in the Handler
==================================


Before Program Termination (cases 2 + 3)
----------------------------------------

   * give some good-bye message to the user

   * flush buffered output streams (files are closed after the call to
     etv_term by TOS)

   * deinstall modified system vectors (*Note Installation::)

   * modify the return code to signal an error condition to the calling
     program (*Note Return Code::)

   * prevent termination (probably after asking the user) and go to a
     safe place to restart the program (for example the prompt in an
     interpreter) or enter a debugger (in case 3 above)



If ^C is pressed (case 4)
-------------------------

   * ignore it completely (*Note Ignore ^C::)

   * ask the user wether to ignore it or to terminate the program

   * go to a safe place and cancel the current action of the program


So what we need to know about terminate handlers are the
restrictions, which TOS-functions can be called, how we can access
the parameter to `Pterm' and how we can resume execution at
the place where ^C was pressed.

* Menu:

* Restrictions::	What can be called from a handler
* Return Code::		access the program's return code
* Ignore ^C::           avoid program termination


File: etv_term  Node: Call, Prev: Actions, Up: Top, Next: Installation

How is the handler called ?
===========================

The etv_term vector is called via `jsr' in supervisor mode.  The
calling routine (`Pterm()') was called in C with one parameter (the
return code for the program) on the stack (*Note Return Code::).


File: etv_term  Node: Installation, Prev: Call, Up: Top, Next: Concept Index

How can i install my own routine ?
==================================

If you are in supervisor mode (after a call to `Super()' from user
mode with a parameter != 1L or in a routine executed by
`Supexec()') you can directly set the variable at `0x400' to
the address of your routine:

     *((void (**)()) 0x400) = own_routine;

More portable and the recommended way is a call to the Bios function #5
(`Setexc()'):

     old_routine = (void (*)()) Setexc(0x102, own_routine);

The terminate handler itself has no parameter and no return value:

     void own_routine(void);


File: etv_term  Node: Restrictions, Prev: Actions, Up: Actions, Next: Return Code

What can be done in a terminate handler ?
=========================================

This depends on what should happen after the handler has finished.  We
have three cases:

  1. cleanup and terminate execution of the program

  2. resume execution at some safe point (eg. top level of program)

  3. ignore the termination request (*Note Ignore ^C::)

In case 1 we should restore the original terminate handler before we
leave our program.  This can be done like the installation with

     Setexc(0x102, old_routine);

If our terminate handler is not removed before other programs are
started, the handler will also be called when these other programs are
about to be terminated.  In a shell this can be used to control the
behavior of other programs with respect to ^C.

In case 2 we should return to user mode since this is the normal mode of
execution for user programs.  This can be done with a call to the gemdos
function `Super()' after calling `longjmp()'.  A sample code
fragment could be this:

     #include <setjmp.h>

     jmp_buf global_exit;
     long stackpointer;

     void
     terminate_handler(void)
     {
             ...
             longjmp(global_exit,1);
     }

     toplevel_routine()
     {
             int ret;

             ...
             stackpointer = Super(0L);
             ret = setjmp(global_exit);
             Super(stackpointer);
             switch(ret) {
                     ...
             }
             ...
     }

In the cases 1 and 2 you may call any routines (including gemdos, bios,
xbios, aes and vdi) since we leave the normal flow of execution anyway
and so no information needs to be preserved.  But you have to be
careful, if `Pterm()' was called as the result of a processor
exception (eg.  bus error): probably some internal data structures are
in an unstable state.

In case 3 it is important to know where the `Pterm()'-call
happened.  As a thumb rule you can call xbios or bios safely from any
place.  But if you have switched to user mode (and want to decide
between cases 1, 2 or 3 later), you cannot use the old userstack.  Some
people might prefer using the user stack in supervisor mode too.  Also
gemdos - if called from user mode - uses a part of the user stack below
the user stack pointer (~40 bytes) to save some registers.  And this is
just the case if `Pterm()' was called via `trap #1'.


File: etv_term  Node: Return Code, Prev: Restrictions, Up: Actions, Next: Ignore ^C

How can the argument to `Pterm()' be accessed ?
===============================================

`Pterm()' is called from C.  The argument to `Pterm()' can
thus be found on the stack.  Since it is also programmed in C we can
access the parameter with the help of processor register A6 the
frame pointer in C.  `Pterm()' calls (*etv_term)() directly.  At
the entry of our routine A6 still points to the right frame.  So
we can get the return value for the program (the parameter of
`Pterm()') simply as the word 8 bytes above the frame pointer (as
usual in C).

The value of the return code can be read and compared against -32 to
decide wether ^C was pressed (or `Pterm(-32)' called).  So we can
distinguish the several causes for a `Pterm()'-call.  We can also
change this value on the stack to change our program's return value.

If you are using KAOS-TOS (some patches to Blitter-TOS from the german
magazine c't - don't ask me about it since TOS 1.4 is certainly better)
the return value if ^C was pressed is different.  Here it is -68 if ^C
was pressed and -69 if an exception occured.


File: etv_term  Node: Ignore ^C, Prev: Return Code, Up: Actions

How can ^C be ignored ?
=======================

Several solutions exist for this problem.  One is to use only functions
ignoring ^C for screen-I/O (`Crawio()', `Crawcin()' or bios
functions).

But you can also use the terminate-vector for this task.  TOS was
programmed in C and the compiler did not know that `Pterm()' is not
supposed to return to the calling routine.  Now if `Pterm()'
returns no surprising effects are observed and the execution goes on as
if ^C was not pressed.  But remember that this is not guaranteed to work
to all eternity (but it works from TOS 1.0 - 1.4).

To ignore ^C all you have to do in the terminate handler is to check for
the return value -32 and before the rts instruction do an extra
`unlk a6' to pop the frame of the C-function `Pterm()'.  The
program will continue as if ^C was never touched.



File: etv_term  Node: Concept Index, Prev: Installation, Up: Top, Next: Intro

Concept Index
*************


* Menu:

* access return code: Return Code.
* actions: Actions.
* call: Call.
* calling conditions: Top.
* etv_term: Top.
* get address of old handler: Installation.
* ignore ^C: Ignore ^C.
* install a new handler: Installation.
* installation: Installation.
* restrictions: Restrictions.
* resume execution: Ignore ^C.
* return code: Return Code.
* stackpointer: Restrictions.
* switch to user mode: Restrictions.
* terminate handler: Intro.
* terminate vector: Top.
* test return code: Return Code.


Tag table:
Node: Intro184
Node: Top619
Node: Actions1810
Node: Call3154
Node: Installation3484
Node: Restrictions4139
Node: Return Code6592
Node: Ignore ^C7771
Node: Concept Index8674

End tag table
-- 

Mail:  Edgar R\"oder			E-Mail: roeder@cs.uni-sb.de
       Liesbet-Dill-Stra\ss e 3
D-6602 Dudweiler                               -o-   -o-
       W-Germany                                   ^
Phone: 06897/74643                     	       	 '---'

apratt@atari.UUCP (Allan Pratt) (01/06/90)

In article <2009@sbsvax.UUCP>, roeder@sbsvax.UUCP (Edgar Roeder) writes
stuff about using the terminate handler.

This "hacker's approach to terminate handling" is one of the scariest
things I have ever seen.  The terminate handler is meant to let you
restore state which you might have changed before you terminate. It is
not intended to let you off the terminate hook, so to speak.  You
should always return normally from a terminate handler, using rts.  If
you don't want ^C to stop your program, you should not use cooked
GEMDOS calls.  If you don't want a bus error to stop your program, you
should install an exception handler of your own.  The terminate
vector just isn't meant for these things.

Correct usage of the terminate vector includes calling BIOS to restore
the state of the screen and keyboard, un-installing vectors like timers
and interrupts you've installed, and the like.  You can't make GEMDOS
calls from inside a terminate handler, or call something which might
make GEMDOS calls, like flushing output streams or even closing
workstations which are really metafiles.

I use terminate handlers in graphics programs which change the rez or
palette: the terminate handler changes back to the original rez and
palette.  Doing other stuff is asking for trouble: most especially, you
will die horribly if run under MetaDOS or some future multi-tasking
TOS. (MetaDOS is a filesystem call dispatcher placed between user
programs and foreign filesystems like CD-ROMs and (eventually)
networks.)

Please, please, just use the terminate vector to clean up after yourself
and RTS, and save yourself the grief later on.

(By the way, the original article has the wrong address for the
terminate vector: it's at 0x0408, not 0x0400.  The exception number,
0x0102, is right.)

============================================
Opinions expressed above do not necessarily	-- Allan Pratt, Atari Corp.
reflect those of Atari Corp. or anyone else.	  ...ames!atari!apratt

t68@nikhefh.nikhef.nl (Jos Vermaseren) (01/09/90)

In a previous article A. Pratt comments about that the terminate
vector should only be used to terminate. There are exceptions to
this!!!!!!!!
A good editor that runs from a good shell (a shell which has installed
an emergency break, so that a child process can be stopped) should
not be killable. See for instance vi under UNIX. Ctrl-C won't kill
it.
The problem is that Atari has no proper way to kill a child process,
so kludge methods has to be made to make GEMDOS believe that the
child itself asks for termination. After that there are some
children that don't want to be killed. This can be done rather
easily. Make a routine and install it as termvector. The whole
routine reads:
	unlk a6
	rts
This causes a return from the term routine in GEMDOS before
it starts cleaning up (I can hear Allan already.....).
Of course it would be much better if Atari would come up with
some standards on how to do this in a conventional way, together
with a proper way to kill processes that don't produce output
via GEMDOS. Using the environment seems to clumsy because it
has to be checked before the kill command (Pterm) is given,
which means that the environment has to be checked from an interrupt
routine. That isn't nice.
A reserved variable in the low memory globals would be better.
It the variable is zero, the process may be killed.
A second variable/flag could indicate whether the Atari is going
to kill a process safely (for instance only when the PC points to
an address inside the memory of the process).
Sounds like some work for Allan to make up a good scheme.

Jos Vermaseren
t68@nikhefh.nikhef.nl