Minimum System Version Check - With Reliable Notification
-
List:
Over the years, the standard mechanisms for requiring a minimum
system version and displaying a kind message to the user have proven
unreliable. I recently had the need to revisit this issue but (as far
as I can tell) searching the list archives and all the other usual
sources don't quite reveal a complete solution, merely suggestions.
I've seen a few people suggest creating a 'miniature' run loop or
using Carbon, but in my mind, the cleanest, most straightforward
solution was just to put the responsibility back into the hands of the
OS (where it was supposed to be in the first damn place). AppleScript
to the rescue.
For the archives, following is a quick and dirty - but complete -
solution to this issue. It assumes 10.5 as the minimum system (I trust
it's obvious what to change and where for other versions). Perhaps not
so obvious to newbies, this belongs in your project's main.m file.
int main(int argc, char *argv[])
{
// Obtain system version
static SInt32 systemVersion;
Gestalt(gestaltSystemVersion, &systemVersion);
// Only run if we're on 10.5 or above ...
if (systemVersion >= 0x1050)
{
// We're fine - start the app instance
return NSApplicationMain(argc, (const char **) argv);
} else {
// Fails minimum requirement - we'll use AppleScript to
// display a warning message to bypass the OS X
// system version checking mess ...
// Set up an autorelease pool (NSAppleScript needs it)
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Create then execute the AppleScript, ignoring any errors
// (Gasp! Ignore errors? Yes - if the script doesn't compile
// or it fails, more is wrong than we can do anything about ...)
NSString * source = @"beep\ndisplay alert \"This awesome application
requires Mac OS X 10.5 or above and cannot run on your computer. Why
on God's great creation are you not up to date?! Bad user, BAD USER!\"
as warning";
NSAppleScript * script = [[[NSAppleScript alloc]
initWithSource:source] autorelease];
[script executeAndReturnError:nil];
// Release the pool. It probably doesn't matter, but what kind
// of Cocoa developer would I be if I didn't? What will people SAY?!
[pool release];
// Goodbye, cruel world!
return -1
}
// To be honest, I'm not sure what's best here, but since
// NSApplicationMain() isn't supposed to come back, errors
// are handled, and main() requires a return, a clean
// return seems the right thing to do ... YMMV.
return 0;
}
AppleScript will beep (with the user's preferred standard error
sound) display the caution icon with your app's icon grafted atop. You
may or may not want to lose the beep. Not sure why caution / error
dialogs don't automatically sound the error beep anyway, but that's
another philosophical debate entirely.
I'd recommend completely foregoing the use of the
LSMinimumSystemVersion key approach (search the docs and the list
archives if you're unfamiliar) since the user will get two "this won't
work" messages when the LSMinimumSystemVersion is actually honored.
Two such messages may seem like you're just rubbing it in. ;-)
Alternatively, you could include the already-compiled script in the
app's resources to save a millisecond or two during runtime, but I
don't feel it's necessary and I would think it'd be easier just to
change the message in the app's source.
Also, regarding the return, I didn't bother looking up exactly what
the implications are but I'd imagine there are a few other ways it can
be done, all of which are probably more or less fine. I'd be
interested in opinions on this.
Experts are welcome to pick the whole thing apart and point out its
wrongness as desired. :-)
--
I.S. -
Hi,
I think the primary issue with this approach is that the app may not get as
far as main.
If the app uses a symbol from the frameworks that is not available at
runtime, dyld will fail to load the binary.
Aaron Hillegass gives a solution at
http://weblog.bignerdranch.com/?p=13that involves using an auxiliary
executable. A shell process that has very
good compatibility loads, and if it determines that the OS is new enough, it
loads the real application.
-Ken
On Sun, Feb 1, 2009 at 9:26 AM, I. Savant <idiotsavant2005...> wrote:> List:
>
> Over the years, the standard mechanisms for requiring a minimum system
> version and displaying a kind message to the user have proven unreliable. I
> recently had the need to revisit this issue but (as far as I can tell)
> searching the list archives and all the other usual sources don't quite
> reveal a complete solution, merely suggestions.
>
> I've seen a few people suggest creating a 'miniature' run loop or using
> Carbon, but in my mind, the cleanest, most straightforward solution was just
> to put the responsibility back into the hands of the OS (where it was
> supposed to be in the first damn place). AppleScript to the rescue.
>
> For the archives, following is a quick and dirty - but complete - solution
> to this issue. It assumes 10.5 as the minimum system (I trust it's obvious
> what to change and where for other versions). Perhaps not so obvious to
> newbies, this belongs in your project's main.m file.
>
>
>
> int main(int argc, char *argv[])
> {
> // Obtain system version
> static SInt32 systemVersion;
> Gestalt(gestaltSystemVersion, &systemVersion);
>
> // Only run if we're on 10.5 or above ...
> if (systemVersion >= 0x1050)
> {
>
> // We're fine - start the app instance
> return NSApplicationMain(argc, (const char **) argv);
>
> } else {
>
> // Fails minimum requirement - we'll use AppleScript to
> // display a warning message to bypass the OS X
> // system version checking mess ...
>
> // Set up an autorelease pool (NSAppleScript needs it)
> NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
>
> // Create then execute the AppleScript, ignoring any errors
> // (Gasp! Ignore errors? Yes - if the script doesn't compile
> // or it fails, more is wrong than we can do anything about
> ...)
> NSString * source = @"beep\ndisplay alert \"This awesome
> application requires Mac OS X 10.5 or above and cannot run on your computer.
> Why on God's great creation are you not up to date?! Bad user, BAD USER!\"
> as warning";
> NSAppleScript * script = [[[NSAppleScript alloc]
> initWithSource:source] autorelease];
> [script executeAndReturnError:nil];
>
> // Release the pool. It probably doesn't matter, but what
> kind
> // of Cocoa developer would I be if I didn't? What will
> people SAY?!
> [pool release];
>
> // Goodbye, cruel world!
> return -1
>
> }
>
> // To be honest, I'm not sure what's best here, but since
> // NSApplicationMain() isn't supposed to come back, errors
> // are handled, and main() requires a return, a clean
> // return seems the right thing to do ... YMMV.
> return 0;
> }
>
> AppleScript will beep (with the user's preferred standard error sound)
> display the caution icon with your app's icon grafted atop. You may or may
> not want to lose the beep. Not sure why caution / error dialogs don't
> automatically sound the error beep anyway, but that's another philosophical
> debate entirely.
>
> I'd recommend completely foregoing the use of the LSMinimumSystemVersion
> key approach (search the docs and the list archives if you're unfamiliar)
> since the user will get two "this won't work" messages when the
> LSMinimumSystemVersion is actually honored. Two such messages may seem like
> you're just rubbing it in. ;-)
>
> Alternatively, you could include the already-compiled script in the app's
> resources to save a millisecond or two during runtime, but I don't feel it's
> necessary and I would think it'd be easier just to change the message in the
> app's source.
>
> Also, regarding the return, I didn't bother looking up exactly what the
> implications are but I'd imagine there are a few other ways it can be done,
> all of which are probably more or less fine. I'd be interested in opinions
> on this.
>
> Experts are welcome to pick the whole thing apart and point out its
> wrongness as desired. :-)
>
> --
> I.S.
> -
On Feb 1, 2009, at 3:54 PM, Ken Ferry wrote:> I think the primary issue with this approach is that the app may not
> get as far as main.
>
> If the app uses a symbol from the frameworks that is not available
> at runtime, dyld will fail to load the binary.
>
Ah *ha* ... you are of course absolutely right.
I confess I hadn't even thought this issue. Alas it seems Aaron's
suggestion is probably the only real solution until we're so far past
10.5 (where the issue is supposed to be "good and fixed") that we
react with shock when we learn the user is still using 10.5.
That sucks.
Thanks for the shot of common sense, though - that was pretty lame
of me. ;-) I completely failed to think the problem all the way
through. I guess it's obvious that the app I tested this on uses some
pretty plain (old-school) Cocoa. :-)
--
I.S.


