HFS Transfer DA
Volume Number: | | 4
|
Issue Number: | | 10
|
Column Tag: | | Programmer's Workshop
|
HFS Transfer DA 
By Clifford Story, Jacksonville, FL
Transfer DA
The Transfer DA is a small desk accessory that adds a Transfer menu to any application that supports desk accessories.
When Apple killed the Minifinder, my work habits were shattered. I tried Oasis, a Finder substitute, for a while but I didnt like the way the working directory was changed by launching a program; that is, if I launched, say, Macwrite, and opened a file, the sfget dialog came up showing the directory that contained Macwrite, rather than the one I had been working in. I had to navigate through folders to return to my work files.
Transfer solves that problem. Now, I work on the desktop but when I want to switch from one application to another, I use Transfer. The working directory is unaffected, and I skip the Finder.
This DA illustrates some interesting programming techniques: it is a segmented DA; it does an HFS search of an entire hard disk; it uses the iaznotify hook; and it creates resources with TML Pascal (a subject which came up in the May Mac Tutor letters column).
How the DA Works
When the DA is launched for the first time, it starts at the root of the disk which contains the System folder, and does an HFS catalog of the entire disk. It finds each application on the disk, and stores the name and directory id in a special resource FILE called Transfer data, in the System folder. Henceforth, it uses this file to build the menu (the HFS catalog takes a little time - about 30 seconds on my 20 meg hard disk - so you wouldnt want to go through it every time).
When an application is selected from the Transfer menu, the applicationss name and directory id are copied into the DAs global data record, and the address of a special launch routine is installed in the iaznotify low-memory global. The transfer may be immediate or delayed; in the first case, the DA will call ExitToShell; in the second, it will wait for the application to quit normally.
When the application quits, the Mac will prepare to launch the next application (usually the Finder). As one of the last steps in this process, it will call InitApplZone to initialize the heap. This trap will, in turn, call the routine whose address is stored in iaznotify - the DAs launch routine. This routine simply writes the name of the desired application to curappname (another low-memory global), and sets the directory to the proper directory. The Mac will then go ahead and launch the application. Sort of a cowbird launch, you might say!
DA Segmentation
This is a small DA, a bit over 6K. so why is it segmented? Am I just showing off?
Before InitApplZone calls the launch procedure, the Mac closes all resource files. That means that if the launch procedure is in a resource (e.g., the DRVR resource), it will be left in a free block. I dont feel very secure when executing code in a free block! The solution is to detach the resource; the block will remain allocated until InitApplZone actually cleans up the heap - which happens after it calls the launch routine.
But if the launch routine is part of the DRVR resource, this means detaching the DRVR. If we do that, well never know about menu selections, since the desk Manager needs a DRVR resource to pass DA events to. So the launch routine has to be in a separate segment.
The launch routine is in a PACK resource. The DRVRs open routine loads and locks the PACK, and records its address. When it wants to call a routine from the PACK, it puts a routine selector into register D0 and jumps to the PACK. At the top of the PACK is a jump table that branches to the routine corresponding to the selector.
There are other ways to segment a DA; this is just the way I did this one.
Creating Resources with TML Pascal
Since it came up in the May letters column, let me say a few words on this topic. Its really simple, once you know the (undocumented) trick. Its all in the link control file.
For example, here is a link control file to create an LDEF:
!fastlist
/codetype LDEF 1001 Fast List 32
list
pas$library
macintfglue
/end
The first line is the linkers start point - the name of the
main routine. The second line defines the resource type. This will be an LDEF, resource id 1001, named Fast List, purgeable. The next three lines are the object files to link, in the order you want them to be linked. And the final line means just what it says.
The code itself should be compiled as a unit, and most resource types will require that the main routine appear at the very top of the resource.
The two resources in this program are linked a bit differently, the DRVR resource because the resource definition requires a DA header at the top, and the PACK resource because I want a jump table at the top. The fact that I left out the first line and got away with it suggests that it may not be necessary.
Using the DA
I have tested this DA with every application on my hard disk (at the time I wrote it). A number of applications require the delayed transfer; for example, any program that uses scratch files. I found only one that would not work at all. Can you guess? Thats right - Microsoft Word.
Transfer vs. Multifinder
I leave as an exercise for the reader the problem of making this DA work with the Multifinder. A more fruitful project might be converting the DA to an FKEY.
;***************************************
;
;types.asm
;--------
;
;(c) 1987, 1988 Attic Software
;
;record formats for Transfer
;
;***************************************
;***************************************
;wrecord
;***************************************
WRthemenuequ 0; MenuHandle
WRresfactorequ 4; integer
WRsysdir equ 6; long
WRpackaddr equ 10; Ptr
WRlaunchpath equ 14; long
WRlaunchaddr equ 18; long
WRiazaddrequ 22; long
WRtheevent equ 26
; EventRecord
WRthenameequ 42; Str255
WRtheblock equ 298; hfs block
;***************************************
(***************************************
types.pas
--------
(c) 1987, 1988 Attic Software
Pascal declarations for Transfer
***************************************)
unit types;
(**************************************)
interface
(**************************************)
uses macintf, hfs;
(**************************************)
type
logical= boolean;
long = longint;
shortpointer = ^integer;
longpointer= ^long;
(***************************************
Program constants consist of (1) DA message types (most of which are
not used in Transfer); (2) low-memory globals; and (3) resource ids.
***************************************)
const
accEvent = 64;
accRun = 65;
accCursor= 66;
accMenu= 67;
accUndo= 68;
accCut = 70;
accCopy= 71;
accPaste = 72;
accClear = 73;
goodbyekiss= -1;
curappname = $910;
erik = $4552494B;
findername = $2E0;
iaznotify= $33C;
sysmap = $A58;
menunum= 1001;
aboutitem= 1;
builditem= 2;
finderitem = 4;
errordialog= 1001;
aboutdialog= 1002;
builddialog= 1003;
delaydialog= 1004;
packnum= 1001;
stringnum= 1001;
datastart= 1000;
(***************************************
A deskrecord is simply an applications name and directory. We need
to know both of these to transfer to that application.
An marray is an array of deskrecords, along with the arrays size.
Finally, a wrecord is the DAs global data. The last three fields
(theEvent, thename and the varient parameter block) are just scratch
areas, used by various routines as local variables. Putting them here
keeps them off the stack.
***************************************)
type
deskrecord = record
dirid : long;
name : Str255;
end;
deskpointer= ^deskrecord;
deskhandle = ^deskpointer;
marray = record
count : integer;
data : array
[0..1000] of deskhandle;
end;
arraypointer = ^marray;
arrayhandle= ^arraypointer;
wrecord= packed record
theMenu: MenuHandle;resfactor: integer; sysdir
: long;packaddr : Ptr;
launchpath : long; launchaddr : long;
iazaddr: long;
theEvent : EventRecord;
thename: Str255;case integer of 1 : (infoblock
: CInfoPBRec);
2 : (hblock
: HParamBlockRec);
3 : (wdblock
: WDPBRec);
end;
wpointer = ^wrecord;
(**************************************)
implementation
(**************************************)
end.
(**************************************)
;***************************************;
;drvrhead.asm
;------------
;
;(c) 1987, 1988 Attic Software
;
;header for DRVR segment of
;Transfer.
;
;***************************************
;***************************************
;includes
;***************************************
includesysequ.d
includetypes.asm
;***************************************
;imported routines
;***************************************
xref open
xref ctl
;***************************************
;exported routines
;***************************************
xdef initglobals
xdef setdir
;***************************************
;
;The canonical DA header. This is a
;modified version of the DA header
;supplied with TML Pascal.
;
;***************************************
dc.w $0400 ; flags
dc.w $0000 ; servicedc.w $ffff ; mask
dc.w $0000 ; menuID dc.w ornopen ; open dc.w orndone ; prime
dc.w ornctl; Control
dc.w orndone ; Status
dc.w ornclose ; close
;***************************************
dc.w (c) 1987, 1988
dc.w Attic Software
dc.w All rights reserved
;***************************************
ornopen
movem.lA0/A1,-(SP)
move.l A1,-(SP) ; device
move.l A0,-(SP) ; param jsr open
movem.l(SP)+,A0/A1
move.l jiodone,-(SP)
rts
ornctl
movem.lA0/A1,-(SP)
move.l A1,-(SP) ; device
move.l A0,-(SP) ; param jsr ctl
movem.l(SP)+,A0/A1
move.l jiodone,-(SP)
rts
ornclose
orndone
move.l jiodone,-(SP)
rts
;***************************************
;
;The routine loader moves a selector
;into D0, and the address of the
;PACK segment into A0. It then
;jumps to the pack (the pack begins
;with a jump table).
;
;***************************************
initglobals
move.w #0,D0
bra.w loader
setdir
move.w #4,D0
bra.w loader
loader
movea.l4(SP),A0 ; globals
movea.lWRpackaddr(A0),A0
jmp (A0)
;***************************************
(***************************************
drvr.pas
--------
(c) 1987, 1988 Attic Software
Pascal routines for DRVR segment of Transfer
***************************************)
unit drvr;
(**************************************)
interface
(**************************************)
uses macintf, hfs, types;
(**************************************)
implementation
(**************************************)
procedure initglobals
(globals : wpointer); external;
function setdir
(dirid : long;
globals : wpointer) : OSErr; external;
(***************************************
drawscreen
----------
This routine draws the About message in its rectangle.
***************************************)
procedure drawscreen(
theWindow : WindowPtr;
theitem : integer);
var
theType: integer;
theHandle: Handle;
thebox : Rect;
begin
SetPort(theWindow);
GetFNum(monaco, theType);
TextFont(theType);
TextSize(9);
GetDItem(theWindow, theitem, theType,
theHandle, thebox);
theHandle := GetResource(INFO,
GetWRefCon(theWindow));
HLock(theHandle);
TextBox(theHandle^, GetHandleSize(theHandle),
thebox, 0);
HUnlock(theHandle);
end;
(***************************************
transalert
----------
I want all the alerts to identify their source, so I made them dialogs,
in titled windows, and wrote this routine to imitate Alert.
This is not the best way to do it; for one thing, a titled window should
be dragable. Modal dialogs, therefore, should not have titles. I should
have simply added a distinctive icon to the item lists.
Since this is the first use of the resfactor field of the global record,
its as good a place as any to explain it. This DA is given a formal
resource id of 16, and all its owned resources are numbered accordingly.
The Font/DA Mover will change all these numbers when it installs the
DA. resfactor, which is computed in the open routine, below, is
the correction factor that converts the hard-coded resource ids to the
actual ids in use.
Note that resfactor doesnt convert the formal ids; it actually converts
the constants defined in the types.pas unit, which are nice positive
numbers, equal to the resource ids plus 16872.
Theres a little bit of code to install the previous procedure if this
is the About dialog.
***************************************)
procedure transalert
(dialognum : integer;
globals : wpointer);
var
savedport: GrafPtr;
theDialog: DialogPtr;
therecord: DialogRecord;
theType: integer;
theHandle: Handle;
thebox : Rect;
choice : integer;
begin
with globals^ do begin
GetPort(savedport);
theDialog := GetNewDialog(dialognum
+ resfactor, @therecord,
pointer(-1));
SetPort(theDialog);
if dialognum = aboutdialog then
begin
GetDItem(theDialog, 2, theType,
theHandle, thebox);
SetDItem(theDialog, 2, theType,
Handle(@drawscreen), thebox);
SetWRefCon(theDialog, dialognum
+ resfactor);
end;
InitCursor;
ShowWindow(theDialog);
repeat
ModalDialog(nil, choice);
until choice = ok;
CloseDialog(theDialog);
SetPort(savedport);
end;
end;
(***************************************
errordisplay
------------
This routine displays error messages. The texts of the messages are
in a string list.
***************************************)
procedure errordisplay
(appnum, sysnum, resnum : long;
globals : wpointer);
var
string1: Str255;
string3: Str255;
string4: Str255;
begin
InitCursor;
with globals^ do begin
GetIndString(string1, resfactor
+ stringnum, resnum);
if string1 = then
string1:= An error has occurred!;
NumToString(appnum, string3);
NumToString(sysnum, string4);
ParamText(string1, ,
string3, string4);
SysBeep(10);
transalert(errordialog, globals);
end;
end;
(***************************************
getapps
------
Here it is - the only recursive routine I have ever been forced to use!
(Recursion is overrated; it is, in my opinion, better to avoid recursion,
if you can do it in a natural fashion. Loops are easier to read, and
generally more efficient.)
This routine is passed a directory id, the count of objects (files and
folders) in that directory, and the volume reference number. It indexes
through the directory with PBGetCatInfo.
If the object is another directory, it calls itself with that directorys
id and count.
If the object is a file, and if the file is an application, it records
its name and the directory id in the application array.
***************************************)
procedure getapps
(thedir : long; thecount : integer;
thevol : integer;
theHandle : arrayhandle;
globals : wpointer);
label
100;
var
index : integer;
anerror: integer;
theDialog: DialogPtr;
jndex : integer;
thelength: integer;
thedesk: deskhandle;
begin
with globals^ do begin
for index := 1 to thecount do
with infoblock do begin
thename := ;
ioCompletion := nil;
ioNamePtr := @thename;
ioVRefNum := thevol;
ioFDirIndex := index;
ioDrDirID := thedir;
anerror:= PBGetCatInfo(@infoblock,
false);
if anerror <> noErr then begin
errordisplay(101, anerror,
2, globals);
goto 100;
end;
if BitAnd(ioFlAttrib, $10) = $10 then
getapps(ioDrDirID, ioDrNmFls,
thevol, theHandle, globals)
else if ioFlFndrInfo.fdType
= APPL then begin
jndex := theHandle^^.count+ 1;
theHandle^^.count := jndex;
SetHandleSize
(Handle(theHandle),
5 * (jndex + 1));
HLock(Handle(theHandle));
with theHandle^^ do
while (IUCompString(thename,
data[jndex - 1]^^.name) < 0)
and (jndex > 1) do begin
data[jndex]:= data[jndex - 1];
jndex := jndex - 1;
end;
thelength:= 10 + length(thename);
thelength:= 2 * (thelength div 2);
theHandle^^.data[jndex]
:= deskhandle
(NewHandle(thelength));
with theHandle^^.data[jndex]^^
do begin
dirid := thedir;
name := thename;
end;
HUnlock(Handle(theHandle));
end;
100: end;
end;
end;
(***************************************
walktree
--------
This routine catalogs all the applications on a given disk.
It first puts up a dialog, telling the user whats going on.
It next calls PBGetCatInfo for the root directory (directory id =
2), to get the number of objects in the root. Then it calls getapps
to walk the HFS tree recursively.
The collected data is written to the current resource file (Transfer
Data, in the System folder), and the dialog is dismissed.
***************************************)
procedure walktree
(thevol : integer;
theHandle : arrayhandle;
globals : wpointer);
var
savedport: GrafPtr;
theDialog: DialogPtr;
therecord: DialogRecord;
index : integer;
anerror: integer;
begin
with globals^ do begin
GetPort(savedport);
theDialog := GetNewDialog(resfactor
+ builddialog, @therecord,
pointer(-1));
SetPort(theDialog);
ShowWindow(theDialog);
DrawDialog(theDialog);
with infoblock do begin
ioCompletion := nil;
ioNamePtr := nil;
ioVRefNum := thevol;
ioFDirIndex := 0;
ioDrDirID := 2;
end;
anerror := PBGetCatInfo(@infoblock,
false);
if anerror <> noErr then
errordisplay(102, anerror,
2, globals)
else
getapps
(2, infoblock.ioDrNmFls,
thevol, theHandle, globals);
end;
HLock(Handle(theHandle));
with theHandle^^ do
for index := 1 to count do begin
AddResource
(Handle(data[index]), .Trn,
datastart + index,
data[index]^^.name);
SetHandleSize
(Handle(data[index]), 4);
end;
HUnlock(Handle(theHandle));
CloseDialog(theDialog);
SetPort(savedport);
end;
(***************************************
buildmenu
--------
This routine assembles the necessary data, and builds the Transfer menu.
It finds the volume reference number of the disk with the System folder,
sets the directory to the System folder, and opens or creates the Transfer
Data file in that directory.
If this file lacks a header resource (whether because it was just created,
or because it has been corrupted), then it must be rebuilt, with walktree.
Then the menu is built. The menu resource is loaded, and the fourth
item set to the name of the current Finder. The remainder of the menu
is copied from the resource file.
***************************************)
procedure buildmenu(globals : wpointer);
label
100;
var
thepointer : shortpointer;
thevolume: integer;
theres : integer;
theHandle: arrayhandle;
index : integer;
jndex : integer;
thedesk: Handle;
theID : integer;
theType: ResType;
anerror: integer;
begin
with globals^ do begin
thepointer := shortpointer(sysmap);
anerror := GetVRefNum
(thepointer^, thevolume);
if anerror <> noErr then begin
errordisplay(103, anerror,
3, globals);
goto 100;
end;
anerror := setdir(sysdir, globals);
if anerror <> noErr then begin
errordisplay(104, anerror,
3, globals);
goto 100;
end;
thename := Transfer Data;
theres := OpenResFile(thename);
if ResError = fnfErr then begin
CreateResFile(thename);
theres := OpenResFile(thename);
end;
if ResError <> noErr then begin
errordisplay(105, ResError,
3, globals);
goto 100;
end;
theHandle
:= arrayhandle
(Get1Resource(.Trn,
datastart));
if theHandle = nil then begin
theHandle := arrayhandle
(NewHandle(6));
theHandle^^.count := 0;
theHandle^^.data[0]
:= deskhandle(NewHandle(16));
AddResource(Handle(theHandle),
.Trn, datastart, );
walktree(thevolume,
theHandle, globals);
SetHandleSize
(Handle(theHandle), 2);
end;
theMenu := GetMenu(resfactor
+ menunum);
BlockMove(Ptr(findername),
Ptr(@thename), 16);
SetItem(theMenu, 4, thename);
jndex := 1;
for index := 1 to theHandle^^.count
do begin
thedesk := Get1Resource(.Trn,
datastart + index);
if thedesk <> nil then begin
AppendMenu(theMenu, .Trn);
GetResInfo(thedesk, theID,
theType, thename);
SetItem(theMenu, jndex + 4,
thename);
jndex := jndex + 1;
end;
end;
InsertMenu(theMenu, 0);
DrawMenuBar;
CloseResFile(theres);
100:
end;
end;
(***************************************
systemvol
--------
This routine is more or less straight out of Tech Note 77, pages 3 and
4. It returns a working directory reference number for the System folder,
suitable for use in file system calls.
Step one is to find the volume reference number of the volume that holds
the System folder. sysmap is the file reference number of the System
file (an open file), so GetVRefNum will find the volume refence number
of the System file and, of course, the System folder.
Step two is to get the directory id, with a call to PBHGetVInfo.
The directory id is returned in the ioVFndrInfo[1] field of the HParamBlockRec.
Finally, PBOpenWD will return the System folders working directory
reference number, which can be used as a volume reference number in file
system calls.
***************************************)
function systemvol
(globals : wpointer) : integer;
var
thepointer : shortpointer;
thevolume: integer;
anerror: integer;
begin
with globals^ do begin
thepointer := shortpointer(sysmap);
anerror := GetVRefNum(thepointer^,
thevolume);
with hblock do begin
ioNamePtr := nil;
ioVRefNum := thevolume;
ioVolIndex := 0;
end;
anerror := PBHGetVInfo(@hblock,
false);
with wdblock do begin
ioWDDirID
:= hblock.ioVFndrInfo[1];
ioNamePtr := nil;
ioVRefNum := thevolume;
ioWDProcID := erik;
end;
anerror := PBOpenWD(@wdblock,
false);
systemvol := wdblock.ioVRefNum;
end;
end;
(***************************************
rebuildmenu
----------
If the Rebuild menu item is chosen from the menu, or an application
is chosen which cant be found, Transfer will rebuild the menu from scratch.
It does this by deleting the Transfer Data file, and calling buildmenu.
***************************************)
procedure rebuildmenu
(globals : wpointer);
var
anerror: integer;
begin
with globals^ do begin
DeleteMenu(resfactor + menunum);
ReleaseResource(Handle(theMenu));
anerror := FSDelete
(Transfer Data,
systemvol(globals));
buildmenu(globals);
end;
end;
(***************************************
dofinder
--------
If the Finder item is chosen from the menu, then no transfer is desired,
so restore the iaznotify hook to the value it held when Transfer was
launched. (This isnt quite right, since something besides Transfer may
have changed it since then. but I dont see any way to correct for that...)
If the option key is down, do nothing else. Otherwise, do an immediate
transfer by calling ExitToShell.
***************************************)
procedure dofinder(globals : wpointer);
var
thepointer : longpointer;
begin
with globals^ do begin
thepointer
:= longpointer(iaznotify);
thepointer^ := iazaddr;
if GetNextEvent(0, theEvent) then
;
if BitAnd(theEvent.modifiers,
optionKey) = 0 then
ExitToShell;
end;
end;
(***************************************
clickmenu
--------
The first few menu choices are handled by routines above.
If an application is chosen, we need to get (1) the applications name,
and (2) its directory. The name is easy; its on the menu. To get the
directory, we have to go back to the Transfer Data file.
Once we have the applications directory, the next thing to do is to
make sure its there. Transferring to a non-existent application will
cause a system bomb.
If everything is ok, then if the option key is down, prepare to do a
delayed transfer; otherwise, do an immediate transfer by calling ExitToShell.
***************************************)
procedure clickmenu
(itemchoice : integer;
globals : wpointer);
label
100;
var
theres : integer;
thedesk: deskhandle;
theinfo: FInfo;
thepointer : longpointer;
anerror: integer;
begin
with globals^ do begin
case itemchoice of
aboutitem:
transalert(aboutdialog,
globals);
builditem:
rebuildmenu(globals);
finderitem :
dofinder(globals);
otherwise
thepointer := longpointer
(iaznotify);
thepointer^ := launchaddr;
anerror := setdir(sysdir,
globals);
if anerror <> noErr then begin
errordisplay(106, anerror,
3, globals);
goto 100;
end;
theres := OpenResFile
(Transfer Data);
if ResError <> noErr then begin
rebuildmenu(globals);
goto 100;
end;
GetItem(theMenu,
itemchoice, thename);
thedesk := deskhandle
(Get1NamedResource(.Trn,
thename));
DetachResource(Handle(thedesk));
CloseResFile(theres);
if thedesk = nil then begin
rebuildmenu(globals);
goto 100;
end;
anerror := setdir
(thedesk^^.dirid, globals);
if anerror <> noErr then begin
rebuildmenu(globals);
goto 100;
end;
anerror := GetFInfo(thename,
0, theinfo);
if anerror <> noErr then begin
rebuildmenu(globals);
goto 100;
end;
launchpath := thedesk^^.dirid;
if GetNextEvent(0, theEvent) then
;
if BitAnd(theEvent.modifiers,
optionKey) = 0 then
ExitToShell
else begin
ParamText(thename,
, , );
transalert(delaydialog,
globals);
end;
end;
100: HiliteMenu(0);
end;
end;
(***************************************
open
----
This is the canonical DA open routine. If the DA has already been opened,
device.dCtlMenu will be nonzero; do nothing. Otherwise, Allocate the
globals, and fill in a few fields. Of particular interest is the calculation
of resfactor by the magic formula $BFE0 - 32 * dCtlRefNum - 1000.
This, assuming I have given the DA the formal resource id of 16, allows
be to refer to owned resources by ids from 1000 to 1031, adding resfactor
to convert to the actual values.
Next, load and detach the PACK segment. And lock it; its created locked
but why take chances?
Finally, call initglobals to fill in the rest of the fields, and buildmenu
to set up the menu.
***************************************)
procedure open(var device : DCtlEntry;
var block : ParamBlockRec);
var
globals: wpointer;
packhandle : Handle;
begin
if device.dCtlMenu = 0 then begin
globals := wpointer
(NewPtr(sizeof(wrecord)));
if globals <> nil then
with globals^ do begin
with device do begin
resfactor := $BFE0
- 32 * dCtlRefNum - 1000;
dCtlMenu := resfactor
+ menunum;
dctlwindow := nil;
dCtlStorage
:= Handle(globals);
end;
packhandle := GetResource
(PACK, resfactor + packnum);
DetachResource(packhandle);
HLock(packhandle);
packaddr := packhandle^;
initglobals(globals);
buildmenu(globals);
end;
end;
end;
(***************************************
ctl
--
The canonical Control routine. The only events were interested in
are menu clicks; if we get one, call clickmenu.
***************************************)
procedure ctl(var device : DCtlEntry;
var block : ParamBlockRec);
var
globals: wpointer;
begin
if (device.dCtlMenu <> 0)
and (block.csCode = accMenu)
then begin
globals := wpointer
(device.dCtlStorage);
clickmenu
(block.csParam[1], globals);
end;
end;
(**************************************)
end.
(**************************************)
/codetype DRVR 16 Transfer HD 16
drvrhead
drvr
pas$library
macintfglue
/end
;***************************************
;
;packhead.asm
;------------
;
;(c) 1987, 1988 Attic Software
;
;header and assembly routines for
;PACK segment of Transfer.
;
;***************************************
;***************************************
;exported routines
;***************************************
xdef setglobal
xdef getglobal
xdef runiaz
;***************************************
;
;The jump table begins with a jump
;into the body of the table, indexed
;by D0 (the routine selector). Then
;it branches to the appropriate
;routine.
;
;***************************************
xref initglobals
xref setdir
jmp 2(PC,D0.w)
bra.w initglobals
bra.w setdir
;***************************************
;
;The global access routines provide
;a way to save the address of the
;global record, which would
;otherwise be lost. This is done by
;writing the address into a four-
;byte constant in the PACK segment.
;
;Note: writing to code segments is
;frowned upon in some circles. This
;objection has a legitimate basis:
;self-modifying code is a horror.
;It makes debugging difficult, and
;maintenance impossible. (5 points:
;where in Inside Mac are you
;instructed to write self-modifying
;code?) In this instance, I an not
;modifying code; I am modifying
;data, and theres nothing wrong
;with that.
;
;procedure setglobal(value : long);
;function getglobal : long;
;
;***************************************
module access
setglobal
movea.l(SP)+,A0
lea dummy,A1
move.l (SP)+,(A1)
jmp (A0)
getglobal
movea.l(SP)+,A0
move.l dummy,(SP)
jmp (A0)
dummy
dc.w xxxx
;***************************************
;
;procedure runiaz(iazaddr : long)
;
;The runiaz routine zeros the
;iaznotify hook, and calls the
;routine that preceded Transfers
;routine, if any.
;
;***************************************
module runiaz
runiaz
clr.l $33C
movea.l4(SP),A0
move.l (SP)+,(SP)
cmpa.l #0,A0
beq.w L0001
move.l A0,-(SP)
L0001
rts
;***************************************
(***************************************
pack.pas
--------
(c) 1987, 1988 Attic Software
Pascal routines for PACK segment of Transfer
***************************************)
unit pack;
(**************************************)
interface
(**************************************)
uses macintf, hfs, types;
(**************************************)
implementation
(**************************************)
procedure setglobal
(value : long); external;
function getglobal : long; external;
procedure runiaz
(iazaddr : long); external;
(***************************************
systemdir
--------
This routine is more or less straight out of Tech Note 67. It returns
a directory id for the System folder, suitable for use in SetVol calls.
Step one is to find the volume reference number of the volume that holds
the System folder. sysmap is the file reference number of the System
file (an open file), so GetVRefNum will find the volume refence number
of the System file and, of course, the System folder. (The Tech Note
skips this step; it searches the boot drive for a System folder, and
may not find one.)
Step two is to get the directory id, with a call to PBHGetVInfo.
The directory id is returned in the ioVFndrInfo[1] field of the HParamBlockRec.
***************************************)
function systemdir
(globals : wpointer) : long;
var
thepointer : shortpointer;
thevolume: integer;
anerror: integer;
begin
with globals^ do begin
thepointer := shortpointer(sysmap);
anerror := GetVRefNum(thepointer^,
thevolume);
with hblock do begin
ioNamePtr := nil;
ioVRefNum := thevolume;
ioVolIndex := 0;
end;
anerror := PBHGetVInfo(@hblock,
false);
systemdir := hblock.ioVFndrInfo[1];
end;
end;
(***************************************
setdir
------
This is just a shell around PBHSetVol.
***************************************)
function setdir
(dirid : long;
globals : wpointer) : OSErr;
begin
with globals^ do begin
with wdblock do begin
ioCompletion := nil;
ioNamePtr := nil;
ioVRefNum := 0;
ioWDDirID := dirid;
end;
setdir := PBHSetVol(@wdblock,
false);
end;
end;
(***************************************
postlaunch
----------
This is the routine that performs the actual launch. It is called by
InitApplZone throught the iaznotify hook.
First, recover the global record with a call to getglobal. This routine
is called after all the resources have been released; the DRVR segment
is no longer around, and even if it was, postlaunch isnt called by
it, so we cant find the globals in the usual way. getglobal will
return a long word stored right in the PACK segment, which has been previously
set to a pointer to the globals.
Next, postlaunch will call any routines that were already installed
in iaznotify when postlaunch was installed there, via the runiaz
routine.
Then it calls setdir to set the volume to the folder containing the
chosen application, and copies the applicationss name to curappname.
The system will now be fooled into launching that application instead
of the Finder.
***************************************)
procedure postlaunch;
var
globals: wpointer;
anerror: integer;
begin
globals := wpointer(getglobal);
with globals^ do begin
runiaz(iazaddr);
anerror := setdir(launchpath,globals);
if anerror = noErr then
BlockMove(@thename, Ptr(curappname), 32);
end;
end;
(***************************************
initglobals
----------
Initialize global data. Note that we preserve the value at iaznotify;
if the current application has installed a routine there, we will want
to run it before we run ours.
Also, we dont want to confuse the new application with the old applicationss
data files, so we clear the finder files.
***************************************)
procedure initglobals
(globals : wpointer);
var
thepointer : longpointer;
message: integer;
count : integer;
index : integer;
begin
setglobal(long(globals));
with globals^ do begin
sysdir := systemdir(globals);
thepointer
:= longpointer(iaznotify);
iazaddr := thepointer^;
launchaddr
:= BitAnd(long(@postlaunch),
$FFFFFF);
end;
CountAppFiles(message, count);
for index := 1 to count do
ClrAppFiles(index);
end;
(**************************************)
end.
(**************************************)
/codetype PACK -15871 Transfer HD 16
packhead
pack
pas$library
macintfglue
/end
*****************************************
*rsrc.r
*
*(c) 1987, 1988 Attic Software
*
*resources for Transfer DA
*
****************************************
Transfer 3.2 HD
DFILDMOV
include drvr
include pack
*****************************************
*
*Menu resource
*
****************************************
type MENU
,-15871 (4)
Transfer
About Transfer...
Rebuild menu
(-
Finder
*****************************************
*
*Error messages
*
****************************************
Type STR#
,-15871 (32)
4
Im unable to load the Transfer DA.
Error building the menu list.
Error opening the Transfer Data file.
Error restoring the volume.
*****************************************
*
*About information
*
****************************************
Type INFO=GNRL
Trans,-15870 (32)
.S
Transfer 3.2 HD adds a Transfer menu ++
to any application that ++
supports desk accessories. It is ++
intended for use with your ++
hard disk, and
.S
expects that you are using ++
HFS.\0D\0DTransfer 3.2 HD was ++
written by Clifford Story of ++
Attic Software.\0D\0D
.S
Attic Software\0D++
P.O. Box 24695\0D++
Jacksonville, ++
Florida 32241
*****************************************
*
*Item lists
*
****************************************
Type DITL
Error,-15871 (32)
2
* 1
BtnItem Enabled
116 83 136 173
OK
* 2
statText Disabled
10 10 106 246
^0^1\0D\0DTransfer HD error ^2,++
\0DSystem error ^3.
About,-15870 (32)
2
* 1
BtnItem Enabled
252 201 272 291
OK
* 2
userItem Enabled
10 10 242 482
Build,-15869 (32)
1
* 1
statText Disabled
10 10 90 246
Transfer is building the menu list. ++
This will take 30 seconds ++
or so but the list will be preserved ++
and wont have to be ++
re-built unless you re-arrange your ++
disk.
Delay,-15868 (32)
2
* 1
BtnItem Enabled
68 83 88 173
OK
* 2
statText Disabled
10 10 58 246
^0 will be launched when you quit ++
this application.
*****************************************
*
*Dialogs
*
****************************************
Type DLOG
Error,-15871 (32)
Transfer HD
64 128 210 384
invisible nogoaway
0
0
-15871
About,-15870 (32)
Transfer 3.2 HD, © 1987, 1988 ++
Attic Software
50 10 332 502
invisible nogoaway
0
0
-15870
Build,-15869 (32)
Transfer HD
64 128 164 384
invisible nogoaway
0
0
-15869
Delay,-15868 (32)
Transfer HD
64 128 162 384
invisible nogoaway
0
0
-15868
****************************************