TweetFollow Us on Twitter

Scripting PackageMaker: Checking Requirements

Volume Number: 25
Issue Number: 08
Column Tag: Installers

Scripting PackageMaker: Checking Requirements

Learn how to validate a target for installation

by José R.C. Cruz

Introduction

One of the main features of PackageMaker 2.0 is its use of JavaScript scripts to check if the target platform or volume can support the payload. For instance, one script can check on the target's system version while another on the amount of free space on a mounted volume. If the target fails any of the checks, the script can either inform the user of the failure or prevent one or more payloads from being installed. Whichever action it takes may depend on the situation at hand.

Today, we will look at how PackageMaker 3.0 improves its support of requirements scripts. But first, we will study the first three phases of an install session. Then we will get a basic overview of the Installer JavaScript language. Next, we will learn how to use PackageMaker 3.0 to create some basic scripts. We will also learn how to add a custom JavaScript function and how to call an external shell script.

All the scripts shown in this article are from the Foobar demo project.

A copy of the project can be found on the MacTech ftp site at ftp.mactech.com.

The Install Session

Figure 1 shows a typical install session. When users double-click a package, the package first presents its Readme text. Then it displays the Software License, which users must either accept or reject. If they reject the license terms, the session ends. But if users accept the terms, the package then asks users to choose a target volume. Once users selected a volume and clicked the Install button, the package will install its payload onto that volume.


Figure 1. A basic install session

Some packages will have a variant of the install session. One package, for instance, may have multiple payloads. If so, it has the option of asking users to choose which payload to install. Another may go directly to asking for a target volume, skipping the display of a Readme or License text. Still another may use the root volume as its sole target, thus removing the need for users to choose a target volume.

Then there are packages that do other tasks at certain points of the install session. One such task is to validate the target platform for installation.

Validating the target

Validating a target for installation can have up to three phases. The first phase, InstallationCheck (Figure 2), occurs right after the package displays its Welcome panel. When the target fails this check, the package either ends the install session or warns the user of the failure.


Figure 2. The InstallationCheck phase

The second phase, VolumeCheck (Figure 3), runs right before the package displays the Destination Select panel. Its scripts check the local or network disk volumes mounted on the target platform. If a volume fails a check, the package places a stop badge on that volume's icon in the panel. It also displays an error message when users try to select the failed volume. But when a volume passes a check, the package lets users select the volume. Also, the volume's icon gets a green arrow when selected.


Figure 3. The VolumeCheck phase

The third phase, PayloadCheck (Figure 4), runs just before the package displays the Installation Type panel. Its scripts runs on a per-payload basis and for both install modes, easy or custom. Each script enables and selects a payload based on its results. If at least one of the payloads stays selected, the package then enables that panel's Install button. But if none of payloads are selected, the Install button remains disabled.


Figure 4. The PayloadCheck phase

Enter Installer JavaScript

PackageMaker 3.0 lets you use your favorite language to write scripts for each of the three phases. But for consistent and reliable checks, you should write these scripts in Installer JavaScript. This JavaScript dialect has three global objects (Figure 5). The System object provides data on the host system and access to external files. The My object returns data on either the target volume or the results of InstallationCheck. And the Choices object refers to each payload in the package.

Apple has never made clear which syntax version of JavaScript these tools supports. So, to avoid any problems, always assume version 1.2 of the JavaScript syntax. Keep in mind as well that only version 2.0 or newer of PackageMaker and Installer uses Installer JavaScript.


Figure 5. The three global JavaScript objects

The System object

The System object is the largest global object in Installer JavaScript. This object is available to all three phases of the target check.

There are six properties in the System object. The first three return the current system state; the second refer to other JavaScript objects (more on these later). The defaults property returns an Array of the current system settings. This is the same as typing defaults read on the Terminal prompt. The users.desktopSessionCount property returns the number of users currently logged in the host system. Its count includes users that appear in the Fast User Switching list and those connected via a Window Server session. The version property returns the contents of the SystemVersion.plist file, which is located in /System/Library/CoreServices. This property works the same as typing sw_vers at the Terminal prompt. Like the defaults property, the version property returns its data as an Array.

