Re: Factor

Factor: the language, the theory, and the practice.

Gestalt

Monday, February 21, 2011

#ffi #macos

Sometimes it is useful to lookup the operating system version that your code is running on. On the Mac OS, 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 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 10.3.15 will be returned
as 0x1039, and Mac OS 10.10.5 will return 0x1095.

A better way to get version information on Mac OS
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 you are running (since it works for all of the released Mac OS versions):

: system-code-name ( -- str )
    system-version 0xFFF0 bitand {
        { 0x1070 [ "Lion"         ] }
        { 0x1060 [ "Snow Leopard" ] }
        { 0x1050 [ "Leopard"      ] }
        { 0x1040 [ "Tiger"        ] }
        { 0x1030 [ "Panther"      ] }
        { 0x1020 [ "Jaguar"       ] }
        { 0x1010 [ "Puma"         ] }
        { 0x1000 [ "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 is running on my laptop:

IN: 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:

IN: scratchpad USE: cocoa.plist

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

The code for this is on my GitHub.