Getting Carbon Volume Mount Events in a Cocoa (Core Foundation) process

  • Greetings,

    I'm trying to get the Carbon volume mount and unmount events in
    a Core Foundation process (daemon). Here's the code I use to
    install the handler:

    - (void)startNotifications
    {
        if (volumeEventUPP==NULL)
            {
            volumeEventUPP = NewEventHandlerUPP(sVolumeEventCallback);
            EventTypeSpec eventTypes[2];
            eventTypes[0].eventClass = kEventClassVolume;
            eventTypes[0].eventKind = kEventVolumeMounted;
            eventTypes[1].eventClass = kEventClassVolume;
            eventTypes[1].eventKind = kEventVolumeUnmounted;
            OSStatus osStatus = InstallApplicationEventHandler(volumeEventUPP,2,eventTypes,self,&volumeEventHandlerRef);
            if (osStatus==noErr)
                [Logger log success...  <-- I get this message
            else
                [Logger log failure...
            }
    }

    ...

    static OSStatus sVolumeEventCallback( EventHandlerCallRef
    handlerCallRef, EventRef event, void *userData )
    {
    #pragma unused(handlerCallRef)
    #pragma unused(userData)
        NSLog(@"%s",__func__);
        FSVolumeRefNum refNum = 0;
        OSStatus osStatus = GetEventParameter (event,kEventParamDirectObject,typeFSVolumeRefNum,NULL,sizeof(refNum),NULL,&refNum);
        if (osStatus==noErr)
            {
            NSString* diskID = nil;
            osStatus = FSCopyDiskIDForVolume(refNum,(CFStringRef*)&diskID);
            NSLog(@"FSCopyDiskIDForVolume() returned %d; %@",osStatus,diskID);
            [diskID autorelease];
            ...
            }
        else
            {
            [Logger log failure ...
            }

        return (noErr);
    }

    What I get is nothing. The success log message in
    startNotifications is recorded, then nothing beyond that. As far
    as I can tell, sVolumeEventCallback is never called.

    I've gone over the Carbon-Cocoa Integration guide and it doesn't
    mention that you have to do anything special to get Carbon
    events in a Cocoa application. It indicates that this should
    "just work," but clearly something is missing.

    If it makes any difference, the main NSRunLoop for this process
    is run using [NSRunLoop runMode:beforeDate:].

    P.S. I originally coded this using the disk arbitration
    framework. But it turns out that DA event don't include volume
    mount and unmount events, which is what I'm really interested in.

    --
    James Bucanek
  • On Oct 8, 2007, at 10:45 PM, James Bucanek wrote:

    > - (void)startNotifications
    > {
    > if (volumeEventUPP==NULL)
    > {
    > volumeEventUPP = NewEventHandlerUPP(sVolumeEventCallback);
    > EventTypeSpec eventTypes[2];
    > eventTypes[0].eventClass = kEventClassVolume;
    > eventTypes[0].eventKind = kEventVolumeMounted;
    > eventTypes[1].eventClass = kEventClassVolume;
    > eventTypes[1].eventKind = kEventVolumeUnmounted;
    > OSStatus osStatus =
    > InstallApplicationEventHandler(volumeEventUPP,
    > 2,eventTypes,self,&volumeEventHandlerRef);

    For future reference, I'd code this something like:

    EventTypeSpec eventTypes[] =
    {
    { kEventClassVolume, kEventVolumeMounted },
    { kEventClassVolume, kEventVolumeUnmounted }
    };
    InstallApplicationEventHandler
    (sVolumeEventCallback
    ,GetEventTypeCount(eventTypes),eventTypes,self,&volumeEventHandlerRef);

    It's easier and less error-prone to use initialization syntax to set
    up the EventTypeSpec array, and by using GetEventTypeCount to get the
    count of elements in the array, you avoid the risk of modifying the
    array and forgetting to modify the count. Also, you can skip the
    NewFooUPP calls in mach-o; they just return the pointer you passed in.

    > I've gone over the Carbon-Cocoa Integration guide and it doesn't
    > mention that you have to do anything special to get Carbon events in
    > a Cocoa application. It indicates that this should "just work," but
    > clearly something is missing.

    The only thing that really should be required is that you spin the
    runloop, and it sounds like you're doing that.

    > If it makes any difference, the main NSRunLoop for this process is
    > run using [NSRunLoop runMode:beforeDate:].

    What mode are you passing? Anything custom?

    -eric
  • Eric Schlegel <mailto:<ericsc...> wrote (Monday, October
    8, 2007 11:06 PM -0700):
    > For future reference, I'd code this something like:
    >
    > EventTypeSpec eventTypes[] =
    > {
    > { kEventClassVolume, kEventVolumeMounted },
    > { kEventClassVolume, kEventVolumeUnmounted }
    > };
    > InstallApplicationEventHandler(sVolumeEventCallback,GetEventTypeCount(eventTypes),eventTypes,self,&volumeEventHandlerRef);

    Thanks, that's much cleaner. I clearly don't do enough Carbon
    programming. ;)

    >> I've gone over the Carbon-Cocoa Integration guide and it
    >> doesn't mention that you have to do anything special to get
    >> Carbon events in a Cocoa application. It indicates that this
    >> should "just work," but clearly something is missing.
    >
    > The only thing that really should be required is that you spin the runloop, and it sounds like you're doing that.
    >
    >> If it makes any difference, the main NSRunLoop for this
    >> process is run using [NSRunLoop runMode:beforeDate:].
    >
    > What mode are you passing?

    NSDefaultRunLoopMode

    > Anything custom?

    A tiny bit custom. I add a special port and a couple of timers
    to the run loop before I start it running. The port is so I can
    terminate the run loop. My -terminate method clears the
    isRunning flag and then sends a message to
    receiveStopMessagePort, this lets [NSRunLoop
    runMode:beforeDate:] method return and the program can exit.

    Beyond that, I don't think I'm doing anything unusual. Here's my
    entire run method.

    - (void)run
    {
        NSRunLoop*  runLoop = [NSRunLoop currentRunLoop];
        NSDate*    endOfTime = [NSDate distantFuture];

        receiveStopMessagePort = [[NSPort port] retain];
        [receiveStopMessagePort setDelegate:self];
        [runLoop addPort:receiveStopMessagePort forMode:NSDefaultRunLoopMode];

        // Add a timer to the run loop to fire the startScheduler message
        [runLoop addTimer:[[[NSTimer alloc]
    initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:0.1]
                                                    interval:0.0
                                                      target:self
                                                    selector:@selector(startScheduler)
                                                    userInfo:nil
                                                      repeats:NO] autorelease]
                  forMode:NSDefaultRunLoopMode];

        // Create a timer that normally never fires. Used to
    terminate the command if a SIGTERM signal is received
        sTermSignalTimer = [[NSTimer alloc] initWithFireDate:endOfTime
                                                    interval:0.0
                                                      target:self
                                                    selector:@selector(termSignalReceived:)
                                                    userInfo:nil
                                                      repeats:NO];
        [runLoop addTimer:sTermSignalTimer forMode:NSDefaultRunLoopMode];
        // Register a signal handler that will fire the timer
        signal(SIGTERM,signal_handler);            // catch SIGTERM
        signal(SIGHUP,SIG_IGN);                    // ignore SIGHUP

        isRunning = YES;
        while (isRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:endOfTime])
            ;

        [sTermSignalTimer release];                    // SIGTERM
    timer is now invalid
        sTermSignalTimer = nil;
        receiveStopMessagePort = nil;
    }

    --
    James Bucanek
  • NSWorkspace posts notifications when volumes are mounted and unmounted
    and includes the device path.  If you need more info, you could
    combine that with the information provided by the disk arbitration
    framework.

    ----
    Aaron Burghardt
    <aburgh...>

    On Oct 9, 2007, at 1:45 AM, James Bucanek wrote:

    > P.S. I originally coded this using the disk arbitration framework.
    > But it turns out that DA event don't include volume mount and
    > unmount events, which is what I'm really interested in.
  • <ajb.lists...> <mailto:<ajb.lists...> wrote (Tuesday,
    October 9, 2007 12:29 PM -0400):

    > NSWorkspace posts notifications when volumes are mounted and unmounted
    > and includes the device path.  If you need more info, you could
    > combine that with the information provided by the disk arbitration
    > framework.

    As I mentioned in my original posting:

    James Bucanek <mailto:<subscriber...> wrote (Monday,
    October 8, 2007 10:45 PM -0700):
    > I'm trying to get the Carbon volume mount and unmount events in a Foundation process (daemon).

    NSWorkspace isn't available in a Foundation application. I wish
    it was. It would solve a *lot* of problems.

    --
    James Bucanek
  • If you don't mind linking against the AppKit, you can use NSWorkspace
    manager without an instance of NSApplication:

    #import <Cocoa/Cocoa.h>

    @interface Listener : NSObject
    - (void)volumeDidMount:(NSNotification *)notif;
    - (void)volumeDidUnmount:(NSNotification *)notif;
    @end

    @implementation Listener
    - (void)volumeDidMount:(NSNotification *)notif
    {
    NSLog(@"Volume mounted: %@", [[notif userInfo]
    valueForKey:@"NSDevicePath"]);
    }
    - (void)volumeDidUnmount:(NSNotification *)notif
    {
    NSLog(@"Volume unmounted: %@", [[notif userInfo]
    valueForKey:@"NSDevicePath"]);
    }
    @end

    int main( int argc, char *argv[]) {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    Listener *listener = [[Listener alloc] init];

    NSWorkspace *sharedWorkspace = [NSWorkspace sharedWorkspace];

    [[sharedWorkspace notificationCenter] addObserver:listener
    selector:@selector(volumeDidMount:)
    name:NSWorkspaceDidMountNotification object:nil];
    [[sharedWorkspace notificationCenter] addObserver:listener
    selector:@selector(volumeDidUnmount:)
    name:NSWorkspaceDidUnmountNotification object:nil];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60.0]];

    [listener release];
    [pool release];
    exit(0);
    }

    compile and run:

    $ gcc -framework Cocoa testworkspace.m -o testworkspace
    $ ./testworkspace

    While this is was running, I used diskutil to mount and unmount a
    thumbdrive (without physically removing it) in another Terminal window:

    $ diskutil unmount disk1s1
    Volume Lexar on disk1s1 unmounted
    $ diskutil mount disk1s1
    Volume Lexar on disk1s1 mounted

    and received this output from testworkspace:

    2007-10-09 20:34:35.070 testworkspace[4960:10b] Volume unmounted: /
    Volumes/Lexar
    2007-10-09 20:34:43.246 testworkspace[4960:10b] Volume mounted: /
    Volumes/Lexar

    HTH,
    ----
    Aaron Burghardt
    <aburgh...>

    On Oct 9, 2007, at 5:40 PM, James Bucanek wrote:

    > <ajb.lists...> <mailto:<ajb.lists...> wrote (Tuesday, October
    > 9, 2007 12:29 PM -0400):
    >
    >> NSWorkspace posts notifications when volumes are mounted and
    >> unmounted
    >> and includes the device path.  If you need more info, you could
    >> combine that with the information provided by the disk arbitration
    >> framework.
    >
    > As I mentioned in my original posting:
    >
    > James Bucanek <mailto:<subscriber...> wrote (Monday,
    > October 8, 2007 10:45 PM -0700):
    >> I'm trying to get the Carbon volume mount and unmount events in a
    >> Foundation process (daemon).
    >
    > NSWorkspace isn't available in a Foundation application. I wish it
    > was. It would solve a *lot* of problems.
    >
    > --
    > James Bucanek
    >
  • Aaron Burghardt <mailto:<ajb.lists...> wrote (Tuesday,
    October 9, 2007 5:47 PM -0400):

    > If you don't mind linking against the AppKit, you can use NSWorkspace manager without an instance of NSApplication:

    Aaron,

    Thanks for thinking outside the box. I had tried to use
    NSWorkspace before outside of an NSApplication and just couldn't
    get it to work -- at least not for what I was attempting -- and
    simply assumed that NSWorkspace couldn't be used at all in a
    Foundation tool.

    Regardless, I gave your idea a try and it works! I'm getting
    NSWorkspace notifications for every volume mount and unmount event.

    Thanks again, and I hope this information helps someone else.

    James
    --
    James Bucanek
previous month october 2007 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
Go to today