There are also three sets of methods in the System object. The first set consists of methods that provide more data on the host system. For instance, the compareVersions() method takes two version strings. It checks which string is greater than the other, returning an integer as its result. One way to use this method is with the version property as follows:

tVer = system.version.ProductVersion;
tChk = system.compareVersion(tVer, "10.5.1");
// returns a 1 if tVer > 10.5.1, -1 if tVer < 10.5.1, 
// or 0 if tVer is 10.5.1

The sysctl() method provides data on the system kernel. It takes a single string – the sysctl node – as input and gives its result in the correct datatype. For example, to get the amount of physical RAM present in the host system, check the hw.memsize node as follows:

   tMem = system.sysctl("hw.memsize");

The above line returns the memory size in bytes and as an integer. For a list of other sysctl nodes, type info sysctl at the Terminal prompt.

The gestalt() method provides data on various system additions. It takes a single string – the Gestalt selector – and returns the result as an Object. You then convert the result to the desired type with JavaScript. Consider, for example, you want to find out what version of QuickTime is installed. To do so, use the method as follows:

tQT = system.gestalt('qtim').toString(16);

The line above queries the Gestalt selector qtim. Then it converts the returned value into a 16-byte string. In the case of QuickTime 7, the above line returns the string 7218000.

The second set of methods provides utility services to the package. The run() method takes the name of an external script and, optionally, a variable list of arguments. The script then returns the results of its run back to the package. The runOnce() method uses the same parameters as run(). It, however, allows the script to be used only once. If the package tries to call the same script, the runOnce() method just returns the results of the first run. So assume, as an example, the package wants to call the external script foobar.sh. To do so, use the run() method as follows:

tErr = system.run("foobar.sh");

The script then returns a 0 when it is done, or a non-zero integer when it has any errors. For these methods to work, make sure to place the external script in the package's Resource directory. Also, the external script must not use any form of user interface.

Finally, the log() method takes a string argument, and sends it to the Installer's log window during the session. It also adds the prefix JS: to the string to distinguish it from other log messages. For example, to display the string "Checking the device tree", use the method as follows:

system.log("Checking the device tree");

So far, the log() method is your only debug option in Installer JavaScript. To view your log messages, choose Installer Logs from the Window menu of the Installer tool.

The third set of methods gives the package access to localized strings. This, however, is beyond the scope of this article. I will instead cover these methods in a future article focused on localized packages.

The application property

The applications property stores an instance of the Applications object (Figure 6). This object provides data on active processes on the host system. The object has three methods. The fromIdentifier() method takes a bundle signature and returns data on the application that has the signature. For example, to get data on the application TextWrangler, pass its bundle ID to the method as follows.

tProc =
   system.applications.fromBundleID("com.barebones.textwrangler");

The fromPID() method takes a process ID and returns data on a daemon process. Most daemons get a unique ID from OS X. The line below, for instance, returns data on the launchd daemon, which has a PID of 119.

   tProc = system.applications.fromPID(119);

The all() method returns data on all active processes on the host OS X system. It returns its results as an Array, with each entry in the Array being the data for each process.


Figure 6. The Applications object

All three methods return the process data as an Array. The elements in this array use keys defined by the ProcessInformation class, five of which are shown in Table 1. You then access each element by using a dot or a bracket notation. For example, the following line uses a dot-notation to read the FileCreator element.

tCreate = tProc.FileCreator;

But the line below uses a bracket notation to read the process serial number. Note the string PSN is enclosed in double quotes:

tPSN = tProc["PSN"];


Table 1. Five common keys used in the ProcessInformation class

The files property

The files property is an instance of the Files object (Figure 7). This object provides data on specified bundles or files on the target volume. The object has three method, the first one being fileExistsAtPath(). This method returns a TRUE if a file is at the specified path, FALSE if otherwise. It takes one argument, which is the path string. For example, to check for the file foobar.class in the /Library/Java directory, use the method as follows:

tChk = system.files.fileExistsAtPath ("/Library/Java/foobar.class");

