How to Write OS X Finder plugin - objective-c

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.

Related

Is it possible to use together pyggel and GLUT libraries

I'm new to PyopenGL and i'm currently working on a code originally based on the pyggel library, but now I'd like to add some features from GLUT (menu & text) and I'm not really sure how I should join both (if possible).
In GLUT, running glutMainLoop() is required, but on the other hand I have this run() routine:
def run(self):
while 1:
self.clock.tick(60)
self.getInput()
self.processInput()
pyggel.view.clear_screen()
self.mouse_over_object = self.scene.render(self.camera)
pyggel.view.refresh_screen()
#glutMainLoop()
Putting the GLUT routine inside my run() doesn't work (it crashes when it gets to the glutMainLoop).
So, how can I join both loops? Can I? I'm guessing that's what I need to make both things work.
Thanks in advance!
You likely are not going to find this easy to do. Pyggel is based on the Pygame GUI framework, while GLUT is its own GUI framework. You may be able to get text-rendering working, as under the covers GLUT is just doing regular OpenGL for that, but the menus are not going to easily work under Pyggel.
Pyggel has both text-rendering and a GUI framework that includes menus, frames, buttons, labels, etc. You likely want to use that if you're using Pyggel in your project there is an example of GUI usage here:
http://code.google.com/p/pyggel/source/browse/trunk/examples_and_tutorials/tut8-gui.py

How to listen to cocos2d-x mouse & keyboard events?

I'm running the proj.mac version of their sample (that comes with the cocos2d-x files).
I saw that the Mac version has EAGLView.mm which has:
- (void)mouseMoved:(NSEvent *)theEvent
{
DISPATCH_EVENT(theEvent, _cmd);
}
I made sure to enable the mouse moved events in the AppController.mm:
[window setAcceptsMouseMovedEvents:YES];
I can see that it's going there, but what I'm trying to find out is how to make my custom CCLayer "register/listen" to the mouse and keyboard events.
The cocos2d-x forums mentioned that they have a KeyboardTest in their TestCase folder, but I was only able to find a KeypadTest which doesn't do much. Also some examples mentioned CCKeyboardDispatcher, which is nowhere to be found.
Thanks in advance!
I think they may be talking about this patch: https://github.com/cocos2d/cocos2d-x/pull/1849/commits
It's not yet in the mainline. If you do apply it directly you'll hit a few patch rejections and you'll need to manually add the new CCKeyboard* files to your XCode project (Build Phases).
I just pushed up the changes I made to get it to compile (and get KeyboardTest to run in the samples dir):
https://github.com/therealdpk/cocos2d-x/
however, most of the work was not authored by me. I don't know if I'll be able to keep this maintained, either, so use it as a starting point at best.

How to create Apple mail plugin

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 :)

Writing a Snow Leopard Service for Finder.app

I am currently looking into solving the problem with the inability to quickly create new files in the Finder. I will open source what I write because I think the Mac community needs this solved.
On Windows, you can right-click, create new text file.
OS X, you should be able to do this with a service which would work like this:
Right-Click > Services > Create New Text File
Writing a Finder Service in Snow Leopard is theoretically the way to accomplish this, although I haven't been able to find any sample code. (I'll admit I've only looked at the documentation very briefly).
I'm not sure how to get started, whether Apple provide a Services template in Xcode. Basically I'm looking for help with getting a working services project running. The implementation code should then be rather trivial for me to write in Obj-C. So what can I do to create a new working Services project? If i'm wrong about this then tell me the right way to do this and please provide sample code or some steps to get me started.
Edit: Trust me guys, I'm not an OS X noob. Have tried many apps to achieve work arounds: PathFinder, Automator, Terminal,etc and I'm happy with none of them.
I want to create a right-clickable menu item for creating New files, the same as Windows has. If this API doesn't let me do this, then I will modify system files if necessary. But I would rather do it in such a way that doesn't require me hack OS X.
The sad fact is that Apple disabled 3rd party contextual menu items when Snow Leopard was released and devs weren't happy. You could create services under the contextual menu with Automator, but it was very limited.
Yes, Quicksilver is the way I create files at the moment, unless I'm in the terminal when I touch ~/Desktop/file.txt or wherever.
If you cannot answer my question by providing source code for an Xcode project for writing a Service, please keep your opinions on how I should be using my computer to yourself. At any rate, I think I will probably be answering my own question after I go and implement this myself.
Read Services Implementation Guide. If you want a working sample code, see this project I cooked up. As explained in the guide, what you need to do is to install the service handler:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSApp setServicesProvider:self];
NSUpdateDynamicServices();
}
- (void)handleServices:(NSPasteboard *)pboard
userData:(NSString *)userData
error:(NSString **)error {
if([[pboard types] containsObject:NSFilenamesPboardType]){
NSArray* fileArray=[pboard propertyListForType:NSFilenamesPboardType];
// do something...
}
}
and advertise it in your Info.plist:
<key>NSServices</key>
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Service Handling Demo</string>
</dict>
<key>NSMessage</key>
<string>handleServices</string> <!-- This specifies the selector -->
<key>NSPortName</key>
<string>services</string> <!-- This is the name of the app -->
<key>NSSendTypes</key>
<array>
<string>NSFilenamesPboardType</string>
</array>
</dict>
</array>
It's as easy as this! You might need to manually turn on the service entry in the Keyboard Shortcut section of the System Preferences.app. If you want to turn it on automatically, you can write into pbs.plist inside Library/Preferences/, but that's not a documented way of doing things.
The problem is that Finder doesn't show this service item when you right-click an empty region in the Finder window. You can't do anything with it, other than injecting the code. This is inherent in the Finder's support of the service system. If you want to change that behavior, you need to inject code inside Finder.app. That's not that hard. On Snow Leopard, it's standard to use the OSAX loading trick, described at e.g. in this blog post. Then you can use Objective-C runtime functions to patch the behavior of the right-clicking of the Finder, by changing the methods described in this Apple document. (I don't know which of the method Finder uses to respond to the right-click event, though.)
Instead, if you're OK with clicking a button on the toolbar of the Finder window instead of right-clicking, you can add a button, as in this utility cd-to. This uses the ability to put an icon of the app to the Finder toolbar. The app itself just reads the path of the frontmost Finder window via Apple events, and opens a Terminal window for that. I think you can tweak the code of this app to do what you want.
Here follow subjective stuffs:
Honestly, you don't have to use Objective-C to write a Finder service if you just want to make a new file. Automator can do the same thing with a shell script and/or Applescript.
If you want to manage files efficiently on Mac, there are already great utilities around: try Butler or launchbar or Quicksilver, for example.

How to implement a Cocoa-based Adobe Photoshop plugin

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.