Search code examples
objective-cmacosqtcore-foundationnsworkspace

NSWorkspace Notifications in CFNotificationCenter


We are working on a Qt project, and there is some Mac specific code that we need to add. We need to register for an event, in a sample program we did that by using:

[[NSNotificationCenter defaultCenter] addObserver:self
                                   selector:@selector(notificationHandler:) 
                                   name:NSWorkspaceDidDeactivateApplicationNotification
                                   object:nil];

Since we can use that directly in our mm file on Qt, we are taking the approach of doing something like:

MyClass::MyClass() : {
    // do other setup ...

    CFNotificationCenterAddObserver
    (
        CFNotificationCenterGetLocalCenter(),
        this,
        &notificationHandler,
        CFSTR("???"),
        NULL,
        CFNotificationSuspensionBehaviorDeliverImmediately
    );
}

whats the string for "NSWorkspaceDidDeactivateApplicationNotification"?? Or how do we attatch ourselves to this particular notification?

We tried NSGod's approach, but since no Objective-C code can be added in a .h with Qt, then we added a private member which its class is defined in the mm file, that containes the actual logic. like this:

SelectedStuffManager.h

class MacWrap;

class SelectedStuffManager
{
  public:
   ....
    doSomething();

    MacWrap* d;

  private:
   ....
};

SelectedStuffManager.mm

@class MDWorkspaceWatcher;

class MacWrap
{
    public:
        MacWrap();
        ~MacWrap();

        void  applicationDeactivated(NSNotification * notification);

        SystemEventsApplication *systemApplication;
        NSRunningApplication *runApp;

        private:
           MDWorkspaceWatcher *workspaceWatcher;
};
MacWrap::MacWrap() {
      this->workspaceWatcher = [[MDWorkspaceWatcher alloc] initWithMyClass:this];
}

MacWrap::~MacWrap() {
      [this->workspaceWatcher release];
}

void  MacWrap::applicationDeactivated(NSNotification* notification)
{
    // guardar el id del proceso para utilizarlo luego
    runApp = [[notification userInfo] valueForKey:@"NSWorkspaceApplicationKey"];
    NSString *systemEventsASppName = [runApp bundleIdentifier];
    if( [ systemEventsASppName isNotEqualTo:@"com.yo.SelectedText"])
    {
        systemApplication = [SBApplication applicationWithBundleIdentifier:systemEventsASppName];
        NSLog(@"Launched. %@",systemEventsASppName);
    }

}

@interface MDWorkspaceWatcher : NSObject {

     MacWrap  *manager;
}

- (id)initWithMyClass:(MacWrap*)obj;
- (void)didDeactivateApp:(NSNotification *)notification; @end

@implementation MDWorkspaceWatcher
- (id)initWithMyClass:(MacWrap*)obj {
    if ((self = [super init])) {
       manager = obj;

       [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
                selector:@selector(didDeactivateApp:)
                name:NSWorkspaceDidDeactivateApplicationNotification
                object:nil];
    }
    return self;
}

- (void)dealloc {
    [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
    [super dealloc];
}

- (void)didDeactivateApp:(NSNotification *)notification {
   manager->applicationDeactivated(notification);
}
@end

SelectedStuffManager::SelectedStuffManager()
{
    d = new MacWrap();
}
SelectedStuffManager::doSomething()
{
    if ([[d->runApp localizedName] isEqualTo: @"something"]) --> here it fails, bad memory access
    {
       ...
    }
}

It seems like someone is freeing both runApp and systemApplication, so we get a null pointer or bad memory. How or why could this be happening?


Solution

  • I don't believe you can just do like you're hoping to. First of all, NSWorkspace uses its own NSNotificationCenter, which is different than the default NSNotificationCenter returned with +defaultCenter.

    I don't believe there's a strict CF-equivalent to those NSWorkspace calls. There are likely high-level Carbon-based equivalents, but they're not available in 64-bit, and should likely be avoided.

    You should be able to accomplish what you want using a small Objective-C helper class to receive the notifications and forward them to your C++ class like in the following code:

    EDIT: updated to remove any Objective-C from header files. Just use generic void * pointers, which you can cast inside the .mm file.

    .h:

    //@class MDWorkspaceWatcher;
    
    class MyClass {
       private:
          // MDWorkspaceWatcher *workspaceWatcher;
          void *workspaceWatcher;
       public:
           MyClass();
           ~MyClass();
    
           // const void didActivateApp(NSNotification *notification) const;
           // const void didDeactivateApp(NSNotification *notification) const;
           const void didActivateApp(void *anNSnotification) const;
           const void didDeactivateApp(void *anNSnotification) const;
    
    };
    

    .mm:

    Objective-C part:

    @interface MDWorkspaceWatcher : NSObject {
        MyClass    *myClass;
    }
    - (id)initWithMyClass:(MyClass *)aMyClass;
    @end
    
    @implementation MDWorkspaceWatcher
    - (id)initWithMyClass:(MyClass *)aMyClass {
        if ((self = [super init])) {
           myClass = aMyClass;
           [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
                    selector:@selector(didActivateApp:)
                    name:NSWorkspaceDidActivateApplicationNotification
                    object:nil];
           [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self
                    selector:@selector(didDeactivateApp:)
                    name:NSWorkspaceDidDeactivateApplicationNotification
                    object:nil];
        }
        return self;
    }
    // very important:
    - (void)dealloc {
        [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
        [super dealloc];
    }
    - (void)didActivateApp:(NSNotification *)notification {
       myClass->didActivateApp(notification);
    }
    - (void)didDeactivateApp:(NSNotification *)notification {
       myClass->didDeactivateApp(notification);
    }
    @end
    

    C++ part:

    MyClass::MyClass() {
          this->workspaceWatcher = [[MDWorkspaceWatcher alloc] initWithMyClass:this];
    }
    
    MyClass::~MyClass() {
          [(MDWorkspaceWatcher *)this->workspaceWatcher release];
    }
    
    MyClass::didActivateApp(void *anNSnotification) {
         NSDictionary *appInfo = [(NSNotification *)anNSnotification userInfo];          
         NSLog(@"appInfo == %@", appInfo);
    
    }
    
    MyClass::didDeactivateApp(void *anNSnotification) {
         NSDictionary *appInfo = [(NSNotification *)anNSnotification userInfo];          
         NSLog(@"appInfo == %@", appInfo);
    
    }
    

    Note that an NSDictionary is toll-free-bridged with CFDictionaryRef, so you can simply cast the appInfo NSDictionary to a CFDictionaryRef and then call the CF functions to get at the contents of the dictionary if you prefer C over Objective-C.

    Note that the notification center owns the appInfo dictionary (in other words, it will be autoreleased), so you shouldn't call CFRelease() on it like you might with CFCreate*/CFCopy*-related code.