By default, the method parses the path string with respect to the root volume. You can change this behavior by using the my.target property (as explained later). You can also use this method to check for a directory or a bundle.


Figure 7. The Files object

The second method bundleAtPath() checks if a bundle exists at the specified path. If TRUE, the method then returns the bundle's Info.plist data as an Array. You then use the Array to extract pertinent information about that bundle. For example, to get information on Xcode.app, use the method as shown below.

tInfo = system.files.bundleAtPath("/Developer/Applications/Xcode");

Then to get the Xcode version number, access the array tInfo as follows.

tVer = tInfo.CFBundleShortVersionString

The third method plistAtPath() checks to see if a plist file exists at the specified path. If this is true, the method then returns the file's contents as an Array. Assume you want to read the BBEdit's preferences file. To do so, type the method as follows.

tPlist = system.files.plistAtPath("/Library/Preferences/ \
   com.barebones.bbedit.plist");

All three methods, however, do not allow shortcut tokens such as '~' to be in the path string.

The ioregistry property

The ioregistry property stores an instance of the IORegistry object (Figure 8). This object queries the host system's device tree using the IORegistry API. For reasons of length, I will be unable to cover this property in detail. I did, however discuss this property in an article on driver and framework installers in the October 2008 issue of MacTech.


Figure 8. The IORegistry object

The My Object

The second global object in the Installer JavaScript dialect is the My object. It has only two properties: target and results. The results property is available in both the InstallationCheck and VolumeCheck phases of the install session. The target property, on the other hand, is available only in the VolumeCheck phase.

The target property

The target property stores an instance of the Target object (Figure 9). This object provides data on each target volume mounted on the host system. There are three properties in this object. One property, mountpoint, returns the path to the target volume from the root node. One way to use this property is as follows.

   tPth = my.target.mountpoint();

Assume, for example, the target system has the following two disk volumes: Foo and Bar. The property then returns the path to the Foo volume as /Volumes/Foo. For the Bar volume, it returns the path /Volumes/Bar.


Figure 9. The Target object

The availableKilobytes property returns the amount of free space in the target volume. It gives this value as an unsigned integer. Below is one way to use this property. Here, if the target volume has exactly 512 Mbytes of space, the above code snippet executes the code within the if-block.

if (my.target.availableKilobytes = (512 * 1024 * 1024))
{
   // do something here..
}

The systemVersion property provides the same service as the system.version property. In short, it also returns the contents of the file /System/Library/CoreServices/SystemVersion.plist file as an Array. But while system.version checks only the boot volume for the file, systemVersion checks all the target volumes. If systemVersion fails to find said file on a target volume, it returns a null string for that volume.

Finally, the Target object has one method called receiptForIdentifier(). This method takes a bundle ID string for input. It then looks for the directory /Library/Receipts in each target volume. If the directory has a receipt bundle with the specified ID, the method returns the contents of the bundle as an Array. Otherwise, it returns a NIL. One example of how to use this method is shown below.

tChk = my.target.receiptForBundle("com.mactech.demo.foobar");
if (tChk == nil) 
{
   // prepare for a new install..
}
else 
{
   // prepare for an upgrade action..
}

In this example, the snippet checks if the target volume has a receipt with an ID of com.mactech.demo.foobar. If this is so, the package assumes that the volume has the payload and prepares to do an upgrade. Otherwise, the snippet prepares to start a new install.

The results property

The results property is an instance of the Results object (Figure 10). InstallationCheck and VolumeCheck scripts use this object to report an error back to the package. The object has only three properties. The type property sets one of three possible error types: Warn, Fatal, and nil. The first two types will display an error dialog. But while a Warn error lets users continue with the install sessions, a Fatal error tells the package to stop the session immediately. A nil error type, on the other hand, tells the package that no errors have occurred, thus no dialog is required.


Figure 10. The Results object

The title property sets the short description of the error. This is the same as setting the Title field in the Requirements Editor dialog. Finally, the message property sets the detailed description of the error. This too is the same as setting the Message field in the editor dialog.

The lines below show one way of using the results property:

my.result.title = 'Leopard Support Only';
my.result.message = 'The payloads requires at least MacOS X 10.5';
my.result.type = 'Fatal';

