Cocoa used to work on CS3 with the trick of putting a Cocoa bundle inside the main Carbon plugin bundle, loading it from Carbon and issuing a NSApplicationLoad(). That's because Photoshop CS3 was Carbon-only and used to unload the plugin bundles.
Photoshop CS4 uses Cocoa and has its own NSAutorelease pool in place on the main thread.
On Photoshop CS4 very simple window-based xibs/nibs loaded by a NSWindowController work out of the box.
But just add a binding to a control on the window and you'll get funny crashes, optionally when you close the window, or the second time you use the plugin, or even when closing Photoshop itself.
Why everything seem to work well until I use some advanced Cocoa features? I'm stuck.
EDIT: I've really found myself the solution to the broader problem "How to use Cocoa in a Photoshop CS3/CS4 plugin?". See below.
You have to create a new Loadable Bundle target that contains your nibs and your Cocoa code. Add the bundle product to the Copy Bundle Resources phase of your plugin. Then the code for a filter plugin that loads a Cocoa window with some controls would be:
Boolean DoUI (void) {
// Create the CF Cocoa bundle
CFBundleRef pluginBundle;
CFURLRef cocoaBundleURL;
pluginBundle = CFBundleGetBundleWithIdentifier(CFSTR("com.example.plugin"));
cocoaBundleURL = CFBundleCopyResourceURL(pluginBundle,
CFSTR("Cocoa_bundle"),
CFSTR("bundle"),
NULL);
CFBundleRef cocoaBundleRef;
cocoaBundleRef = CFBundleCreate(kCFAllocatorDefault, cocoaBundleURL);
CFRelease(cocoaBundleURL);
// start Cocoa (for CS3)
NSApplicationLoad();
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// load the cocoa bundle by identifier
NSBundle* cocoaBundle;
cocoaBundle = [NSBundle bundleWithIdentifier:#"com.example.plugin.cocoa"];
// load the window controller from the bundle
Class testControllerClass;
testControllerClass = [cocoaBundle classNamed:#"MyWindowController"];
MyWindowController* winController = [[testControllerClass alloc] init];
[NSApp runModalForWindow:[winController window]];
[[winController window] performClose:nil];
[winController release];
// release the bundle
CFRelease(cocoaBundleRef);
[pool release];
return 1;
}
This is based on the Craig Hockenberry bundle trick. I'm still testing it but it should work both on CS3 and CS4.
I just started working on writing a Cocoa-based plugin for CS4. Really, there is almost no information out there on this topic, and I've been figuring it out as I go.
Start from this Apple example, and make sure you download the whole project, because there are a few little details missing from the text:
Carbon/Cocoa
Pick one of the Photoshop SDK examples (I used ColorMunger), and keep it simple to start, so just try to replace the "About" dialog box, using the Apple example as your template. Once you have that working with no memory issues, you should be on your way.
I've been a Java and Ruby programmer for 10 years, so my C/C++ foo is rusty, and I'm just learning Objective C as I go. Two "gotchas" I ran into, just in case....
do NOT create a controller object in your NIB/XIB file. Because, based on that Apple example, the controller opens up the NIB file in it's init method, and you get a really interesting recursive loop
The Apple example is embedding the Cocoa stuff in a Carbon based C app. The Adobe examples are all C++. Don't forget your extern "C" {} in your header file.
CS2 will load PowerPC Mach-O code as readily as CS3/CS4. Has anyone tested this Cocoa approach in CS2?
Currently I use Carbon for CS2/CS3/CS4 as this is guaranteed to work everywhere the plugin loads; and Cocoa for CS5 of course, whether 32 or 64 bit.
Chris Cox isn't optimistic about Cocoa working in anything other than CS5:
http://forums.adobe.com/message/3256555#3256555
So what's the real deal here? It's pretty hard to ignore advice from the horse's mouth.
Related
I'm going to create a mail plugin for the OS X Mail.app application for some additional features.
I have no idea where to start as there is no official documentation for plugins.
Can anyone please help me, how can I start the project.
Is there any initial link or tutorial, please suggest?
As noted, writing Apple Mail plugins is not straightforward, since it only has a private plugin API, which is entirely undocumented and can change with any new version of Mail.app. The best code example is GPGMail, which is open source & still active (already working on Yosemite support). Here is what I successfully did to get started (will put it up on github once finished):
How to build a minimal Apple Mail plugin (as of Mavericks & Xcode 6.0.1)
you need to create an OSX "Bundle" project in XCode
wrapper extension is mailbundle (under Packaging in the project Build settings)
a bundle needs to be stored under ~/Library/Mail/Bundles (as Build Phase add a Copy Files action with that as absolute path destination and the *.mailbundle from your build/ folder as item to copy)
for development, I have set up /Applications/Mail.app as executable in my run scheme, so that Run in XCode will build it, copy the bundle and start mail; note that at this point you'll get an error from Mail that your plugin cannot be started and was disabled
you need to provide a list of SupportedPluginCompatibilityUUIDs in the Info.plist, I stole it from GPGMail, these change with new Mail/OSX versions
use class-dump to generate the header files from Mail.app's private API
starting point is MVMailBundle, which you have to inherit from and which has a registerBundle method to hook you in
I extracted that from the huge generated header file in a small MVMailBundle.h header to include where needed (as done by GPGMail)
create a new class MyMailBundle, inheriting from NSObject
it needs an initialize method
and set it as "Principle class" in the Info.plist so that it gets run when the bundle is loaded by Mail.app
#import <Cocoa/Cocoa.h>
#interface MyMailBundle : NSObject
+ (void)initialize;
#end
initialize implementation: previously, you could use the simple way and directly inherit as done in Letterbox, however, since 64-bit runtimes of Objective-C you have to use the dynamic way as done by GPGMail:
using NSClassFromString to dynamically get the MVMailBundle class
and class_setSuperclass from <objc/runtime.h> to have your own class inherit from it
and then call registerBundle on it casted as MVMailBundle (requires include of MVMailBundle.h)
#import <objc/runtime.h>
#import "MVMailBundle.h"
#import "MyMailBundle.h"
#implementation MyMailBundle
+ (void)initialize
{
NSLog(#"Loading MyMail plugin...");
// since 64-bit objective-c runtimes, you apparently can't load
// symbols directly (i.e. through class inheritance) and have to
// resort to NSClassFromString
Class mvMailBundleClass = NSClassFromString(#"MVMailBundle");
// If this class is not available that means Mail.app
// doesn't allow plugins anymore or has changed the API
if (!mvMailBundleClass)
return;
// dynamically change super class hierarchy
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
class_setSuperclass([self class], mvMailBundleClass);
#pragma GCC diagnostic pop
// register our plugin bundle in mail
[[((MyMailBundle *)self) class] registerBundle];
NSLog(#"Done registering MyMail plugin.");
}
#end
add some NSLog logging calls to verify the right thing is happening, they'll be visible in XCode's console when running/debugging Mail.app from within XCode or alternatively in the system logs of Console.app
This should successfully run the plugin in Mail with no error!
The next steps involve crazy things like MethodSwizzling and ClassPosing to modify Mail's behavior, where GPGMail can be a helpful example. (Haven't been there myself yet)
For reference, here are some of the resources that helped me:
GPGMail
Adam Nash: Getting Ready to Write an Apple Mail.app Plug-in for Mac OS X - some good links, but apparently he never finished the project, so no code
James R. Eagan: Demystifying Mail.app Plugins on Leopard - using PyObjC to write a plugin in Python, explains the basic mechansims, very useful
Aaron Harnly: Mail Plugin Template - for XCode 2 I think, unfortunately the template (download a zip) doesn't work as template in Xcode anymore, but the code is still useful to look at
Aaron Harnly: Letterbox sources - from the same guy, but also from 2007, very outdated; contains a readme from the template, though it doesn't really help if you can't use the template.
There is no official supported way to build such a tool - you need to start trying to hook in to Mail.app without any official support.
If you want to persist on this sort of thing, then you'll need to understand how Mail.app internals work, which is a bunch of using the debugger and class dump to inspect libraries in other apps:
https://github.com/nygard/class-dump
You'll probably also want a way to inject code into other applications, for example:
https://github.com/rentzsch/mach_inject
And every time Apple update Mail.app you'll potentially need to redo everything :)
Let's say I have an existing iOS or Mac Framework that we'll call Test.framework. This framework has compiled and linked successfully (a trick was done to lipo both i386 and armv6/7 archs for the iOS part) and is working perfectly fine.
Now I want to add an image to it let's call it 'test.png'. I have copied it into the Resources folder of my framework so that I have the following structure:
ls Test.framework
- Headers -> Versions/Current/Headers
- Resources -> Versions/Current/Resources
- Test -> Versions/Current/Test
- Versions
ls Test.framework/Resources
- Info.plist
- test.png
Yet when I create a project using my framework I cannot access that image.
I've tried:
[UIImage imageNamed:#"test.png"]
[UIImage imageNamed:#"Test.framework/test.png"]
[UIImage imageNamed:#"Test.framework/Resources/test.png"]
But nothing worked out that always gave me back a nil object.
Any ideas ?
EDIT
After much further investigation it seems what I am trying to accomplish can't be done on iOS. The reason is that the final application bundle (the .app) doesn't copy the private frameworks where applications compiled for Mac OS will.
This is further detailed in the iOS Bundle Structures documentation.
Thanks to Rob Keniger and xuzhe for their appreciated help. I will credit xuzhe for the answer as it is actually the most appropriate answer to my original problem (even though Rob comment made me dig quite deeper into the issue)
The "imageNamed:" method is only for images in your App bundle. You should use
[[UIImage alloc] initWithContentsOfFile:path];
instead.
About the path, since I am not sure if the "Test.Framework" is in you App bundle, I am not able to give you a sample code. But if it dose, the code below should work:
NSString* path = [Nsstring stringWithFormat:#"%#/Test.framework/Resources/test.png", [[NSBundle mainBundle] bundlePath]];
You can get the bundle for a particular class using [NSBundle bundleForClass:[SomeClass class]]. All you need to do is pass in a class that's defined in the framework and you'll have a reference to the framework's bundle.
You can then ask the bundle for the path to the image using the pathForResource:ofType: method of NSBundle, and then use the initWithContentsOfFile: method of NSImage to create your image.
Note that you should never hard-code paths. Ever. There are many different functions and methods for obtaining paths to resources, you never need to hard-code them.
I have an AppleScript that I am trying to convert to ScriptingBridge. Since my application is a C++/Obj-C application, ScriptingBridge is much easier to use and quite a bit faster (not to mention I hate dynamically building AppleScripts).
The AppleScript sends a message to Photoshop to open a file. The file parameter is sent as an alias, but ScriptingBridge imports the parameter as an id. I don't know what Obj-C object I should pass in?
I've tried passing an NSURL and an NSString (probably incorrectly :-P), but to no avail. Any suggestions on what I should be passing for the file alias?
The short answer is that you can't open documents in Photoshop with Scripting Bridge.
Apple's docs really spell it out like it is. All classes must have a container, which is a mutable array, that they need to be added to before they can be acted upon, as shown in the generated header...
#interface photoshopCS4Application : SBApplication
- (SBElementArray *) documents;
- (SBElementArray *) fonts;
- (SBElementArray *) notifiers;
... and that is the complete list of top-level containers available to us. The open command requires a photoshopCS4OpenOptions to be generated and populated. Because the API doesn't expose the array to store the newly created photoshopCS4OpenOptions object, we can't use a newly created photoshopCS4OpenOptions object. Therefore we can't make a target document and by extensions can't use the open command in Scripting Bridge. The same can be said of all the commands that require some kind of options object.
The only workaround that I have sorted out is to either open a document with native Applescript called from Cocoa or objc-appscript, and then parse the documents array looking for the one just opened. It's not ideal, but then neither is Scripting Bridge because it requires application developers write their scripting APIs in a very specific way that is not native to the OSA framework.
If your program is such that opening a Photoshop document can be executed outside your AppleScript script/Scripting Bridge code, Cocoa provides a method to open files with a specific application:
[[NSWorkspace sharedWorkspace] openFile:#"/Users/bavarious/Desktop/test.psd" withApplication:#"Adobe Photoshop CS4"];
or, if you want to use the default application that handles that file type, you can drop the application name altogether:
[[NSWorkspace sharedWorkspace] openFile:#"/Users/bavarious/Desktop/test.psd"];
Consider Appscript. http://appscript.sourceforge.net/
Here's the code using that:
APApplication *adobePhotoshopCs4 = [APApplication applicationWithName: #"Adobe Photoshop CS4"];
id result = [[adobePhotoshopCs4 open_] send];
(Note, I'm not a Cocoa programmer - I mainly use Appscript with Python but Appscript comes with ASTranslate which translates Applescript into Python, Ruby or Obj-C and that's the output - but I've found there are subtle mistakes in the past sometimes with the translator)
How would I retrieve an array of paths to the selected files in Finder?
I have searched around but have only found links regarding AppleScript. I have also looked at NSWorkspace and NSFileManager but I didn't find anything.
Expanding on #Bavarious's (correct) answer, here's how I've gotten the selection from Finder using the Scripting Bridge:
#import "Finder.h" //my copy is here: https://github.com/davedelong/BetterInfo/blob/master/Finder.h
FinderApplication * finder = [SBApplication applicationWithBundleIdentifier:#"com.apple.finder"];
SBElementArray * selection = [[finder selection] get];
NSArray * items = [selection arrayByApplyingSelector:#selector(URL)];
for (NSString * item in items) {
NSURL * url = [NSURL URLWithString:item];
NSLog(#"selected item url: %#", url);
}
If it is possible to get the list of selected files in a given Finder window using AppleScript, you can probably use Scripting Bridge in a Cocoa application to interface with Finder. Quoting Apple’s documentation,
Scripting Bridge is a framework and a technology that makes it much easier for Cocoa developers to control and communicate with scriptable applications. Instead of incorporating AppleScript scripts in your application or dealing with the complexities of sending and handling Apple events, you can simply send Objective-C messages to an object that represents an application with a scripting interface. Your Cocoa application can do anything an AppleScript script can, but it does so in Objective-C code that is integrated with the rest of your project’s code.
There is no Cocoa class that represents Finder or, more specifically, Finder windows. Finder is an application, and a scriptable application at that, so Scripting Bridge is the way to go.
I'm looking for a guide or sample code for writing Mac OS X Finder plugins? It would like to know how to do some simple actions:
adding image overlayers to icons
adding context menu items
listen to file changes
I found the following two resources:
Writing Contextual Menu Plugins for OS X: An outdated document from 2002 that uses the COM API targeting Mac OS X 8/9.
SCPlugin: Open-source SVN Mac application that includes a Finder plug-in.
I am tempted to review the SCPlugin code, but was hoping to find an easier sample to digest.
The Finder Icon Overlay example project represents a small and very basic but actually working example of the answer below.
https://github.com/lesnie/Finder-Icon-Overlay
I know this is so old, but some may be still interested in topic (?)
Here is what I have it done under Leopard (10.6). At first proper Finder's headers are needed. Use class-dump tool to get it. Then write your code as a SIMBL plugin (refer to documentation how to do it), swizzling some methods. For instance to draw something over icon in ListView, drawIconWithFrame: method of TIconAndTextCell method must be overriden.
Here's the code for method swizzling:
+ (void) Plugin_load
{
Method old, new;
Class self_class = [self class];
Class finder_class = [objc_getClass("TIconAndTextCell") class];
class_addMethod(finder_class, #selector(FT_drawIconWithFrame:),
class_getMethodImplementation(self_class, #selector(FT_drawIconWithFrame:)),"v#:{CGRect={CGPoint=dd}{CGSize=dd}}");
old = class_getInstanceMethod(finder_class, #selector(drawIconWithFrame:));
new = class_getInstanceMethod(finder_class, #selector(FT_drawIconWithFrame:));
method_exchangeImplementations(old, new);
}
I am overriding "drawIconWithFrame:" method with my method "FT_drawIconWithFrame:". Below is sample implementation for this method.
- (void) FT_drawIconWithFrame:(struct CGRect)arg1
{
[self FT_drawIconWithFrame:arg1];
if ([self respondsToSelector:#selector(node)]) {
if ([[[[NSClassFromString(#"FINode") nodeWithFENode:[(TNodeIconAndNameCell *)self node]] fullPath] lastPathComponent] hasPrefix:#"A"])
[myPrettyIconOverlayImage drawInRect:NSMakeRect(arg1.origin.x, arg1.origin.y, arg1.size.height, arg1.size.height) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
}
}
Essentially it draws "myPrettyIconOverlayImage" over every icon for file with filename starts with letter "A". This logic is up to you.
Pay attention to this line: [self FT_drawIconWithFrame:arg1]; this is how to call 'super' in order to get normal icon and name etc. I know, looks weird, like loop, but actually it isn't. Then wrap in into SIMBL plugin, install SIMBL and ...run.
Due to changes in Lion some work have to be done from scratch (make new "Finder.h" file with all declarations needed in it, find proper classess and methods to override), but this technique still works.
Happy hacking!
For Yosemite (MacOS 10.10 & newer), you can use Apple's FinderSync framework, which allows Finder extensions to:
Express interest in specific folder hierarchies
Provide "badges" to
indicate the status of items inside those hierarchies
Provide dynamic
menu items in Finder contextual menus, when the selected items (or
the window target) are in those hierarchies
Provide a Toolbar Item
that displays a menu with dynamic items (even if the selection is
unrelated)
Sadly, programming a Finder plugin actually does still require getting your hands dirty with COM. If you look at the SCFinderPlugin subproject of the SCPlugin project, you will find that it follows exactly the same techniques outlined in your first link, including setting up a vtable for COM, writing AddRef/ReleaseRef functions, and so on. Writing a plugin, where you're simultaneously managing old-school Carbon memory management, COM-style memory management, and Cocoa/new-style Carbon memory management, can be an incredible pain—and that totally ignores the fact that you'll be interacting in three or more radically different APIs, with different naming conventions and calling semantics. Calling the situation hysterically poor would be a vast understatement.
On the bright side, the Finder in Mac OS X 10.6 Snow Leopard has been fully rewritten in Cocoa--and with that come vastly superior plugin interfaces. If you are lucky enough to be in a situation where you can actually only target Snow Leopard, you probably should grab an ADC Premier or higher membership, download the prerelease builds, and code against that. Besides, your plugin may not work on 10.6 anyway without a Cocoa rewrite, so it might make good sense to take a look at Snow Leopard before it gets released, regardless.
There is no official or supported plugin system for the Finder. Starting with OS X 10.6, you will need to inject code into the Finder process and override objective C methods in the Finder process.
I've done this for a proprietary project. I can tell you that the reason that there are no examples or tutorials for this is because it is a significantly difficult and time consuming development task. For this reason, there's plenty of incentive for individuals or organizations who have accomplished this to guard the specifics of their process closely.
If there's any way at all that you can accomplish your goal using the Services API, do it. Writing a Finder plugin will take you 1-2 solid months of painstaking development and reasonably deep knowledge of C and Objective-C internals.
If you're still convinced that you want do to this, grab mach_star. Good luck.
As far as I know, there's no official plugin architecture for the Finder. You may be able to add image overlays to icons through an external application without having to hook into the Finder, although it wouldn't be on the fly. I don't think there is a way to add contextual menu items aside from Folder Actions and Automator. You can also look into writing an external application to monitor File System changes using the FSEvents API.
Here's a completed solution for Finder icon badges and contextual menus in Lion and Mountain Lion using the techniques described by Les Nie.
Liferay Nativity provides a scripting bundle that will swizzle the relevant Finder methods and a Java client for setting the icons and context menus. It also includes equivalent projects for Windows and Linux.
The project is open source under LGPL, so feel free to contribute any bug fixes or improvements!
The pickings are slim; it's never been really clear to me whether Finder Plugins are actually supported. A few more leads, though:
SampleCMPlugIn - Carbon-based of course, since so is Finder. Note that almost any Finder plugin is probably going to stop working with 10.6.
Automator can save things as a "Finder plugin." It's a more supported version of what you're discussing, but of course less flexible.
To add Finder/File browser icon overlays and context menus, in a cross-platform manner, from Java, take a look at the Liferay Nativity library.
I also make mention of this in another SO post, which also contains links to Apple's 'Finder Sync' docs and API.