CGDisplayIOServicePort is deprecated in OS X >= 10.9, how to replace? - objective-c

I did small app to allow quickly change screen resolutions on multiple monitors. I want to show product name as title of the monitor, and it's very simple to find using this code:
NSDictionary *deviceInfo = (__bridge NSDictionary *)IODisplayCreateInfoDictionary(CGDisplayIOServicePort(dispID), kIODisplayOnlyPreferredName);
NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
if([localizedNames count] > 0) {
_title = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]];
} else {
_title = #"Unknown display";
}
But CGDisplayIOServicePort is deprecated in OS X >= 10.9 and Apple's documentation says there is no replacement. How to find service port or product name without using this method?
I tried to iterate through IO-registry and tried to use IOServiceGetMatchingServices method to find display services but I'm not very familiar with IO-registry so I couldn't find solution.
Thanks for help!

It looks like #Eun's post missed a piece of information to close this discussion. With a little search, I found that IOServicePortFromCGDisplayID is not an API which Apple provides. Rather, it's a piece of open source code found here:
https://github.com/glfw/glfw/blob/e0a6772e5e4c672179fc69a90bcda3369792ed1f/src/cocoa_monitor.m
I copied IOServicePortFromCGDisplayID and also 'getDisplayName' from it.
I needed two tweaks to make it work on OS X 10.10.
Remove the code to handle serial number in IOServicePortFromCGDisplayID. (CFDictionaryGetValue for
kDisplaySerialNumber returns NULL for me.)
Remove project specific
error handling code in getDisplayName.
If you need more information
Issue tracker of the problem: github.com/glfw/glfw/issues/165
Commit
for the solution:
github.com/glfw/glfw/commit/e0a6772e5e4c672179fc69a90bcda3369792ed1f
I would thank Matthew Henry who submitted the code there.

Here is my take on the issue. I also started with the code from GLFW 3.1, file cocoa_monitor.m.
But I had to modify it in different ways than Hiroshi said, so here goes:
// Get the name of the specified display
- (NSString*) screenNameForDisplay: (NSNumber*) screen_id
{
CGDirectDisplayID displayID = [screen_id unsignedIntValue];
io_service_t serv = [self IOServicePortFromCGDisplayID: displayID];
if (serv == 0)
return #"unknown";
CFDictionaryRef info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName);
IOObjectRelease(serv);
CFStringRef display_name;
CFDictionaryRef names = CFDictionaryGetValue(info, CFSTR(kDisplayProductName));
if ( !names ||
!CFDictionaryGetValueIfPresent(names, CFSTR("en_US"), (const void**) & display_name) )
{
// This may happen if a desktop Mac is running headless
CFRelease( info );
return #"unknown";
}
NSString * displayname = [NSString stringWithString: (__bridge NSString *) display_name];
CFRelease(info);
return displayname;
}
// Returns the io_service_t (an int) corresponding to a CG display ID, or 0 on failure.
// The io_service_t should be released with IOObjectRelease when not needed.
- (io_service_t) IOServicePortFromCGDisplayID: (CGDirectDisplayID) displayID
{
io_iterator_t iter;
io_service_t serv, servicePort = 0;
CFMutableDictionaryRef matching = IOServiceMatching("IODisplayConnect");
// releases matching for us
kern_return_t err = IOServiceGetMatchingServices( kIOMasterPortDefault, matching, & iter );
if ( err )
return 0;
while ( (serv = IOIteratorNext(iter)) != 0 )
{
CFDictionaryRef displayInfo;
CFNumberRef vendorIDRef;
CFNumberRef productIDRef;
CFNumberRef serialNumberRef;
displayInfo = IODisplayCreateInfoDictionary( serv, kIODisplayOnlyPreferredName );
Boolean success;
success = CFDictionaryGetValueIfPresent( displayInfo, CFSTR(kDisplayVendorID), (const void**) & vendorIDRef );
success &= CFDictionaryGetValueIfPresent( displayInfo, CFSTR(kDisplayProductID), (const void**) & productIDRef );
if ( !success )
{
CFRelease(displayInfo);
continue;
}
SInt32 vendorID;
CFNumberGetValue( vendorIDRef, kCFNumberSInt32Type, &vendorID );
SInt32 productID;
CFNumberGetValue( productIDRef, kCFNumberSInt32Type, &productID );
// If a serial number is found, use it.
// Otherwise serial number will be nil (= 0) which will match with the output of 'CGDisplaySerialNumber'
SInt32 serialNumber = 0;
if ( CFDictionaryGetValueIfPresent(displayInfo, CFSTR(kDisplaySerialNumber), (const void**) & serialNumberRef) )
{
CFNumberGetValue( serialNumberRef, kCFNumberSInt32Type, &serialNumber );
}
// If the vendor and product id along with the serial don't match
// then we are not looking at the correct monitor.
// NOTE: The serial number is important in cases where two monitors
// are the exact same.
if( CGDisplayVendorNumber(displayID) != vendorID ||
CGDisplayModelNumber(displayID) != productID ||
CGDisplaySerialNumber(displayID) != serialNumber )
{
CFRelease(displayInfo);
continue;
}
servicePort = serv;
CFRelease(displayInfo);
break;
}
IOObjectRelease(iter);
return servicePort;
}
This works fine for me in a screensaver I wrote under macOS 10.11 (El Capitan).
I tested it with the built-in display of my MacBookPro and an Apple Display connected via Thunderbolt.