These tell the package that a fatal error has occurred. The package then displays an error dialog with the specified message. Since the error is Fatal, the package ends the install session when users dismiss the dialog.

The Choice Object

The third global object in the Installer JavaScript arsenal is the Choice object (Figure 11). This object gives a package script access to the settings of a specific payload choice in the Custom Install panel. There are seven properties in this object. The first three properties: selected, enabled and visible, refer to the current state of the choice. The title property returns its name in the Custom Install panel, and the description property returns its short description.


Figure 11. The Choices object

The packageUpgradeAction property sets the type of install operation to perform. The packages property returns the payload's contents as an Array. Covering these two properties is beyond the scope of this article. I will, however, cover them in detail in a future PackageMaker article on relocation and upgrades.

To use the object, we first specify which payload to query. This we do with the choice ID value in the Identifier field. Assume, for example, your payload is the one shown in Figure 11. To read the title property, enter the following line.

tTitle = choice.choice2.title;

To disable that same payload, use the following line:

choice.choice2.enabled = false;

Writing The Requirements Scripts

One notable feature of PackageMaker 3.0 is its new Requirements Editor. This editor lets you define a requirements script without writing a single line of code. There are two variants of the editor. You get the first variant (Figure 12, left) from the Requirements panel for the whole package. On the other hand, you get the second variant (Figure 12, right) from the Requirements panel for each package. Both editor variants share the same set of check conditions. They differ, however, on what actions they specify for a failed check. The one on the left, for instance, specify what message to display on the error dialog. The one on the right, however, specify the state of the payload's choice.


Figure 12. Variants of the Requirements Editor

PackageMaker converts each check setting into a JavaScript script as it builds the installer package. It then stores the script into the package's Distribution.plist file. You can, of course, get a preview of the script by choosing Raw Editing Mode from the Project menu. This causes PackageMaker to switch its window to the one similar to Figure 13. On the left column of this window, you get a list of items that will appear in the package. Then if you select the entry Distribution, you get its contents shown on the right of the window. To switch back to the standard window layout, choose Normal Editing Mode from the Project menu.

You can also edit the contents of the Distribution file while PackageMaker is in raw editing mode. PackageMaker then updates the installer project with your changes when you choose Save from the File menu or when you switch back to the standard editing window. Be careful, however, as PackageMaker sometimes crashes during the switch, thus losing your changes.


Figure 13. The Raw Editor Mode

Scripting the InstallationCheck phase

Let us look at how PackageMaker handles the scripts for each phase of the requirements check. Once again, all the scripts featured here are from the Foobar demo project. The first check setting is shown in Figure 14. This check looks at the version string of the host system. If the host system is older than 10.5, the check then displays a fatal error dialog with the specified string.


Figure 14. Checking the host system

Listing 1 shows the script source for the above check. First, the script uses system.version to retrieve the version string from the host system. Then it sets my.result to the correct error message and type. Note the script is inside the function pm_install_check(). This function serves as the hook to the InstallationCheck phase. If you change the name of this function, you will disable that phase of the requirements check.

Listing 1. Checking the system version

function pm_install_check() {
  if(!(system.version.ProductVersion >= '10.5')) {
    my.result.title = 'Leopard Support Only';
    my.result.message 
         = 'The payloads requires at least MacOS X 10.5 or newer.';
    my.result.type = 'Fatal';
    return false;
  }
  return true;
}

Figure 15 shows the second check setting in the Foobar project. This check looks at the number of CPUs in the host system. If it finds only one CPU, the check displays a warning error dialog with its own message.


Figure 15. Checking the number of CPUs

Listing 2 shows the script source for the above setting. Here, the script uses a system.sysctl() call to get the number of CPUs. It then sets my.result to the correct message and type, which is Warn in this case. Note that the first part of the script is the check setting shown in Figure 14. Note as well that both settings are in the same pm_install_check() hook function. In fact, it is common for an InstallationCheck phase to have more than one check settings.

Listing 2. Checking the system version and CPU capacity

