TweetFollow Us on Twitter

Christmas Graphics
Volume Number:1
Issue Number:13
Column Tag:Pascal Procedures

Christmas Graphics

By Alan Wootton, President, Top-Notch Productions, MacTutor Contributing Editor

Last month I promised to complete my discussion on desk accessories. I still plan to do that, but this month is Christmas so we will take it easy and do some more lighthearted things. We will take an overview of the types of programming one might do on the Macintosh. I have two short programs demonstrating how to write code in resources, and a short program that draws a Christmas tree.

Resources as code

Unless you are writing code for a dedicated appliance controller, or something similar, the programs you write will be started by another program, usually the operating system. Your program, when finished, will return control to that 'other' program. In the case of a Macintosh Application, the program is started by another Application (usually the Finder) using the Toolbox procedure Launch (see Segment Loader chapter of Inside Mac). When the program is finished it returns to the Finder by calling ExitToShell (or Launch!), or by just reaching the end of the main procedure, in which case ExitToShell is called for you. If you have been following this column you will know that there are other kinds of programs besides just Applications. Desk Accessories are called repeatedly by the toolbox when the resident Application calls SystemTask. In addition to these two there are many other ways to get your piece of code executed.

One of the features of native code compilers (for the 68000) is that the code they produce is position independent. This means that it does not matter what address the program occupies -- it always runs properly. So, potentially any portion of memory could be loaded with code and run. In the case of an application, the Segment Loader handles this chore. In the case of DAs, the Device Manager loads resources of type DRVR and passes control to the code within. The Window Manager, the Menu Manager, the Control Manager, and others will load and run pieces of code. There is no reason why we cannot do it, too.

The compilers normal output (actually produced by the linker) is a single piece of code that is stored in the resource CODE #1. The structure of this code is illustrated in figure 1.

Note that the code is produced in the same order as the procedures occur. A jump is used to transfer control to the main procedure. Further, note that the main procedure does not begin and end like the others. It does not return control to the caller at all. Rather, it exits to the finder. The link and unlink are used to reserve space on the stack for the vars declared for that procedure.

An important thing to notice is the existence of phantom procedures past the end of the program. These are known as library routines and have the purpose of providing functions that are needed by the program. String functions and Operating System traps are examples. You can even make your own library procedures and, by declaring them, cause the linker to include that code.

There is a problem with the MDS linker. It is not as efficient as it should be. When you refer to a .rel file in a link directive file (link directive files are produced automatically by TML Pascal, although you may make your own), the whole .rel file is linked onto the end of your code, whether it is used or not. If you need more control you can make your own library files that only have those routines actually needed in them (requiring much work in assembly language). Another way is the get the Optimizing Linker and Librarian from Consulair Corp. (Portola Valley, CA). Consulair normally sells a C compiler but, since that compiler is MDS compatible, you may use their products in conjunction with the TML Pascal Compiler. TML may be able to sell you that linker also.

There is another oddity about the main procedure that is not shown in the diagram. All variables declared in the main procedure are accessed in a special way. These variables are not created temporarily like in the other procedures. The variables for the main procedure are created by the segment loader above the beginning of the stack and the register A5 is reserved for the sole purpose of accessing those variables. This means that, except for application code, procedures in resources absolutely must not access any global variables. This was mentioned in regard to DAs last month and is applicable here, too.

Before we lose our way in all this technical mush, let us remember that our goal is to use a resource as a procedure. In order to do this we have one final hurdle to overcome.

The most general method will be to use the beginning of the resource as the beginnning of the program. A glance at figure 1 shows that this does not work on the program in its CODE 1 form. Since we will have to use the Rmaker to convert from type CODE to another type, (here we use PROC) we could use Rmaker to convert from the program form to the procedure form. Rmaker does not make this an easy task. There is a Rmaker type that will trim off the first two words (created by the segment loader) of the CODE 1 resource (refer to your Rmaker documentation). Unfortunately, we need to also avoid the jump to the main procedure. If we tell the compiler to put our procedure into a CODE 2 resource then there is no jump, but Rmaker will not trim any other than CODE 1. After much thought I came upon what seems to be the best solution. Use the GNRL type directive to place the word $6008 at the front of the destination resource and then copy the CODE 1 resource after that. $6008 is the 68000 code to branch over the next 8 bytes. This effectively skips the two segment loader bytes and the jump. For CODE types other than number 1 there will not be a jump, so use $6004 to skip only the first two words. See the Rmaker input file below for an example.