As of macOS 10.15 -[NSScreen localizedName] is available:
NSLog(#"Name of main display is %#", NSScreen.mainScreen.localizedName);

NSString* screenNameForDisplay(CGDirectDisplayID displayID)
{
NSString *screenName = nil;
io_service_t service = IOServicePortFromCGDisplayID(displayID);
if (service)
{
NSDictionary *deviceInfo = (NSDictionary *)IODisplayCreateInfoDictionary(service, kIODisplayOnlyPreferredName);
NSDictionary *localizedNames = [deviceInfo objectForKey:[NSString stringWithUTF8String:kDisplayProductName]];
if ([localizedNames count] > 0) {
screenName = [[localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]] retain];
}
[deviceInfo release];
}
return [screenName autorelease];
}

Related

Memoryleak when using CFRelease

I have the method which causes a memory leak as shown by the instrument:
-(BOOL)checkIfGroupExistWithName:(NSString*)groupName
{
BOOL hasGroup = NO;
//checks to see if the group is created ad creats group for Handheld contacts
CFErrorRef error = NULL;
ABAddressBookRef ab = ABAddressBookCreateWithOptions(NULL, &error);
CFIndex groupCount = ABAddressBookGetGroupCount(ab);
CFArrayRef allGroups = ABAddressBookCopyArrayOfAllGroups(ab);
for (int i=0; i<groupCount; i++) {
ABRecordRef group = CFArrayGetValueAtIndex(allGroups, i);
CFStringRef CFcurrentGroupName = ABRecordCopyCompositeName(group);
NSString *currentGroupName = (__bridge_transfer NSString *)CFcurrentGroupName;
if ([currentGroupName isEqualToString:groupName]) {
//!!! important - save groupID for later use
groupId = ABRecordGetRecordID(group);
hasGroup = YES;
i = (int) groupCount;
}
CFRelease(CFcurrentGroupName);
CFRelease(group);
}
return hasGroup;
}
If I use CFRelease(ab); before return hasGroup, it crashes.
I couldn't understand what is happening here.
The static analyzer (shift+command+B or "Analyze" on Xcode's "Product" menu) is remarkably good at identifying these issues for you:
Bottom line, the Create Rule dictates that you have to CFRelease any objects returned from Core Foundation functions with either Copy or Create in their name (except those that you transfer ownership with either CFBridgingRelease or __bridge_transfer, as that allows ARC to clean those up for you).
Needless to say, you shouldn't CFRelease anything else (such as group, which was returned by an function without Copy or Create in the name, nor CFcurrentGroupName, which you already transferred ownership with __bridge_transfer).
Anyway, you end up with something like:
- (BOOL)checkIfGroupExistWithName:(NSString*)groupName {
BOOL hasGroup = NO;
//checks to see if the group is created ad creats group for Handheld contacts
CFErrorRef error = NULL;
ABAddressBookRef ab = ABAddressBookCreateWithOptions(NULL, &error);
if (!ab) {
NSLog(#"%#", CFBridgingRelease(error));
return false;
}
CFIndex groupCount = ABAddressBookGetGroupCount(ab);
CFArrayRef allGroups = ABAddressBookCopyArrayOfAllGroups(ab);
for (int i = 0; i < groupCount; i++) {
ABRecordRef group = CFArrayGetValueAtIndex(allGroups, i);
NSString *currentGroupName = CFBridgingRelease(ABRecordCopyCompositeName(group));
if ([currentGroupName isEqualToString:groupName]) {
//!!! important - save groupID for later use
groupId = ABRecordGetRecordID(group);
hasGroup = true;
break;
}
}
if (allGroups) CFRelease(allGroups);
CFRelease(ab);
return hasGroup;
}
I suspect that your attempt to CFRelease(ab) was crashing for other reasons (e.g., ab was NULL because ABAddressBookCreateWithOptions failed, perhaps you neglected to add NSContactsUsageDescription to your plist; ab was released before subsequent references to ab; you were releasing things you shouldn't have; you received NULL from some Core Foundation function and tried to CFRelease that; etc.). But the above should work fine as it fixes the aforementioned issues.
Clearly, if using iOS 9 and later only, you can use the Contacts.framework and avoid all of these bridging annoyances altogether.

Find out if Mac is Force Touch - capable

Is it possible to find out if a Mac is Force Touch capable - either via a built-in Trackpad, like the new MacBook, or a Bluetooth device like the Magic Trackpad 2?
I'd like to present preferences specific to Force Touch if the Mac is Force Touch capable, but not display (or disable) those preferences if Force Touch is not available.
In the portion after the separator, you see the options I have in mind in the pic linked here. (sorry, embedding the pic itself didn't work).
So, not showing the preferences wouldn't restrict users who don't have force touch, it would just let users who have it configure how it should work, and those settings would be useless to users who don't have it.
Is there a way to achieve this?
Thank you and kind regards,
Matt
Edit: It's in Objective-C.
I figured it out:
+ (BOOL)isForceTouchCapable
{
if (![[self class] isAtLeastElCapitan])
return NO;
io_iterator_t iterator;
//get default HIDDevice dictionary
CFMutableDictionaryRef mDict = IOServiceMatching(kIOHIDDeviceKey);
//add manufacturer "Apple Inc." to dict
CFDictionaryAddValue(mDict, CFSTR(kIOHIDManufacturerKey), CFSTR("Apple Inc."));
//get matching services, depending on dict
IOReturn ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, mDict, &iterator);
BOOL result = YES;
if (ioReturnValue != kIOReturnSuccess)
NSLog(#"error getting matching services for force touch devices");
else
{
//recursively go through each device found and its children and grandchildren, etc.
result = [[self class] _containsForceTouchDevice:iterator];
IOObjectRelease(iterator);
}
return result;
}
+ (BOOL)_containsForceTouchDevice:(io_iterator_t)iterator
{
io_object_t object = 0;
BOOL success = NO;
while ((object = IOIteratorNext(iterator)))
{
CFMutableDictionaryRef result = NULL;
kern_return_t state = IORegistryEntryCreateCFProperties(object, &result, kCFAllocatorDefault, 0);
if (state == KERN_SUCCESS && result != NULL)
{
if (CFDictionaryContainsKey(result, CFSTR("DefaultMultitouchProperties")))
{
CFDictionaryRef dict = CFDictionaryGetValue(result, CFSTR("DefaultMultitouchProperties"));
CFTypeRef val = NULL;
if (CFDictionaryGetValueIfPresent(dict, CFSTR("ForceSupported"), &val))
{
Boolean aBool = CFBooleanGetValue(val);
if (aBool) //supported
success = YES;
}
}
}
if (result != NULL)
CFRelease(result);
if (success)
{
IOObjectRelease(object);
break;
} else
{
io_iterator_t childIterator = 0;
kern_return_t err = IORegistryEntryGetChildIterator(object, kIOServicePlane, &childIterator);
if (!err)
{
success = [[self class] _containsForceTouchDevice:childIterator];
IOObjectRelease(childIterator);
} else
success = NO;
IOObjectRelease(object);
}
}
return success;
}
Just call + (BOOL)isForceTouchCapable and it will return YES if a Force Touch device is available (a Magic Trackpad 2 or a built in force-touch-trackpad) or NO if there isn't.
For those interested in how this came to be, I wrote about it on my blog with an example project.

unable to detect application running with another user (via switch user)

Use case: I login as "user1" and launch Safari, then I click "user1" on top-right corner and switch user to "user2".
Now, I am trying to detect whether Safari is running for "user1" but I am unable to do so with standard calls. I am using OS X 10.8 Mountain Lion for both development and running my code.
I have used the following but in vain:
[[NSWorkspace sharedWorkspace] runningApplications] - Safari not there in the list
GetNextProcess() - Safari does not come up
GetProcessForPID() - I get an error "no such process"
But when I do a ps -aef | grep Safari from the terminal, I can see Safari. (This is not only the case with Safari but other applications as well.)
Can someone please help. Thank you very much.
You can use sysctl or ps command to get a list of all BSD processes.Have a look at Technical Q&A QA1123
#include <sys/sysctl.h>
#include <pwd.h>
typedef struct kinfo_proc kinfo_proc;
static int GetBSDProcessList(kinfo_proc **procList, size_t *procCount)
// Returns a list of all BSD processes on the system. This routine
// allocates the list and puts it in *procList and a count of the
// number of entries in *procCount. You are responsible for freeing
// this list (use "free" from System framework).
// On success, the function returns 0.
// On error, the function returns a BSD errno value.
{
int err;
kinfo_proc * result;
bool done;
static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
// Declaring name as const requires us to cast it when passing it to
// sysctl because the prototype doesn't include the const modifier.
size_t length;
// assert( procList != NULL);
// assert(*procList == NULL);
// assert(procCount != NULL);
*procCount = 0;
// We start by calling sysctl with result == NULL and length == 0.
// That will succeed, and set length to the appropriate length.
// We then allocate a buffer of that size and call sysctl again
// with that buffer. If that succeeds, we're done. If that fails
// with ENOMEM, we have to throw away our buffer and loop. Note
// that the loop causes use to call sysctl with NULL again; this
// is necessary because the ENOMEM failure case sets length to
// the amount of data returned, not the amount of data that
// could have been returned.
result = NULL;
done = false;
do {
assert(result == NULL);
// Call sysctl with a NULL buffer.
length = 0;
err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1,
NULL, &length,
NULL, 0);
if (err == -1) {
err = errno;
}
// Allocate an appropriately sized buffer based on the results
// from the previous call.
if (err == 0) {
result = malloc(length);
if (result == NULL) {
err = ENOMEM;
}
}
// Call sysctl again with the new buffer. If we get an ENOMEM
// error, toss away our buffer and start again.
if (err == 0) {
err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1,
result, &length,
NULL, 0);
if (err == -1) {
err = errno;
}
if (err == 0) {
done = true;
} else if (err == ENOMEM) {
assert(result != NULL);
free(result);
result = NULL;
err = 0;
}
}
} while (err == 0 && ! done);
// Clean up and establish post conditions.
if (err != 0 && result != NULL) {
free(result);
result = NULL;
}
*procList = result;
if (err == 0) {
*procCount = length / sizeof(kinfo_proc);
}
assert( (err == 0) == (*procList != NULL) );
return err;
}
+ (NSArray*)getBSDProcessList
{
kinfo_proc *mylist =NULL;
size_t mycount = 0;
GetBSDProcessList(&mylist, &mycount);
NSMutableArray *processes = [NSMutableArray arrayWithCapacity:(int)mycount];
for (int i = 0; i < mycount; i++) {
struct kinfo_proc *currentProcess = &mylist[i];
struct passwd *user = getpwuid(currentProcess->kp_eproc.e_ucred.cr_uid);
NSMutableDictionary *entry = [NSMutableDictionary dictionaryWithCapacity:4];
NSNumber *processID = [NSNumber numberWithInt:currentProcess->kp_proc.p_pid];
NSString *processName = [NSString stringWithFormat: #"%s",currentProcess->kp_proc.p_comm];
if (processID)[entry setObject:processID forKey:#"processID"];
if (processName)[entry setObject:processName forKey:#"processName"];
if (user){
NSNumber *userID = [NSNumber numberWithUnsignedInt:currentProcess->kp_eproc.e_ucred.cr_uid];
NSString *userName = [NSString stringWithFormat: #"%s",user->pw_name];
if (userID)[entry setObject:userID forKey:#"userID"];
if (userName)[entry setObject:userName forKey:#"userName"];
}
[processes addObject:[NSDictionary dictionaryWithDictionary:entry]];
}
free(mylist);
return [NSArray arrayWithArray:processes];
}

Get a list of unmountable drives using Cocoa

I would like to obtain a list of drives that are unmountable/ejectable using Cocoa/Objective-C under OS X.
I was hoping that NSWorkspace getFileSystemInfoForPath::::: would help me:
NSArray* listOfMedia = [[NSWorkspace sharedWorkspace] mountedLocalVolumePaths];
NSLog(#"%#", listOfMedia);
for (NSString* volumePath in listOfMedia)
{
BOOL isRemovable = NO;
BOOL isWritable = NO;
BOOL isUnmountable = NO;
NSString* description = [NSString string];
NSString* type = [NSString string];
BOOL result = [[NSWorkspace sharedWorkspace] getFileSystemInfoForPath:volumePath
isRemovable:&isRemovable
isWritable:&isWritable
isUnmountable:&isUnmountable
description:&description
type:&type];
NSLog(#"Result:%i Volume: %#, Removable:%i, W:%i, Unmountable:%i, Desc:%#, type:%#", result, volumePath, isRemovable, isWritable, isUnmountable, description, type);
}
Output:
...
Result:1 Volume: /Volumes/LR Photos, Removable:0, W:1, Unmountable:0, Desc:hfs, type:hfs
...
"LR Photos" is an external drive (connected via Thunderbolt) that should be removable and/or unmountable (or, at least I think it should be). :)
Should I be going about this a different way?
Thanks in advance!
You can use diskArbitration framework.
#import <DiskArbitration/DiskArbitration.h>
+(NSMutableArray *)getListOfEjectableMedia
{
NSArray *mountedRemovableMedia = [[NSFileManager defaultManager] mountedVolumeURLsIncludingResourceValuesForKeys:nil options:NSVolumeEnumerationSkipHiddenVolumes];
NSMutableArray *result = [NSMutableArray array];
for(NSURL *volURL in mountedRemovableMedia)
{
int err = 0;
DADiskRef disk;
DASessionRef session;
CFDictionaryRef descDict;
session = DASessionCreate(NULL);
if (session == NULL) {
err = EINVAL;
}
if (err == 0) {
disk = DADiskCreateFromVolumePath(NULL,session,(CFURLRef)volURL);
if (session == NULL) {
err = EINVAL;
}
}
if (err == 0) {
descDict = DADiskCopyDescription(disk);
if (descDict == NULL) {
err = EINVAL;
}
}
if (err == 0) {
CFTypeRef mediaEjectableKey = CFDictionaryGetValue(descDict,kDADiskDescriptionMediaEjectableKey);
CFTypeRef deviceProtocolName = CFDictionaryGetValue(descDict,kDADiskDescriptionDeviceProtocolKey);
if (mediaEjectableKey != NULL)
{
BOOL op = CFEqual(mediaEjectableKey, CFSTR("0")) || CFEqual(deviceProtocolName, CFSTR("USB"));
if (op) {
[result addObject:volURL];
}
}
}
if (descDict != NULL) {
CFRelease(descDict);
}
if (disk != NULL) {
CFRelease(disk);
}
if (session != NULL) {
CFRelease(session);
}
}
return result;
}
Unfortunately getFileSystemInfoForPath: is not really the right way to do this. What removable means is that the volume is on removable media such as a CD or DVD. In practice unmountable seems to give the same results as removable. See for example, this post on results using getFileSystemInfoForPath. So unless you want to simply know if a volume is on removable media, you'll need to use another technique.
What you really want to check is the connection bus type of the volume. Firewire, USB, Thunderbolt, etc. are unmountable in the sense you mean. You can see this information in Disk Utility if you select the volume and push the "Info" button under "Connection Bus". Getting this information programmatically is much harder and as far as I can tell is only possible using the IOKit. Details are in Apple's documentation on Accessing Hardware from Applications.
you can use command line version of Disk Utility app that is "diskutil", run it with parameter "list" and pipe output and get it in your program ( don't need to use cocoa ).

Close all windows of another app using Accessibility API

I already know how to use the Mac OSX Accessibility API within Objective-C to reposition windows of another running application, without the use of any kind of scripting bridge.
Now, I want to use this same Accessibility API (again, without any scripting bridge) to close all the open windows of another running application.
The code that I want to write in Objective-C should do the same thing as this AppleScript code:
tell application "TheApplication"
close every window
end tell
I would guess that this is possible, because it's permitted within AppleScript.
Here's my solution ...
+(void)closeWindowsOfApp:(NSString*)appName {
boolean_t result = false;
if (appName == nil) {
return;
}
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = kNoProcess;
while (GetNextProcess(&psn) == noErr) {
pid_t pid = 0;
if (GetProcessPID(&psn, &pid) != noErr) {
continue;
}
AXUIElementRef elementRef = AXUIElementCreateApplication(pid);
NSString* title = nil;
AXUIElementCopyAttributeValue(elementRef, kAXTitleAttribute, (CFTypeRef *)&title);
if (title == nil) {
continue;
}
if ([title compare:appName] != NSOrderedSame) {
CFRelease(title);
continue;
}
CFRelease(title);
CFArrayRef windowArray = nil;
AXUIElementCopyAttributeValue(elementRef, kAXWindowsAttribute, (CFTypeRef*)&windowArray);
if (windowArray == nil) {
CFRelease(elementRef);
continue;
}
CFRelease(elementRef);
CFIndex nItems = CFArrayGetCount(windowArray);
if (nItems < 1) {
CFRelease(windowArray);
continue;
}
for (int i = 0; i < nItems; i++) {
AXUIElementRef itemRef = (AXUIElementRef) CFArrayGetValueAtIndex(windowArray, i);
AXUIElementRef buttonRef = nil;
AXUIElementCopyAttributeValue(itemRef, kAXCloseButtonAttribute, (CFTypeRef*)&buttonRef);
AXUIElementPerformAction(buttonRef, kAXPressAction);
CFRelease(buttonRef);
}
CFRelease(windowArray);
break;
}
}
There's a Cocoa class, NSApplescript, that lets you compile and run Applescript from within your ObjC code. You haven't really said why you don't want to use AS. Since you've already got the script that does what you want, you can make your program work right now and just use it:
NSApplescript * as = [[NSApplescript alloc] initWithSource:#"tell application \"TheApplication\"\nclose every window\nend tell"];
NSDictionary * errInfo;
NSAppleEventDescriptor * res = [as executeAndReturnError:&err];
if( !res ){
// An error occurred. Inspect errInfo and perform necessary actions
}
[as release];
Worry about ideological purity or performance later.