function pm_install_check() {
  if(!(system.version.ProductVersion >= '10.5')) {
    my.result.title = 'Leopard Support Only';
    my.result.message 
         = 'The payloads requires at least MacOS X 10.5 or newer.';
    my.result.type = 'Fatal';
    return false;
  }
   
  if(!(system.sysctl('hw.ncpu') > 1)) {
    my.result.title = 'Insufficient CPU Capacity';
    my.result.message 
         = 'The payload runs best on multiprocessor system.';
    my.result.type = 'Warn';
  }
  return true;
}

Scripting the VolumeCheck phase

The third check setting in the Foobar project is shown in Figure 16. Here, the check measures the amount of free space in each target volume. If a volume has less than 512 Mbytes of space, the check displays its error message when users try to select the volume.


Figure 16. Checking for free space

The script source for the above check is shown in Listing 3. Note the pm_volume_check() function that encloses the script. This function serves as the hook to the VolumeCheck phase of the install session. Note as well that the script multiples the value 512 by 1024 twice. This is because the property my.target.availableKilobytes returns the free space value in bytes, not in kilobytes as stated by the check.

Listing 3. Checking for free space

function pm_volume_check() {
  if(!(my.target.availableKilobytes > 512 * 1024 * 1024)) {
    my.result.title = 'Failure';
    my.result.message 
         = 'There is not enough free space on this target volume.';
    my.result.type = 'Fatal';
    return false;
  }
  return true;
}

Figure 17 shows the fourth check setting in the Foobar project. This time, the check scans for an Application directory in the root level of each target volume. Again, if a volume does not have this directory, the check displays its error message for that volume.


Figure 17. Checking for a directory

The script source for this check is in Listing 4. Examine, this time, the second if-block at the end of pm_volume_check(). Note the use of system.files.fileExistsAtPath() to retrieve the path to the Application directory. Note as well the use of my.target.mountpoint to complete the path string to the directory. Again, each check in the VolumeCheck phase runs in the order they appear on the Requirements panel.

Listing 4. Checking for free space and a directory

function pm_volume_check() {
   if(!(my.target.availableKilobytes > 512 * 1024 * 1024)) {
      my.result.title = 'Failure';
      my.result.message 
         = 'There is not enough free space on this target volume.';
      my.result.type = 'Fatal';
      return false;
   }
   
   if(!(system.files.fileExistsAtPath(my.target.mountpoint 
         + '/Applications') == true)) {
      my.result.title = 'Failure';
      my.result.message 
         = 'The volume does not have  an Applications directory.';
      my.result.type = 'Fatal';
      return false;
   }
   return true;
}

Scripting the PayloadCheck phase

Figure 18 shows a typical check setting for one of the payloads. This check examines the sysctl node to see if the host system has Altivec hardware. If the check fails, it deselects and disables the choice for that payload.


Figure 18. Checking for Altivec

Listing 5 shows the script source for the above check setting. Note the use of two hook functions for the same payload choice. Each function changes a specific state of that choice. For instance, pm_choice2_selected() sets the selected state of the choice. Note as well that both functions share the same script. This may appear redundant at first, but it implies that either function can also have its own script base.

Finally, PackageMaker creates these hook functions only when the failure state for the choice is set to either Yes or No. If the state is set to Unchanged, as it is for the Hidden state, the tool will not create a hook function for that state. Furthermore, if there are more than one payload choices, PackageMaker will create separate set of hook for each choice.

Listing 5. Checking for Altivec

function pm_choice2_selected() {
   result = true;
   result = result && 
      (system.sysctl('hw.optional.altivec') == '1');
   return result;
}
function pm_choice2_enabled() {
   result = true;
   result = result && 
      (system.sysctl('hw.optional.altivec') == '1');
   return result;
}

Scripting with JavaScript

Sometimes, you may find the predefined check conditions unsuitable for certain tasks. If this is the case, you can write a custom JavaScript function and link it to a specific phase. Or you can link an external shell script to one of the phases. You can do either one via the new Requirements Editor.

