Vincent Gable’s Blog

May 4, 2008

Getting Mac OS X Version Information

Filed under: MacOSX,Objective-C,Programming,Sample Code,UNIX | , ,
― Vincent Gable on May 4, 2008

Cocoa

For a human-readable string, use [[NSProcessInfo processInfo] operatingSystemVersionString]. It looks like “Version 10.5 (Build 9A581)”, but that format might change in the future. NSProcessInfo.h explicitly warns against using it for parsing. It will always be human-readable though.

For machine-friendly numbers, use the Gestalt function, with one of the selectors:
gestaltSystemVersionMajor (in 10.4.17 this would be the decimal value 10)
gestaltSystemVersionMinor (in 10.4.17 this would be the decimal value 4)
gestaltSystemVersionBugFix (in 10.4.17 this would be the decimal value 17)
Do not use gestaltSystemVersion unless your code needs to run on OS X 10.2 or earlier. It’s a legacy function that can’t report minor/bugfix versions > 9; meaning it can’t distinguish between 10.4.9 and 10.4.11. The CocoaDev wiki has example code that works in 10.0 if you need to go that route.

Here’s a simpler example of using Gestalt:

- (BOOL) usingLeopard {
  long minorVersion, majorVersion;
  Gestalt(gestaltSystemVersionMajor, &majorVersion);
  Gestalt(gestaltSystemVersionMinor, &minorVersion);
  return majorVersion == 10 && minorVersion == 5;
}

Scripts

For scripts and command-line work, there is the sw_vers command.
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.5
BuildVersion: 9A581
$ sw_vers -productName
Mac OS X
$ sw_vers -productVersion
10.5
$ sw_vers -buildVersion
9A581

Java

Check the value of:

System.getProperty("os.name");
System.getProperty("os.version");

Fallback

Finally, if you must, you can parse /System/Library/CoreServices/SystemVersion.plist — but I don’t like the idea of doing that. There’s a chance that the file could have been corrupted or maliciously altered. It seems safer, and less complicated, to directly ask the operating system it’s version.

Minimum OS Requirements

To enforce a minimum OS requirement for your application, you can set the LSMinimumSystemVersion key in the Info.plist file. Big Nerd Ranch has a more robust, user-friendly, but complicated solution for older legacy systems.

April 23, 2008

Printing a FourCharCode

Filed under: Bug Bite,C++,Cocoa,MacOSX,Programming,Sample Code,Tips | , , , ,
― Vincent Gable on April 23, 2008

A lot of Macintosh APIs take or return a 32-bit value that is supposed to be interpreted as a four character long string. (Anything using one of the types FourCharCode, OSStatus, OSType, ResType, ScriptCode, UInt32, CodecType probably does this.) The idea is that the value 1952999795 tells you less, and is harder to remember, then 'this'.

It’s a good idea, unfortunately there isn’t a simple way to print integers as character strings. So you have to write your own code to print them out correctly, and since it has to print correctly on both big endian and little endian machines this is tricker then it should be. I’ve done it incorrectly before, but more importantly so has Apple’s own sample code!

Here are the best solutions I’ve found. Please let me know if you have a better way, or if you find any bugs. (The code is public-domain, so use it any way you please.)

FourCharCode to NSString:
use NSString *NSFileTypeForHFSTypeCode(OSType code);
Results are developer-readable and look like: @"'this'", but Apple says “The format of the string is a private implementation detail”, so don’t write code that depends on the format.

FourCharCode to a C-string (char*):
use the C-macro

#define FourCC2Str(code) (char[5]){(code >> 24) & 0xFF, (code >> 16) & 0xFF, (code >> 8) & 0xFF, code & 0xFF, 0}
(requires -std=c99; credit to Joachim Bengtsson). Note that the resulting C-string is statically allocated on the stack, not dynamically allocated on the heap; do not return it from a function.
If you want a value on the heap, try:


void fourByteCodeString (UInt32 code, char* str){
  sprintf(str, "'%c%c%c%c'",
    (code >> 24) & 0xFF, (code >> 16) & 0xFF,
    (code >> 8) & 0xFF, code & 0xFF);
}

In GDB, you can print a number as characters like so:

(gdb) print/T 1936746868
$4 = 'spit'

(via Borkware’s GDB tips)

If you do end up rolling your own FourCharCode printer (and I don’t recommend it!), then be sure to use arithmetic/shifting to extract character values from the integer. You can not rely on the layout of the integer in memory, because it will change on different machines. Even if you are certain that you’re code will only run on an x86 machine, it’s still a bad idea. It limits the robustness and reusability of your code. It sets you up for an unexpected bug if you ever do port your code. And things change unexpectedly in this business.

Printing a FourCharCode is harder then it should be. Experienced developers who should know better have been bitten by it (and so have I). The best solution is probably a %format for printf/NSLog that prints integers as character strings. Unfortunately, it doesn’t look like we’ll be seeing that anytime soon.

April 11, 2008

NSWindow setResizable:

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code | ,
― Vincent Gable on April 11, 2008

-[NSWindow setResizable:(BOOL)] does not exist. But, here is a way to get that effect as long as the window was resizable when created. (I don’t know of a way to make a window resizeable if it was first created unresizeable).

The basic idea is to show/hide the resizing controls, and implement delegate functions that prevent the window from being resized when the resizing controls are hidden.

//show or hide hide the window's resizing controls:
[[window standardWindowButton:NSWindowZoomButton] setHidden:!resizable];
[window setShowsResizeIndicator:resizable];

Note that setShowsResizeIndicator: only shows/hides the resize-indicator in the lower-right of the window. It will not make the window non-resizeable.

To keep the window from being resized when showsResizeIndicator is false, implement the delegate methods:

- (NSSize)windowWillResize:(NSWindow *) window toSize:(NSSize)newSize
{
	if([window showsResizeIndicator])
		return newSize; //resize happens
	else
		return [window frame].size; //no change
}

- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
{
	//let the zoom happen iff showsResizeIndicator is YES
	return [window showsResizeIndicator];
}

No Delegate Hack

Finally, there is a hack that avoids using delegate methods, although I don’t recommend it.

[[window standardWindowButton:NSWindowZoomButton] setHidden:!resizable];
[window setShowsResizeIndicator:resizable];
CGFloat resizeIncrements = resizable ? 1 : MAXFLOAT;
[window setResizeIncrements:NSMakeSize(resizeIncrements, resizeIncrements)];

setResizeIncrements “Restricts the user’s ability to resize the receiver so the width and height change by multiples of width and height increments.” So setting it to MAXFLOAT x MAXFLOAT means that the user can’t resize the window, because they won’t have a big enough screen. Note that this won’t disable the “zoom” function, although the user probably couldn’t use it with the zoom button hidden.

This approach uses slightly fewer lines of code, but it is more brittle and indirect, since it depends on the screen-size, not if the window is (visibly) resizeable. I wouldn’t use it over delegates. But here it is.

March 18, 2008

Finding your laptop’s location with a shell command

Filed under: MacOSX,Programming,Sample Code,Tips,UNIX | ,
― Vincent Gable on March 18, 2008

Every wireless access point has a MAC address — a 48 bit number that uniquely identifies it. By checking the MAC address of the wireless access point your computer is connected to, you can figure out if it is being used at home, in your office, etc.

You can read this value from the shell with the command:
arp `ipconfig getoption en1 router`
Here’s how it works. The command ipconfig getoption en1 router gets the IP address of the router for the interface en1, which is the name of the Airport interface. If you want to get the router connected to the ethernet port use en0. The output of the command looks like:
128.62.96.1

arp prints information about the IP address, including the MAC address. arp is a little tricky in that it needs to take the address as an argument, you can’t pipe the output from ipconfig into it — that’s why ipconfig getoption en1 router is surrounded in “. Output from arp 128.62.96.1 looks like:
cisco-128-62-96-1.public.utexas.edu (128.62.96.1) at 0:12:44:f:5f:0 on en1 [ethernet]

The MAC address is in there, but you may need to use a regular expression to get at it. Here is an example of a perl script which prints just the MAC address of the wireless access point, or an error message.

if (qx{arp `ipconfig getoption en1 router`} =~ /([0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2})/){
   print "$1\n";
} else {
   print "Error; wireless internet is most likely unavailable\n";
}

This is essentially how IMLocation determines location.

In summary: Using the shell commands: arp `ipconfig getoption en1 router`, and the regular expression ([0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}:[0-9A-Fa-f]{1,2}) on it’s output, you can get the MAC-address of the wireless access point your computer is using. This gives you the computer’s location in about a 150 foot radius.

EDITED TO ADD: Another way to get this information is to use the private tool: /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport
-s gives a list of surrounding networks; -I gives information about the current network.
Try /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport --help for more information.

You can even get the MAC address of all visible base-stations this way. Unfortunately, any system update could change the Apple80211.framework enough to break your code.

« Newer Posts

Powered by WordPress