Vincent Gable’s Blog

January 12, 2009

Stopping an AMWorkflow Workaround

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code | , , ,
― Vincent Gable on January 12, 2009

-[AMWorkflowController stop:] does not stop a workflow from executing (more info). Here is a workaround that will stop all workflows your application is be running.

When you execute a worklfow using Automator.framework, the workflow is run in its own process, which insulates it from the invoking application, and vica verca 1

. At least on Leopard, the program that runs the workflow is /System/Library/CoreServices/Automator Runner.app/Contents/MacOS/Automator Runner . It has the bundle-identifier com.apple.AutomatorRunner , and it will be a sub-process of your application, since your application kicked it off.

That gives us enough information to kill just the instances of Automator Runner that are running your worklfows. This will stop workflow execution dead in it’s tracks.


- (void) killAutomatorRunnerSubprocesses; {
   ProcessSerialNumber   PSN = {kNoProcess};
   while(GetNextProcess(&PSN) == noErr) {
      NSDictionary *info = (NSDictionary*)ProcessInformationCopyDictionary(&PSN,kProcessDictionaryIncludeAllInformationMask);
      if(info) {
         NSString *theirBundleID = [info objectForKey:@"CFBundleIdentifier"];
         if([theirBundleID isEqualTo:@"com.apple.AutomatorRunner"] && [self processWasLaunchedByUs:PSN]){
            NSLog(@"Killing AutomatorRunner sub-process: PSN={high=%u low=%u}", PSN.highLongOfPSN, PSN.lowLongOfPSN);
            KillProcess(&PSN);
         }
         CFRelease((CFDictionaryRef)info);
      }
   }
}

- (BOOL) processWasLaunchedByUs:(ProcessSerialNumber)PSN; {
   ProcessInfoRec processInfo = {0};
   //we use GetProcessInformation(), and not the more modern ProcessInformationCopyDictionary()
   //because ProcessInformationCopyDictionary stores the "ParentPSN" as a 32 or 64-bit number,
   //and it is not clear to me what the endian-safe way to transform it into a ProcessSerialNumber
   //structure is, see http://lists.apple.com/archives/carbon-dev/2007/Mar/msg00283.html
   if(GetProcessInformation(&PSN,&processInfo) != noErr)
      return NO;
   Boolean   theyAreOurChild = FALSE;
   ProcessSerialNumber ourPSN = {0,kCurrentProcess};
   OSStatus err = SameProcess(&processInfo.processLauncher, &ourPSN, &theyAreOurChild);
   return !err && theyAreOurChild;
}
   

Please let me know if this works for you, of if you have a better solution.


1
Automator.app does not appear to spawn an Automator Runner when it runs a workflow. I’m not sure why. But since the stop button works in Automator.app, understanding why might lead to a better work around. Or not.

December 19, 2008

Undocumented Automator.framework Goodness: Actions Library

In Leopard, Apple introduced AMWorkflowView and AMWorkflowController, which let you easily add automator-editing capabilities to your application (modulo bugs!)

Obviously, for editing a workflow to be useful, you need to be able to add actions to it. And to do that, you need to be able to browse and search actions. But the current Automator.framework has no official support for doing this.

But there are undocumented private APIs, which Automator.app (link launches it) uses…

How to Find Them

class-dump is an amazingly useful command-line tool that lets you generate headers from a compiled Objective-C binary. Download it, and point it at /System/Library/Frameworks/Automator.framework/Automator, to see everything Automator.framework really lets you do.

Instant Library Panel

The most useful definition I found was,
@interface NSApplication (AMLibraryPanel)
- (void)orderFrontAutomatorLibraryPanel:(id)sender;
@end

Calling [NSApp orderFrontAutomatorLibraryPanel:nil]; will show a panel with the same action-library view Automator has.

This is what I’m using right now in IMLocation to let people find actions for workflows. I don’t like the idea of using private APIs. But I like the idea of implementing my own AMLibraryView replacement even less. It would be a lot of work to make it as good as the real thing. Any differences give users a fractured experience — one way to do something in Automator, another in my program. And I would get dismal ROI, because I expect Apple to expose this functionality in the (near) future.

(I also wouldn’t be so bullish on using private APIs if I didn’t have workarounds in place right now. The only way orderFrontAutomatorLibraryPanel: gets called is if a user presses a “Show Actions” button on the toolbar. That same toolbar has an “Open in Automator” button that opens the worklfow in Automator, where they can edit it without bugs. So even if orderFrontAutomatorLibraryPanel: stopped working tomorrow, users could still do everything they could before — albeit less elegantly).

Looking Just Like Automator

I don’t recommend doing this, but I’ve been able to embed an AMLibraryView in an NSSplitView next to an AMWorkflowView, to get a more Automator.app-like appearance.

Here’s how I did it, given the IBOutlets workflowViewAndLibrarySplitView which is an NSSplit view with an AMWorkflowView in one side, and the other side’s view connected to the outlet workflowLibraryView,

[workflowViewAndLibrarySplitView replaceSubview:workflowLibraryView with:[[AMLibraryPanel sharedLibraryPanel] _libraryView]];

Will put the library view inside your split view.

Of course this screws up orderFrontAutomatorLibraryPanel:. And I would not expect it to work in two windows. And it’s using an underscore-private method of an already private and undocumented API. That’s just to too risky for me; so I stick with the panel. It gets the job done with one line of code, and that’s good enough for me.

December 18, 2008

Automator Bugs

I’ve started using Automator in IMLocation to let people build a set of actions that are executed when they arrive somewhere (for example, muting their laptop’s speakers in a classroom).

Unfortunately, my impression of Automator.framework so far is: buggy.

Here are some issues I’ve run into so far.

-[AMWorkflowController stop:] does not stop the workflow! I do not yet have a workaround.

AMWorkflowController sends its delegate a -document message whenever an action is removed from the workflowView, by clicking the (x) button. If the delegate does not implement it, the action will not be removed!

The (x) button is enabled , but does nothing, in workflows that are not editable.

-[AMWorkflow initWithContentsOfURL:error:] throws an exception when the URL is nil.

This project demonstrates the first two issues.

Powered by WordPress