To use a custom JavaScript function to check the host system, choose Result of JavaScript as the check condition. To use the function to check a target volume, choose Result of JavaScript for Target. Either way, the editor switches to a new settings view (Figure 19, left). Now, click the Script Repository button to enter the editing mode (Figure 19, right). Type in your JavaScript function in the field provided. Click the Save button when you are done. Next, enter the function's name in the field provided by the editor. Set the desired result and failure message, and click the OK button to add the check setting to the package.


Figure 19. Adding a custom JavaScript function

Assume we have the custom function in Listing 6. This function first finds out if Xcode is present at the specified path. Then it checks if this is version 3.0 of Xcode. The function returns a TRUE if both conditions are met; otherwise, it returns a FALSE.

Listing 6. Checking for Xcode

function checkForXcode() { 
   var chk_flag = false;
   var xcode_vers;
   
   try { 
      // is Xcode installed?
      chk_flag =
         system.files.fileExistsAtPath('/Developer/¬
         Applications/Xcode.app'); 
      
      if (chk_flag) {
         // is it the right version of Xcode?
         xcode_data = 
            system.files.bundleAtPath('/Developer/Applications/Xcode.app');
         xcode_vers = xcode_vers.CFBundleShortVersionString;
         chk_flag = (xcode_vers == '3.0');
      }
   } catch (e) {
      // an exception just occurred
      return (false)
   } 
   // return the check results   
   return chk_flag; 
} 

To call this function, update the check setting as shown in Figure 20. This tells the package to use checkForXcode() during the InstallationCheck phase. If we switch to raw editing mode, we will see the call made in the Distribution file as shown in Listing 7.


Figure 20. Checking for Xcode

Listing 7. Calling the custom JavaScript function

function pm_install_check() {
  if(!(checkForXcode() == true)) {
    my.result.title = 'Incorrect version of Xcode';
    my.result.message 
         = 'The payload requires version 3.0 of the Xcode IDE';
    my.result.type = 'Warn';
  }
  return true;
}

You can have more than one JavaScript functions in the Script Repository. Also, you can call any of these functions in any of three phases of the requirements check. Make sure, however, that the JavaScript object used by your function is available at each phase.

Scripting with an external shell script

You can also use an external shell script to do complex checks in each install phase. To use a shell script to check the host system, choose Result of Script as the check condition. To check the target volume, choose Result of Script for Target. Again, the editor will display the settings panel in Figure 21 for either condition.


Figure 21. Using an external shell script

Assume you want your package to run foobar.sh in the InstallationCheck phase. To add the script, type its name in the file path field. Alternatively, click the Choose button and use the Open File dialog to find and select foobar.sh. Figure 22 shows how the check setting may appear when using an external script. Listing 8 shows how the call to foobar.sh appears in the Distribution file. Note the use of system.run() to call the script.


Figure 22. Using the script foobar.sh

You can write your external scripts in your favorite shell language. The only restriction is that script must not display a user interface as it executes. The script must also return a TRUE or FALSE when it ends its run. Otherwise, the package will assume a default value of TRUE. And a copy of the script must be inside the package's Resources directory in order to be used. PackageMaker, however, does not create that copy when it builds the package. You must, therefore, perform this last task manually.

Listing 8. Calling foobar.sh

function pm_install_check() {
   if(!(system.run('foobar.sh') == true)) {
    my.result.title = 'Script Error';
    my.result.message 
         = 'The host system failed to pass the checks made by foobar.sh';
    my.result.type = 'Fatal';
    return false;
  }
  return true;
}

Concluding Remarks

PackageMaker 3.0 makes it very easy for you to add a requirements check to your installer package. Using its new Requirements Editor, you can add a simple check without writing a single line of code. You can also use it to write a custom JavaScript function and link that function to the right phase. You can even use it to choose which external shell script to run at each phase.

The tool is not without its faults, however. Yet, thanks largely to Apple's hardworking developers, the tool is evolving into a reliable tool for building and customizing installer packages.

Bibliography and References

Apple Computers. PackageMaker Users Guide. 2007 Jul 23. Copyright 2007. Apple Computers, Inc. Online:

http://developer.apple.com/DOCUMENTATION/DeveloperTools/Conceptual/PackageMakerUserGuide/Introduction/chapter_1_section_1.html

Apple Computers. Installer JavaScript Reference. 2007 Jul 23. Copyright 2007. Apple Computers, Inc. Online:

http://developer.apple.com/documentation/DeveloperTools/Reference/InstallerJavaScriptRef/index.html


JC is a freelance engineering writer from North Vancouver, British Columbia. He spends his time writing technical articles; tinkering with Cocoa, REALbasic, and Python; and visiting his foster nephew. He can be reached at anarakisware@gmail.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Sibelius 2020.6 - Music notation solutio...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Bookends 13.4.2 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
OmniGraffle Pro 7.16 - Create diagrams,...
OmniGraffle Pro helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use... Read more
Drive Genius 6.1.0 - $79.00
Drive Genius features a comprehensive Malware Scan. Automate your malware protection. Protect your investment from any threat. The Malware Scan is part of the automated DrivePulse utility. DrivePulse... Read more
Tor Browser 9.5 - Anonymize Web browsing...
The Tor Browser Bundle is an easy-to-use portable package of Tor, Vidalia, Torbutton, and a Firefox fork preconfigured to work together out of the box. It contains a modified copy of Firefox that... Read more
VueScan 9.7.28 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
OmniGraffle 7.16 - Create diagrams, flow...
OmniGraffle helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use Graffle to... Read more
WALTR 2 2.6.26 - $39.95
WALTR 2 helps you wirelessly drag-and-drop any music, ringtones, videos, PDF, and ePub files onto your iPhone, iPad, or iPod without iTunes. It is the second major version of Softorino's critically-... Read more
Airmail 4.1 - Powerful, minimal email cl...
Airmail is an mail client with fast performance and intuitive interaction. Support for iCloud, MS Exchange, Gmail, Google Apps, IMAP, POP3, Yahoo!, AOL, Outlook.com, Live.com. Airmail was designed... Read more
Iridient Developer 3.3.12 - Powerful ima...
Iridient Developer (was RAW Developer) is a powerful image-conversion application designed specifically for OS X. Iridient Developer gives advanced photographers total control over every aspect of... Read more

Latest Forum Discussions

See All

Willy Jetman: Astromonkey's Revenge...
Barcelona-based developer Last Chicken Games are set to bring their game Willy Jetman: Astromonkey's Revenge to both iOS and Android on 9th July. The Metroidvania is already available on the likes of PS4, Switch and PC but now mobile folk will be... | Read more »
The 5 Best Mobile Real Time Strategy Gam...
Real-time strategy games feel like they’d be a perfect fit for mobile, but they’re trickier to pull off that you might think. The traditional mold of base-building and micro management can work on touch screens, but needs to be carefully honed so... | Read more »
Using your phone in a protest
I can't write about games today. There is a struggle happening in the streets right now and it needs everyone's attention. Here's some good info on how you can use your iOS device safely amidst a protest. | Read more »
Dungonian is a card-based dungeon crawle...
Dungonian is a card-based dungeon crawler from developer SandFish Games that only recently launched as a free-to-play title. It offers an extensive roster of playable heroes to collect and enemies to take down, and it's available right now for iOS... | Read more »
Steam Link Spotlight - Signs of the Sojo...
Steam Link Spotlight is a feature where we look at PC games that play exceptionally well using the Steam Link app. Our last entry was XCOM: Chimera Squad. Read about how it plays using Steam Link's new mouse and keyboard support over here. | Read more »
Steampunk Tower 2, DreamGate's sequ...
Steampunk Tower 2 is a DreamGate's follow up to their previous tower defence game. It's available now for both iOS and Android as a free-to-play title and will see players defending their lone base by kitting it out with a variety of turrets. [... | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »
Pokemon Go Fest 2020 will be a virtual e...
Niantic has announced that Pokemon Go Fest will still take place this year although understandably it won't be a physical event. Instead, it will become a virtual celebration and is set to be held on 25th and 26th July. [Read more] | Read more »
Marvel Future Fight's major May upd...
Marvel Future Fight's latest update has now landed, and it sounds like a big one. The focus this time around is on Marvel's Guardians of the Galaxy, and it introduces all-new characters, quests, and uniforms for players to collect. [Read more] | Read more »
SINoALICE, Yoko Taro and Pokelabo's...
Yoko Taro and developer Pokelabo's SINoALICE has now opened for pre-registration over on the App Store. It's already amassed 1.5 million Android pre-registrations, and it's currently slated to launch on July 1st. [Read more] | Read more »

Price Scanner via MacPrices.net

Apple restocks 27″ iMacs, Certified Refurbish...
Apple has restocked Certified Refurbished 2019 27″ iMacs starting at $1529 and up to $350 off the cost of new models. Apple’s one-year warranty is standard, shipping is free, and each iMac features a... Read more
Apple’s new 2020 13″ 4-Core MacBook Airs on s...
B&H Photo has Apple’s new 2020 13″ 4-Core MacBook Airs on sale today for $100 off Apple’s MSRP, only $1199. Expedited shipping is free to many addresses in the US. The MacBook Airs are the same... Read more
New Verizon promo: $150 off any Apple Watch w...
Verizon is offering $150 off any Apple Watch when purchased alongside an iPhone through June 10, 2020. They’re also offering up to $100 on any Apple Watch trade-in. Here are the details: “Get $150... Read more
Last year’s 13″ 2.4GHz MacBook Pros are avail...
Apple has Certified Refurbished 2019 13″ 2.4GHz/256GB 4-Core Touch Bar MacBook Pros available for $1359, $440 off original MSRP. Apple’s one-year warranty is included, shipping is free, and each... Read more
Apple’s new 2020 13″ MacBook Pros on sale for...
Apple reseller Abt Electronics has new 2020 13″ MacBook Pros on sale today for up to $140 off MSRP, starting at $1208. Shipping is free, and most configurations are in stock today. Note that Abt’s... Read more
Apple CEO Reacts To Nationwide Protests Over...
NEWS: 06.03.20 – With the recent death of a black man in the custody of a white police officer igniting outrage among Americans from all walks of life, which resulted in protests and civil unrest... Read more
At up to $420 off MSRP, these Certified Refur...
Apple has Certified Refurbished 2019 16″ MacBook Pros available for up to $420 off the cost of new models, starting at $2039. Each model features a new outer case, shipping is free, and an Apple 1-... Read more
Apple restocks refurbished 3rd generation 12....
Apple restocked select 3rd generation 12.9″ WiFi iPad Pros starting at only $699 and up to $330 off original MSRP. Each iPad comes with a standard Apple one-year warranty, outer cases are new, and... Read more
These wireless carriers are offering Apple’s...
Looking for a deal on Apple’s new iPhone SE? Apple itself offers the unlocked 64GB iPhone SE for $399 or $16.62/month. If you’re willing to try a new carrier, two of Apple’s wireless carriers are... Read more
Save $80 on the 64GB 10.5″ iPad Air with this...
Apple has 10.5″ 64GB WiFi iPad Airs models available for $80 off MSRP, Certified Refurbished. Each iPad comes with Apple’s standard one-year warranty and includes a new outer case. Shipping is free... Read more

Jobs Board

*Apple* Architect - SAIC (United States)
**Description** We are currently seeking a motivated, career and customer oriented Apple Architect to join our team in Washington, DC to begin an exciting and Read more
*Apple* Support Engineer - SAIC (United Stat...
**Description** We are currently seeking a motivated, career and customer oriented Apple Support Engineer to join our team in Washington, DC to begin an exciting and Read more
Perioperative RN - ( *Apple* Hill Surgical C...
Perioperative RN - ( Apple Hill Surgical Center) Tracking Code 60593 Job Description Monday - Friday - Full Time Days Possible Saturdays General Summary: Under the Read more
Senior Practice Manager - *Apple* Hill Eye...
Senior Practice Manager - Apple Hill Eye Center Tracking Code 61713 Job Description Apple Hill Medical Center General Summary: Under general supervision, manages Read more
*Apple* Mac Desktop Support - Global Dimensi...
…Operate and support an Active Directory (AD) server-client environment for all Apple devices operating on the BUMED network + Leverage necessary industry enterprise Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.