Monday, February 21, 2011

Gestalt

Sometimes it is useful to lookup the operating system version that your code is running on. On the Mac OS X, this information can be retrieved in several ways. We will be adding Factor support for using the Gestalt function (available as part of the Gestalt Manager in the CoreServices.framework since Mac OS X 10.0).

First, we create a namespace and list of vocabularies we will be using:

USING: alien.data alien.syntax combinators core-foundation
formatting io.binary kernel math ;

IN: gestalt

Using Factor's C library interface, we can declare the function and its types:

TYPEDEF: SInt16 OSErr

TYPEDEF: UInt32 OSType

FUNCTION: OSErr Gestalt ( OSType selector, SInt32* response ) ;

Using this, we can create the gestalt word, which calls the function, checks for errors, and returns the result:

: gestalt ( selector -- response )
    { SInt32 } [ Gestalt 0 assert= ] with-out-parameters ;

Looking at Gestalt.h, we can see several enum definitions that can be used to retrieve system version information:

enum {
  gestaltSystemVersion          = 'sysv',
  gestaltSystemVersionMajor     = 'sys1',
  gestaltSystemVersionMinor     = 'sys2',
  gestaltSystemVersionBugFix    = 'sys3'
};

Knowing these, we can create words for accessing the various system versions exposed by Gestalt.

: system-version ( -- n ) "sysv" be> gestalt ;

: system-version-major ( -- n ) "sys1" be> gestalt ;

: system-version-minor ( -- n ) "sys2" be> gestalt ;

: system-version-bugfix ( -- n ) "sys3" be> gestalt ;

However, we see a comment in Gestalt.h which says:

If the values of the minor or bug fix revision are larger
than 9, then gestaltSystemVersion will substitute the value
9 for them. For example, Mac OS X 10.3.15 will be returned
as 0x1039, and Mac OS X 10.10.5 will return 0x1095.

A better way to get version information on Mac OS X
would be to use the new gestaltSystemVersionMajor,
gestaltSystemVersionMinor, and gestaltSystemVersionBugFix
selectors, which don't have arbitrary limits on the values
returned.

With this limitation, we could still use the system-version to retrieve Apple's "code name" for the version of Mac OS X you are running (since it works for all of the released Mac OS X versions):

: system-code-name ( -- str )
    system-version HEX: FFF0 bitand {
        { HEX: 1070 [ "Lion"         ] }
        { HEX: 1060 [ "Snow Leopard" ] }
        { HEX: 1050 [ "Leopard"      ] }
        { HEX: 1040 [ "Tiger"        ] }
        { HEX: 1030 [ "Panther"      ] }
        { HEX: 1020 [ "Jaguar"       ] }
        { HEX: 1010 [ "Puma"         ] }
        { HEX: 1000 [ "Cheetah"      ] }
        [ drop "Unknown" ]
    } case ;

And then, we can make a version string using each of the major, minor, and bugfix values:

: system-version-string ( -- str )
    system-version-major
    system-version-minor
    system-version-bugfix
    "%s.%s.%s" sprintf ;

Using this, we can see which version of Mac OS X is running on my laptop:

( scratchpad ) system-version-string system-code-name 
              "%s (%s)\n" printf
10.6.6 (Snow Leopard)

By the way, another comment in Gestalt.h says:

If you want to know the product build version string,
product name, or the user visible version string you should
read in the system version information from the file
/System/Library/CoreServices/SystemVersion.plist.

Using the cocoa.plist vocabulary, we can do just that:

( scratchpad ) USE: cocoa.plist

( scratchpad ) "/System/Library/CoreServices/SystemVersion.plist"
               read-plist .
H{
    { "ProductVersion" "10.6.6" }
    { "ProductName" "Mac OS X" }
    { "ProductBuildVersion" "10J567" }
    { "ProductUserVisibleVersion" "10.6.6" }
    { "ProductCopyright" "1983-2011 Apple Inc." }
}

The code for this is on my Github.

No comments: