QTfrontend/NSWorkspace_RBAdditions.m
author nemo
Sun, 12 Jun 2011 14:45:26 -0400
changeset 5237 963d787a25c2
parent 3697 d5b30d6373fc
child 6479 4f08821cbff5
permissions -rw-r--r--
If 2 or more resolutions are available, use the 2nd in the list. This should (usually) be smaller than the desktop resolution, which should reduce noob fail (not realising part of interface is obscured)

//
//  NSWorkspace_RBAdditions.m
//  PathProps
//
//  Created by Rainer Brockerhoff on 10/04/2007.
//  Copyright 2007 Rainer Brockerhoff. All rights reserved.
//

#import "NSWorkspace_RBAdditions.h"
#include <IOKit/IOKitLib.h>
#include <sys/mount.h>

NSString* NSWorkspace_RBfstypename = @"NSWorkspace_RBfstypename";
NSString* NSWorkspace_RBmntonname = @"NSWorkspace_RBmntonname";
NSString* NSWorkspace_RBmntfromname = @"NSWorkspace_RBmntfromname";
NSString* NSWorkspace_RBdeviceinfo = @"NSWorkspace_RBdeviceinfo";
NSString* NSWorkspace_RBimagefilepath = @"NSWorkspace_RBimagefilepath";
NSString* NSWorkspace_RBconnectiontype = @"NSWorkspace_RBconnectiontype";
NSString* NSWorkspace_RBpartitionscheme = @"NSWorkspace_RBpartitionscheme";
NSString* NSWorkspace_RBserverURL = @"NSWorkspace_RBserverURL";

// This static funtion concatenates two strings, but first checks several possibilities...
// like one or the other nil, or one containing the other already.

static NSString* AddPart(NSString* first,NSString* second) {
	if (!second) {
		return first;
	}
	second = [second stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
	if (first) {
		if ([first rangeOfString:second options:NSCaseInsensitiveSearch].location==NSNotFound) {
			if ([second rangeOfString:first options:NSCaseInsensitiveSearch].location==NSNotFound) {
				return [NSString stringWithFormat:@"%@; %@",first,second];
			}
			return second;
		}
		return first;
	}
	return second;
}

// This static functions recurses "upwards" over the IO registry. Returns strings that are concatenated
// and ultimately end up under the NSWorkspace_RBdeviceinfo key.
// This isn't too robust in that it assumes that objects returned by the objectForKey methods are
// either strings or dictionaries. A "standard" implementations would use either only CoreFoundation and
// IOKit calls for this, or do more robust type checking on the returned objects.
//
// Also notice that this works as determined experimentally in 10.4.9, there's no official docs I could find.
// YMMV, and it may stop working in any new version of Mac OS X.

static NSString* CheckParents(io_object_t thing,NSString* part,NSMutableDictionary* dict) {
	NSString* result = part;
    io_iterator_t parentsIterator = 0;
    kern_return_t kernResult = IORegistryEntryGetParentIterator(thing,kIOServicePlane,&parentsIterator);
    if ((kernResult==KERN_SUCCESS)&&parentsIterator) {
		io_object_t nextParent = 0;
		while ((nextParent = IOIteratorNext(parentsIterator))) {
			NSDictionary* props = nil;
			NSString* image = nil;
			NSString* partition = nil;
			NSString* connection = nil;
			kernResult = IORegistryEntryCreateCFProperties(nextParent,(CFMutableDictionaryRef*)&props,kCFAllocatorDefault,0);
			if (IOObjectConformsTo(nextParent,"IOApplePartitionScheme")) {
				partition = [props objectForKey:@"Content Mask"];
			} else if (IOObjectConformsTo(nextParent,"IOMedia")) {
				partition = [props objectForKey:@"Content"];
			} else if (IOObjectConformsTo(nextParent,"IODiskImageBlockStorageDeviceOutKernel")) {
				NSData* data = nil;
				if (data = [[props objectForKey:@"Protocol Characteristics"] objectForKey:@"Virtual Interface Location Path"]) {
					image = [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:NSUTF8StringEncoding] autorelease];
				}
			} else if (IOObjectConformsTo(nextParent,"IOHDIXHDDriveInKernel")) {
				image = [props objectForKey:@"KDIURLPath"];
			}
			NSDictionary* subdict;
			if (subdict = [props objectForKey:@"Protocol Characteristics"]) {
				connection = [subdict objectForKey:@"Physical Interconnect"];
			} else {
				connection = [props objectForKey:@"Physical Interconnect"];
			}
			if (connection) {
				[dict setObject:AddPart([dict objectForKey:NSWorkspace_RBconnectiontype],connection) forKey:NSWorkspace_RBconnectiontype];
			}
			if (partition) {
				[dict setObject:partition forKey:NSWorkspace_RBpartitionscheme];
			}
			if (image) {
				[dict setObject:image forKey:NSWorkspace_RBimagefilepath];
			}
			NSString* value;
			if (subdict = [props objectForKey:@"Device Characteristics"]) {
				if (value = [subdict objectForKey:@"Product Name"]) {
					result = AddPart(result,value);
				}
				if (value = [subdict objectForKey:@"Product Revision Level"]) {
					result = AddPart(result,value);
				}
				if (value = [subdict objectForKey:@"Vendor Name"]) {
					result = AddPart(result,value);
				}
			}
			if (value = [props objectForKey:@"USB Serial Number"]) {
				result = AddPart(result,value);
			}
			if (value = [props objectForKey:@"USB Vendor Name"]) {
				result = AddPart(result,value);
			}
			NSString* cls = [(NSString*)IOObjectCopyClass(nextParent) autorelease];
			if (![cls isEqualToString:@"IOPCIDevice"]) {

// Uncomment the following line to have the device tree dumped to the console.
//				NSLog(@"=================================> %@:%@\n",cls,props);

				result = CheckParents(nextParent,result,dict);
			}
			IOObjectRelease(nextParent);
		}
    }
    if (parentsIterator) {
		IOObjectRelease(parentsIterator);
    }
	return result;
}

// This formats the (partially undocumented) AFPXMountInfo info into a string.

static NSString* FormatAFPURL(AFPXVolMountInfoPtr mountInfo,NSString** devdesc) {
	UInt8* work = ((UInt8*)mountInfo)+mountInfo->serverNameOffset;
	if (devdesc) {
		*devdesc = [[[NSString alloc] initWithBytes:&work[1] length:work[0] encoding:NSUTF8StringEncoding] autorelease];
	}
	work = ((UInt8*)mountInfo)+mountInfo->volNameOffset;
	NSString* volname = [[[NSString alloc] initWithBytes:&work[1] length:work[0] encoding:NSUTF8StringEncoding] autorelease];
	work = ((UInt8*)mountInfo)+mountInfo->alternateAddressOffset;
	AFPAlternateAddress* afpa = (AFPAlternateAddress*)work;
	AFPTagData* afpta = (AFPTagData*)(&afpa->fAddressList);
	NSString* ip = nil;
	NSString* dns = nil;
	int i = afpa->fAddressCount;
	while ((i-->0)) {
		switch (afpta->fType) {
			case kAFPTagTypeIP:
				if (!ip) {
					ip = [[[NSString alloc] initWithBytes:&afpta->fData[0] length:afpta->fLength-2 encoding:NSUTF8StringEncoding] autorelease];
				}
				break;
			case kAFPTagTypeIPPort:
				ip = [NSString stringWithFormat:@"%u.%u.%u.%u:%u",afpta->fData[0],afpta->fData[1],afpta->fData[2],afpta->fData[3],OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[4])];
				break;
			case kAFPTagTypeDNS:
				dns = [[[NSString alloc] initWithBytes:&afpta->fData[0] length:afpta->fLength-2 encoding:NSUTF8StringEncoding] autorelease];
				break;
			case 0x07:
				ip = [NSString stringWithFormat:@"[%x:%x:%x:%x:%x:%x:%x:%x]",OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[0]),
					OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[2]),OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[4]),
					OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[6]),OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[8]),
					OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[10]),OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[12]),
					OSSwapBigToHostInt16(*(UInt16*)&afpta->fData[14])];
				break;
		}
		afpta = (AFPTagData*)((char*)afpta+afpta->fLength);
	}
	return [NSString stringWithFormat:@"afp://%@/%@",dns?:(ip?:@""),volname];
}

@implementation NSWorkspace (NSWorkspace_RBAdditions)

// Returns a NSDictionary with properties for the path. See details in the .h file.
// This assumes that the length of path is less than PATH_MAX (currently 1024 characters).

- (NSDictionary*)propertiesForPath:(NSString*)path {
	const char* ccpath = (const char*)[path fileSystemRepresentation];
	NSMutableDictionary* result = nil;
	struct statfs fs;
	if (!statfs(ccpath,&fs)) {
		NSString* from = [NSString stringWithUTF8String:fs.f_mntfromname];
		result = [NSMutableDictionary dictionaryWithObjectsAndKeys:
			[NSString stringWithUTF8String:fs.f_fstypename],NSWorkspace_RBfstypename,
			[NSString stringWithUTF8String:fs.f_mntonname],NSWorkspace_RBmntonname,
			nil];
		if (strncmp(fs.f_mntfromname,"/dev/",5)==0) {
// For a local volume,get the IO registry tree and search it for further info.
			mach_port_t masterPort = 0;
			io_iterator_t mediaIterator = 0;
			kern_return_t kernResult = IOMasterPort(bootstrap_port,&masterPort);
			if (kernResult==KERN_SUCCESS) {
				CFMutableDictionaryRef classesToMatch = IOBSDNameMatching(masterPort,0,&fs.f_mntfromname[5]);
				if (classesToMatch) {
					kernResult = IOServiceGetMatchingServices(masterPort,classesToMatch,&mediaIterator);
					if ((kernResult==KERN_SUCCESS)&&mediaIterator) {
						io_object_t firstMedia = 0;
						while ((firstMedia = IOIteratorNext(mediaIterator))) {
							NSString* stuff = CheckParents(firstMedia,nil,result);
							if (stuff) {
								[result setObject:stuff forKey:NSWorkspace_RBdeviceinfo];
							}
							IOObjectRelease(firstMedia);
						}
					}
				}
			}
			if (mediaIterator) {
				IOObjectRelease(mediaIterator);
			}
			if (masterPort) {
				mach_port_deallocate(mach_task_self(),masterPort);
			}
		}
		//Don't need this for disk images, gets around warnings for some deprecated functions

		/* else {
// For a network volume, get the volume reference number and use to get the server URL.
			FSRef ref;
			if (FSPathMakeRef((const UInt8*)ccpath,&ref,NULL)==noErr) {
				FSCatalogInfo info;
				if (FSGetCatalogInfo(&ref,kFSCatInfoVolume,&info,NULL,NULL,NULL)==noErr) {
					ParamBlockRec pb;
					UInt16 vmisize = 0;
					VolumeMountInfoHeaderPtr mountInfo = NULL;
					pb.ioParam.ioCompletion = NULL;
					pb.ioParam.ioNamePtr = NULL;
					pb.ioParam.ioVRefNum = info.volume;
					pb.ioParam.ioBuffer = (Ptr)&vmisize;
					pb.ioParam.ioReqCount = sizeof(vmisize);
					if ((PBGetVolMountInfoSize(&pb)==noErr)&&vmisize) {
						mountInfo = (VolumeMountInfoHeaderPtr)malloc(vmisize);
						if (mountInfo) {
							pb.ioParam.ioBuffer = (Ptr)mountInfo;
							pb.ioParam.ioReqCount = vmisize;
							if (PBGetVolMountInfo(&pb)==noErr) {
								NSString* url = nil;
								switch (mountInfo->media) {
								case AppleShareMediaType:
									url = FormatAFPURL((AFPXVolMountInfoPtr)mountInfo,&from);
									break;
								case 'http':
									url = from;
									break;
								case 'crbm':
								case 'nfs_':
								case 'cifs':
									url = [NSString stringWithUTF8String:(char*)mountInfo+sizeof(VolumeMountInfoHeader)+sizeof(OSType)];
									break;
								}
								if (url) {
									[result setObject:url forKey:NSWorkspace_RBserverURL];
								}
							}
						}
						free(mountInfo);
					}
				}
			}
		}*/
		[result setObject:from forKey:NSWorkspace_RBmntfromname];
	}
	return result;
}

@end