Prompt_For_String

The procedure I present to illustrate the use of procedures in resources is a compiled version of the Dialog box example presented here in July (Vol.1 No. 8). Its purpose is to open a dialog box, request a string from the user, and then go away, returning the string to the calling program. To compile this first compile Pr_for_Str.pas, link Pr_for_Str.link (this file is created by the compiler), and then use Rmaker with Pr_for_String.R. The resulting file is Pr_for_Str.PROC which contains the resources PROC 567, which is our procedure, and also the DLOG and DITL for the dialog box.

The most interesting part of the example is the third part, the program Pr_for_Str.Mpas. This is a short (except for the declarations) MacPascal program that shows how you could use an external and compiled procedure from within MacPascal.

Inline again

All the action in Pr_for_Str.Mpas (below) takes place in the main procedure. For readability of those eleven lines I have declared the Toolbox routines as procedures, rather than just using inlines in the code. This is the simplest use of inline, once you get the definitions correct it is difficult to call the Toolbox traps incorrectly. Following the example below it is easy to type in Toolbox routines using Inside Mac as a guide.

The exceptions are register based traps. For these I use Generic (see Advanced Mac'ing in MacTutor Vol.1 No. 5). When Inside Mac says (using Hlock as an example):

PROCEDURE Hlock(h:handle);
  On entry       A0: h (handle)
    On exit            D0: result code (integer)

You set regs.a[0] to the handle and call Generic with $A029 (look up the trap number in a cross reference). The record regs is read by Generic and the trap is called. Note that later you can check loword(regs.d[0]) for an error.

This is not the end of the the ugliness. In order to call an external procedure it is necessary to abuse inline. Instead of a trap number we use the word $4E75 which will transfer execution to the address corresponding to the last argument to inline. The last argument is @jsr[0] which is the address of an array of 4 words which has been set to a short routine to shuffle some registers and then call the address that is next to last in the arguments. In this case it is the address of the resource PROC 567. For more info on jsr see my column in MacTutor Vol.1 No.9. The printing example uses jsr and the 68000 code is given.

Once you become comfortable with the inline kludges required, you can make arbitrarily complex MacPascal programs. Simply work on your program until it becomes too large (or too slow), then compile those procedures that are in a relatively finished state. When the compiled procs are replaced by the code to call them externally your program will then be much shorter, and you can add to it until it is time to repeat the cycle. Eventually, all that is left is a core with the bulk of the program compiled. At that point you move the last of the program to the compiler and you have a completed program!

Skip over the Pr_for_Str stuff (3 files) now and we will do something much more fun.

TML Pascal code
Program Pr_For_Str;{ file Pr_For_Str.pas }
{ by Alan Wootton 10/85 }
{ written for TML Pascal }

{ $I means include these interface files }
(*$I MemTypes.ipas  *)
(*$I QuickDraw.ipas *)
(*$I OSIntf.ipas    *)
(*$I ToolIntf.ipas  *)

{ We will convert this code with Rmaker in
  such a way as to cause execution to begin
  with the FIRST PROCEDURE, and not in the
  main procedure.    }

{ This procedure expects the resource
  DLOG 12345 to be available.  It opens a
  dialog box and returns a string in result.
  If no string is input by the user then
  the string '' is returned.      }
 
{ Execution begins here } 
Procedure Prompt_for_Str(var Prompt:str255;
                  var Sample:str255;
                   var Result:str255);
  const
       OKbutton      =1;{ items in DLOG box }
       CANCELbutton  =2;
       RESULTtext    =3;
       PROMPTtext    =4;
  var
       DlogPtr : DialogPtr;
       TempHand: handle;
       itype, itemHit : integer;
       R : rect;
       itemH : Handle;
begin
     TempHand:=GetResource('DLOG',12345);
      if TempHand<>nil then
          begin
               DlogPtr:=GetNewDialog(12345,nil,pointer(-1));
    
               GetDItem(DlogPtr,PROMPTtext,itype,itemH,R);
                if length(Prompt)<>0 then
           SetItext( itemH, Prompt);
    
               GetDItem(DlogPtr,RESULTtext,itype,itemH,R);
                if length(Sample)<>0 then
           SetItext( itemH, Sample);
               { Note that itemH is now handle to result }
               { text item and will be used later. }
    
               ModalDialog( nil, itemHit);
    
               if itemHit=CANCELbutton then
           result := ''
                else
           GetIText( itemH, result);
  
               DisposDialog( DlogPtr);
          end;
   end;{ of procedure }

begin{ main }
   { ¡¡¡¡ main not used !!!! }
end.

                  Rmaker code
;;                                     
;;  file Pr_For_Str.R
;; 
;;  Feeding this to Rmaker is the last
;;  step when compiling Pr_For_Str
;; 
;;  The CODE 1 resource is read from 
;;  Pr_For_Str, the link output, and 
;;  is written to the resource PROC 567
;;  in Pr_For_Str.PROC
;;  
;;  A branch is added to the front of
;;  the code to skip the segment header 
;;  (4 bytes), and in this case, to also
;;  skip the instruction to jump to the 
;;  main procedure (4 bytes, for 8 total).
;;  Use 6004 for bra.s *+4.
;;                                     
Pr_For_Str.PROC;;;  destination file name 
????????;;  type and creator 
;;                                     

type PROC = GNRL
Prompt_For_Str,567
.H 
6008;;  bra.s *+8 
.R
Pr_For_Str CODE 1

;;****************************************
;;**  Definition of a Dialog Manager    **
;;**  window.                           **
;;****************************************
;;  global coordinates !!

type DLOG
box,12345
;;notitle
96 128 148 384 ;; top left bottom right 
visible goaway
1              ;; window type = dBoxProc 
0              ;; refcon
12345          ;; ID of DITL associated
               ;; with this DLOG

;;****************************************
;;**  Next is a list of 'items' to go   **
;;**  in the window.                    **
;;****************************************

type DITL       ;; see Dialog Manager
items,12345
4               ;; four items 

Button
4 120 24 180    ;;  local coordinates !!
OK;;

Button
4 188 24 248
Cancel;;

EditText Disabled  
32 8 48 248        
;;;;;;;;;;;;;;;;;;text added later 

StaticText Disabled  
4 8 20 120           
Type a String;; prompt, modified later

;;                                     
;;  end of file Pr_For_Str.R
;;                                     

               MacPascal code

program Pr_For_Str_Test;{ by Alan Wootton 10/85 }
{ This program exercises an external procedure }
{ that presents a dialog box requesting the user }
{ to input a string. }
 type
     ptr = ^char;
     handle = ^ptr;
     ResType = longint;
 var
     ResRefNum : integer;{ resource file ref num }
     str : str255;
     regs : record { for generic }
          A : array[0..4] of longint;
          D : array[0..7] of longint;
      end;

{--------------------------------------------------------------------}
{--Toolbox interface routines we will be using----------}
{--------------------------------------------------------------------}
 function OpenResFile (filename : str255) : integer;
 begin
     OpenResFile := WinlineF($A997, @filename);
 end;
 procedure CloseResFile (refNum : integer);
 begin
     inlineP($A99A, refnum);
 end;
 function HomeResFile (TheResource : Handle) : integer;
 begin
     HomeResFile := WinlineF($A9A4, TheResource);
 end;
 function GetResource (TheType : ResType; TheID : integer) : Handle;
 begin
     GetResource := pointer(LinlineF($A9A0, TheType, TheID));
 end;
 procedure DetachResource (TheResource : Handle);
 begin
     inlineP($A992, TheResource);
 end;
{ The Hlock that is predefined does not work!!! }
 procedure Hlock (H : Handle);
 begin
     regs.a[0] := ord(h);
     Generic($A029, regs);
 end;
{ convert a Str255 to ResType }
 function StrToType (str : str255) : ResType;
  var
      TheType : ResType;
 begin
     BlockMove(@str[1], @TheType, 4);
     StrToType := TheType;
 end;
{--end of interface routines-------------------------------}
{------------------------------------------------------------------}

{------------------------------------------------------------------}
{--Routine to call  PROC 567 resource  ----------------}
{------------------------------------------------------------------}
 procedure Prompt_for_String (Pr : str255;
                                                                Sa : 
str255;
                                                       var Re : str255);
  var
      Hand : handle;
      jsr : array[0..3] of integer;
 begin
     stuffHex(@jsr, '5488225F2F084ED1');
       { code to jsr to top of stack }
     Hand := GetResource( StrToType ('PROC'), 567);
     if Hand <> nil then
         begin
             Hlock(Hand);
             inlineP($4E75, @Pr, @Sa, @Re, Hand^, @jsr);
{            $4E75 is rts to code in @jsr which calls Hand^ }
{            normally you might unlock Hand now }
         end
        else
            writeln('PROC 567 not found');
    end;

begin     {   main, test prompt for string }
    ResRefNum := OpenResFile('Pr_For_Str.PROC');
    if ResRefNum > 0 then
        begin
            Prompt_For_String('Str Please', 'example str', str);
            writeln('The str returned is ', str);
            CloseResFile(ResRefNum)
        end
    else
        writeln('OpenResFile failed');
end.

Christmas graphics

For those who like short MacPascal programs that make intricate drawings I present the program X_Tree (below) that makes the picture-of-many-needles (above). The way this works is that it loops, while drawing branches (needles) proportional to the remaining length, until the remaining length is short. To draw the branches the same routine is called again (an example of recursion), so that the branches look like smaller versions of the whole tree. This would be a simple program except that in order to project a line of a particular length (Length), in a particular direction (Direction) one must use the trigonometric functions Sin and Cos. These functions, when multiplied by a length, give the horizontal (Cos) and vertical (Sin) component of a line in the given direction. It wouldn't be so bad, but the direction given to Sin and Cos is not in degrees! It is in radians. Radians are a mathematical unit for angles used by SANE and scientists everywhere. To use radians note that 180° is the same as Π radians (see how the variable pi is set to the value of Π below). This means that 60° (or 180°/3) is the same as pi/3.

To make the drawing above I pasted the DrawSomething procedure into the program Pict_to_Clip (October MacTutor, Vol.1 No.11) and ran it. I then quit MacPascal and started MacDraw and did a paste. After that I added the text next to it and cut everything onto the clipboard. I then pasted the result into MacWrite for this article. [The Pict_to_Clip utility referred to above is a marvelous little program that writes a user defined function to a pict resource and moves the pict resource into the clipboard where it can be pasted into other applications that support laser printing, like Mac Draw. In this way, it makes the Macintosh into a plotter! Available on our source code disks or as a back issue (October 1985) through the MacTutor mail order store. -Ed.]

program X_Tree;
   uses
       SANE;

 procedure DrawSomething;
     const
           NeedleMin = 5;{ cutoff size for Needles }
     var
          pi, Direction, X, Y : extended;

{ The variables above are global to the proc "Tree". }
{ X, and Y, are the pen position in floating point form. }
{ Direction is an angle pointing in the direction the }
{ "tree" is. Direction is in radians, ie. -pi/2 is up, }
{  pi/2 is down, 0 is to the right, pi is to the left. }

  procedure Tree (Length : extended);
{ Given a length and a direction, this proc will }
{ draw a line of the given length and, size permitting, }
{ will draw a series of "subtrees", or "needles", of }
{ decreasing size alongside the "tree" line. }
    var
        OldDir, Needle, PrevX, PrevY : extended;
  begin
      PrevX := X;
      PrevY := Y;{ Save direction and position. }
      OldDir := Direction;
      Needle := Length / 3;{ Length of first "needle". }

      while Length > 1 do
          begin { Subdivide "tree" }

              if Needle > NeedleMin then
                  begin { Draw left, then right, needle. }
                      Direction := OldDir - pi / 3;{ 60 degrees }
                      Tree(Needle);
                      Direction := OldDir + pi / 3;
                     Tree(Needle);
                  end
              else { else make line remaining length }
                  Needle := Length * 3;

              { Draw portion of tree between successive needles }
              Direction := OldDir;
              MoveTo(num2integer(X), num2integer(Y));
              X := X + (Cos(Direction) * Needle / 3);
              Y := Y + (Sin(Direction) * Needle / 3);
              LineTo(num2integer(X), num2integer(Y));

              Length := Length - Needle / 3;{ shorten length }
              Needle := Needle * (1 - 1 / 9);{ shorten needle }
          end;

         X := PrevX;
         Y := PrevY;{ restore position and direction }
         Direction := OldDir;
     end;

 begin { procedure DrawSomething }
     pi := arctan(1) * 4;{ 3.14159... }
     Direction := -pi / 2;{ -90 degrees = up }
     X := 200;
     Y := 240;{ tree base at 200,240 }
     Tree(200);{ 200 = size of tree }
 end;


begin { main program }
    ShowDrawing;
    DrawSomething;
end.

More Resources as code

A more technical example of writing code for resources is now presented for advanced programmers. The VBLExample (below) is an INIT resource that installs a Vertical Retrace routine at system startup.

Vertical Retrace routines are short pieces of code that are executed periodically by the system. They are not for general use, however, since they are executed during an interrupt. This means that you cannot use any Toolbox traps that use the memory manager. This includes Quickdraw. Bob Denny (C Workshop, MacTutor Vol.1 No.9) gives a good description of the Vertical Retrace Manager so I won't do it here. All the example does is increment the first longint in the screen buffer. This makes a tiny binary counter in the upper left of the screen.

To put a task into the Vertical Retrace queue you must fill out a short record and pass it to Vinstall. Notice that I break all the rules and put this record in the same resource with the code! The procedure dummy is declared to make some unused space and GetGlobalData is called to get a pointer to our permanent storage record (remember, only the application can have permanent global variables).

Inlines again! The TML Pascal compiler has a type of inline that you can use (carefully). It is not the same as the MacPascal inlines. The syntax is a procedure declaration, followed immediately by "inline", and then by an integer. When the procedure is called the word is excuted in the place of the normal jsr. In the program VBLExample I create a procedure that will set register A0 and another that will invoke the trap _Vinstall. By using these two it is possible to alleviate the need for any libraries at all (no other Toolbox calls or procedures require library support in the example).

Bob Denny also gives a good description of how INIT resources work, so I won't repeat it. Debugging is another story. The code in INIT resources is called during startup and at that time it is just about impossible to use a debugger! This can make INIT resources very hard to trace. To make it easy I wrote the MacPascal program Run_INIT (below). This program does to an INIT resource the same thing the system does at startup. It is also a good description of how the system treats INIT resouces. Note that since no parameters are passed you can use Generic to call the external procedure instead of @jsr like it did in Pr_for_Str.Mpas. Otherwise these two have much in common.

We have seen the operation of two types of code resources, INIT and PROC. These are merely the tip of the iceberg. Perhaps later we'll try MDEF, WDEF, or CDEF (menu, window, and control definintion functions) in addition to our normal projects involving CODE and DRVR (for applications and desks accessories).

Until next month, may the bugs bite you only in obvious places, and Merry Christmas!

program VBLExample;{ file VBLExample.pas }
{  by Alan Wootton 10/85 }
{ written in TML Pascal }

{  $I=Include these interface files }
(*$I MemTypes.ipas  *)
(*$I QuickDraw.ipas *)
(*$I OSIntf.ipas    *)
(*$I ToolIntf.ipas  *)

{ We will convert this code with Rmaker in
  such a way as to cause execution to begin
  with the FIRST PROCEDURE, and not in the
  main procedure.    }

TYPE

  GlobalDataP=^GlobalData;
  GlobalData=record
  vblPart:VBLTask;
  count:longint;
       end;{ 18 bytes long }
    
{ var
     no global variables allowed }
     
{ the following four procs don't generate code now }
Procedure SetA0(a0:longint);inline $205F;{ MOVE.l (SP)+,A0 }
Procedure Vinstall_Trap;inline $A033;{ _Vinstall trap }
Function GetGlobalData : GlobalDataP;FORWARD;
Procedure VBLScreenTask;FORWARD;

{ execution begins here }
{ We install VBLScreenTask in the VBL queue and 
   set up the body of the dummy procedure as our
   data record. }

Procedure InstallVBLTask;{ one time setup routine }
var
     cp : GlobalDataP;
begin
  cp := GetGlobalData;
  cp^.count:=0;
  with cp^.VBLPart do
    begin
        qType:=ord(vType);
 vblAddr:=@VBLScreenTask;
 vblCount:=1;
 vblPhase:=0;
 { This funky double step is my way of calling Vinstall 
    without having to link with another file.  Register A0
    is set and then the trap is called. }
 SetA0(ord(@cp^.VBLPart));
 Vinstall_Trap;
    end;
end;

Procedure Dummy;{ reserve some bytes in the code space }
begin
    Dummy; Dummy; Dummy; Dummy;{ 8 bytes }
    Dummy; Dummy; Dummy; Dummy;{ 8 bytes }
    Dummy;
end;

Function GetGlobalData {: GlobalDataP};
begin
    GetGlobalData := pointer(ord(@Dummy));
end;

{ Reset the VBLCount so we remain in queue and
  utilise $824 (SCRNBASE global) to find address
  of the screen and write the count there.        }

Procedure VBLScreenTask;
   var
       cp : GlobalDataP;
       ScreenP:^longint;
begin
  ScreenP:=pointer($824);
  ScreenP:=pointer(ScreenP^);
  cp:=GetGlobalData;
  with cp^ do 
      begin
         count:=count+1;
  with VBLpart do
      begin
        VBLCount:=1;
        ScreenP^:=count;
      end;
       end;
 end;{ vbltask }
  
begin{ main }
    { ¡¡¡ main procedure not used !!! }
end.


;;----------------------------------------------------
;;  file VBLExample.R
;; 
;;  Feeding this to Rmaker is the last
;;  step when compiling VBLExample.
;; 
;;  The CODE 1 resource is read from 
;;  VBLExample, the link output, and 
;;  is written to the resource INIT 16 
;;  in VBLExample.INIT
;; 
;;  A branch is added to the front of
;;  the code to skip the segment header 
;;  (4 bytes), and in this case, to also
;;  skip the instruction to jump to the 
;;  main procedure (4 bytes, for 8 total).
;;  Use 6004 for bra.s *+4.
;;----------------------------------------------------
VBLExample.INIT;;;  destination file name 
????????;;  type and creator 
;;----------------------------------------------------

type INIT = GNRL
VBLExample,16 (80)
.H 
6008;;  bra.s *+8 
.R
VBLExample CODE 1

;;----------------------------------------------------
;;  end of file VBLExample.R
;;----------------------------------------------------

program Run_INIT;{  by Alan Wootton 10/85 }
{ This MacPascal program is for testing }
{ INIT resources.  It loads the INIT }
{ resource number 16 from the file named below, }
{ and runs it just as the boot code would at system }
{ startup.  The handle is writeln'd and you are given }
{ the opportunity to invoke a debugger, and set }
{ breakpoints, if desired. }
 type
     ptr = ^char;
     handle = ^ptr;
     ResType = longint;
 var
     ResRefNum : integer;{ resource file ref num }
     str : str255;
     hand : handle;{ handle to code }
     longP : ^longint; 
    regs : record { for generic }
          A : array[0..4] of longint;
          D : array[0..7] of longint;
      end;

{--------------------------------------------------------------------}
{--Toolbox interface routines we will be using----------}
{ copy routines from Pr_For_Str_test (above) }

{------------------------------------------------------------------}
{--Routine to call a 68000 proc in memory-------------}
{--note that the handle is not locked,-------------------}
{--and no parameters are passed------------------------}
 procedure RunHandle (Hand : handle);
 begin
     regs.A[0] := ord(hand^);{ set A0 }
     Generic($4E90, regs);{ $4E90 = JSR (A0) }
 end;


begin              {  main,  program starts here }
    ShowText;
    ResRefNum := OpenResFile('VBLExample.INIT');
    if ResRefNum > 0 then
        begin
            hand := GetResource(StrToType('INIT'), 16);
            if hand <> nil then
                begin
                    if HomeResfile(hand) = ResRefNum then
                        begin  
                            writeln('handle is ', ord(hand));
                            writeln('run resource ? (y/n)');
                            readln(str);
                            if str = 'y' then
                                begin
                                    DetachResource(hand);
                                    RunHandle(hand);
                                    longP := hand^;
                                    longP^ := $4E714E71;{ nop nop }
                                end;
                           end
                       else
                           writeln(' resource from wrong file');
                   end
                  else
                      writeln(' resource not loaded ');
              CloseResFile(ResRefNum);
        end
    else
        writeln('OpenResFile failed');
end.


 
AAPL
$100.96
Apple Inc.
-0.83
MSFT
$47.52
Microsoft Corpora
+0.84
GOOG
$596.08
Google Inc.
+6.81

MacTech Search:
Community Search:

Software Updates via MacUpdate

Audio Hijack Pro 2.11.3 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Airfoil 4.8.9 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
WhatRoute 1.13.0 - Geographically trace...
WhatRoute is designed to find the names of all the routers an IP packet passes through on its way from your Mac to a destination host. It also measures the round-trip time from your Mac to the... Read more
Chromium 37.0.2062.122 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. FreeSMUG-Free OpenSource Mac User Group build is... Read more
Attachment Tamer 3.1.14b9 - Take control...
Attachment Tamer gives you control over attachment handling in Apple Mail. It fixes the most annoying Apple Mail flaws, ensures compatibility with other email software, and allows you to set up how... Read more
Duplicate Annihilator 5.0 - Find and del...
Duplicate Annihilator takes on the time-consuming task of comparing the images in your iPhoto library using effective algorithms to make sure that no duplicate escapes. Duplicate Annihilator detects... Read more
jAlbum Pro 12.2 - Organize your digital...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code!... Read more
jAlbum 12.2 - Create custom photo galler...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results Simply drag and drop photos into groups, choose a design... Read more
Quicken 2015 2.0.4 - Complete personal f...
Quicken 2015 helps you manage all your personal finances in one place, so you can see where you're spending and where you can save. Quicken automatically categorizes your financial transactions,... Read more
iMazing 1.0 - Complete iOS device manage...
iMazing (formerly DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and... Read more

Latest Forum Discussions

See All

View Source – HTML, JavaScript and CSS...
View Source – HTML, JavaScript and CSS 1.0 Device: iOS Universal Category: Utilities Price: $.99, Version: 1.0 (iTunes) Description: View Source is an app plus an iOS 8 Safari extension that makes it easy to do one key web developer... | Read more »
Avenged Sevenfold’s Hail To The King: De...
Avenged Sevenfold’s Hail To The King: Deathbat is Coming to iOS on October 16th Posted by Jessica Fisher on September 19th, 2014 [ permalink ] Just in time for Halloween, on October 16 Avenged Sevenfold will be launching | Read more »
Talisman Has Gone Universal – Can Now be...
Talisman Has Gone Universal – Can Now be Played on the iPhone Posted by Jessica Fisher on September 19th, 2014 [ permalink ] | Read more »
Tap Army Review
Tap Army Review By Jennifer Allen on September 19th, 2014 Our Rating: :: SHOOT EM ALLUniversal App - Designed for iPhone and iPad Mindless but fun, Tap Army is a lane-based shooter that should help you relieve some stress.   | Read more »
Monsters! Volcanoes! Loot! Epic Island f...
Monsters! Volcanoes! Loot! | Read more »
Plunder Pirates: Tips, Tricks, Strategie...
Ahoy There, Seadogs: Interested in knowing our thoughts on all this plundering and pirating? Check out our Plunder Pirates Review! Have you just downloaded the rather enjoyable pirate-em-up Plunder Pirates and are in need of some assistance? Never... | Read more »
Goat Simulator Review
Goat Simulator Review By Lee Hamlet on September 19th, 2014 Our Rating: :: THE GRUFFEST OF BILLY GOATSUniversal App - Designed for iPhone and iPad Unleash chaos as a grumpy goat in this humorous but short-lived casual game.   | Read more »
A New and Improved Wunderlist is Here fo...
A New and Improved Wunderlist is Here for iOS 8 Posted by Jessica Fisher on September 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Evernote Update for iOS 8 Adds Web Clipp...
Evernote Update for iOS 8 Adds Web Clipping, Quick Notes, and More Posted by Ellis Spice on September 19th, 2014 [ permalink ] | Read more »
Apple Names Ultimate Productivity Bundl...
Apple Names Ultimate Productivity Bundle by Readdle as the Essential Bundle on the App Store Posted by Jessica Fisher on September 19th, 2014 [ permalink | Read more »

Price Scanner via MacPrices.net

iFixIt Tears Down iPhone 6; Awards Respectabl...
iFixit notes that even the smaller 4.7″ iPhone 6 is a giant among iPhones; so big that Apple couldn’t fit it into the familiar iPhone form factor. In a welcome reversal of a recent trend to more or... Read more
Phone 6 Guide – Tips Book For Both iPhone 6...
iOS Guides has announced its latest eBook: iPhone 6 Guide. Brought to you by the expert team at iOS Guides, and written by best-selling technology author Tom Rudderham, iPhone 6 Guide is packed with... Read more
How to Upgrade iPhone iPad to iOS 8 without D...
PhoneClean, a iPhone cleaner utility offered by iMobie Inc., reveals a solution for upgrading iPhone and iPad to iOS 8 without deleting photos, apps, the new U2 album or anything. Thanks to more than... Read more
Inpaint 6 – Photo Retouching Tool Gets Faster...
TeoreX has announced Inpaint 6, a simple retouching tool for end users that helps remove scratches, watermarks, and timestamps as well as more complex objects like strangers, unwanted elements and... Read more
Worldwide PC Monitor Market Sees Growth in To...
Worldwide PC monitor shipments totaled 32.5 million units in the second quarter of 2014 (2Q14), a year-over-year decline of -2.9%, according to the International Data Corporation (IDC) Worldwide... Read more
Updated Price Trackers
We’ve updated our Mac Price Trackers with the latest information on prices, bundles, and availability on systems from Apple’s authorized internet/catalog resellers: - 15″ MacBook Pros - 13″ MacBook... Read more
Mac Pros available for up to $260 off MSRP
Adorama has Mac Pros on sale for up to $260 off MSRP. Shipping is free, and Adorama charges sales tax in NY & NJ only: - 4-core Mac Pro: $2839.99, $160 off MSRP - 6-core Mac Pro: $3739.99, $260... Read more
13-inch 2.6GHz/256GB Retina MacBook Pros avai...
B&H Photo has the 13″ 2.6GHz/256GB Retina MacBook Pro on sale for $1379 including free shipping plus NY sales tax only. Their price is $120 off MSRP. Read more
Previous-generation 15-inch 2.0GHz Retina Mac...
B&H Photo has leftover previous-generation 15″ 2.0GHz Retina MacBook Pros now available for $1599 including free shipping plus NY sales tax only. Their price is $400 off original MSRP. B&H... Read more
21″ 2.7GHz iMac available for $1179, save $12...
Adorama has 21″ 2.7GHz Hawell iMacs on sale for $1179.99 including free shipping. Their price is $120 off MSRP. NY and NJ sales tax only. Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** At the Apple Store, you connect business professionals and entrepreneurs with the tools they need in order to put Apple solutions to work in their Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** The Apple Store is a retail environment like no other - uniquely focused on delivering amazing customer experiences. As an Expert, you introduce people Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** As businesses discover the power of Apple computers and mobile devices, it's your job - as a Solutions Engineer - to show them how to introduce these Read more
Position Opening at *Apple* - Apple (United...
…Summary** As a Specialist, you help create the energy and excitement around Apple products, providing the right solutions and getting products into customers' hands. You Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.