Writing a Snow Leopard Service for Finder.app - objective-c

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.

Related

File icons getting changed to App icon in macOS Catalina

I have a Mac app that opens office files(.doc, .xls, etc.) and I need to show custom icon for these supported files.
I used to achieve by adding the supported UTI types in CFBundleDocumentTypes and assign my custom icns icon. Also set my app as the default app which opens these file types.
Now this approach worked flawlessly until macOS Catalina beta, even with Microsoft apps being present along with my app. In macOS Catalina beta onwards, I am seeing my app icon in place of all the file icons.
I tried clearing icon cache and even relaunching Finder, but to no avail.
Later on I even tried by adding the UTI types under UTExportedTypeDeclarations and UTImportedTypeDeclarations.
Is this a bug with Catalina Beta? Or anything I can do to get this working.
My plist has UTExportedTypeDeclarations looking like this. UTImportedTypeDeclarations and CFBundleDocumentTypes have similar values.
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>org.openxmlformats.spreadsheetml.sheet</string>
<string>org.openxmlformats.openxml</string>
<string>public.composite-content</string>
</array>
<key>UTTypeDescription</key>
<string>Excel Open XML spreadsheet</string>
<key>UTTypeIconFile</key>
<string>custom.icns</string>
<key>UTTypeIdentifier</key>
<string>com.microsoft.excel.openxmlformats.spreadsheetml.sheet</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>com.apple.ostype</key>
<string>XLSX</string>
<key>public.filename-extension</key>
<array>
<string>xlsx</string>
</array>
</dict>
</dict>
</array>
It looks like macOS 10.15 changed the way how the corresponding icon for a type is resolved.
I found a way how to get proper document icons for my app on Catalina:
Previously I only had an icon defined for the CFBundleTypeIconFile key in my CFBundleDocumentTypes dictionaries. But since Catalina, the system uses the LSItemContentTypes array to find an exported UTI and then uses the icon of that UTI (defined via the UTTypeIconFile).
If the LSItemContentTypes array contains types that are not known to the system yet (= newly introduced custom types), those types must also be exported by defining dictionaries in UTExportedTypeDeclarations.
This works for my case because I use a custom file format with its own UTI.
For the case you are describing, where existing UTIs are used, I am not sure if it is still possible to override the icons of UTIs that you don't "own".
I think the correct way to define custom icons for 3rd party types would be to define dictionaries in the imported types array (UTImportedTypeDeclarations). In that case the system should choose your custom icon as long as no other app declares ownership of an UTI by exporting it. I suppose that the icon of the app that is set as Default application for a type wins in the case where 2 or more apps claim ownership for it (Haven't tried that).
Another issue I ran into was, that the icon file assigned to UTTypeIconFile can't come from an Asset Catalog (this worked for CFBundleTypeIconFile). It needs to refer to an .icns file in the Resource folder in your bundle.
Existing .iconset folders from the Asset Catalog can be converted to icns with the following command:
iconutil --convert icns iconname.iconset
I am not sure if this intended behavior or if this is just a bug in the Asset Catalog compiler build phase of Xcode 11. I filed a bug via Feedback Assistant.
I also have this issue. Tried empty project and reproduced it with bundled macOS applications, so it's mostly safe to assume this is a bug in macOS Catalina.
As trojanfoe suggested in a comment the only thing to do right now is reporting it to Apple via the Feedback Assistant.
Thomas Zoechling refers to another issue that may affect earlier macOS versions too, but does not solve the main issue here.

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 intercept reading of plist values in Objective-C code?

We're using the new Urban Airship iOS plugin for PhoneGap.
In the plugin's plist file, we're supposed to enter the app-specific keys needed to enable push notifications.
The problem is we have two versions, free and paid, of the same app, but the plist file only accommodates one version.
Essentially, we need to modify the Objective-C code to read different plist values, depending on whether it's the free or premium version.
We currently manage both versions with the same code base and Xcode project. Unless we change the plugin code, it seems like we need to create a new Xcode project, which we don't want to do.
How do we adjust Urban Airship's Objective-C files to read different values from the plsit file?
Sorry to keep you waiting, I wanted to give you a very detailed answer instead of rushing last night :) So here we go.
First in your project we need to add a new target. Go to your project settings and right click your target. Click duplicate.
You'll get a new target probably named Target-copy. You'll also get a new info.plist file just for that target.
Next we're going to edit our Pro version's Built Settings. Scroll or search and find Apple LLVM compiler 4.0 Preprocessing. Add to both your Debug and Release configurations. I normally just go with the simple PRO=1. You also need to add PRO=0 to your lite version or it will be undefined when you try to build that version.
Now lets look at how to add a custom plist like I'm sure you'll need. First create two folders. Its important these are folders not groups. In each folder we can create a plist with the exact same filename.
Since Now you can add something to each of them. I just added a key property and a value pro string / lite string. Finally to the code. In the sample project I made I simple overrode viewDidLoad but obviously this will work anywhere. Since the plists have the same name you can load them with one line of code. They'll never get mixed up because they are only copied to their respective target. If you need to do code level based logic you can use the PRO preprocessor we made.
- (void)viewDidLoad
{
[super viewDidLoad];
// This will load the proper plist automatically.
NSLog(#"Plist Value: %#",[[NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Property List" ofType:#"plist"]] objectForKey:#"property"]);
// Also remember we set up a preprocessor PRO. you can use it as well.
if (PRO) {
NSLog(#"Only Show for Pro");
} else {
NSLog(#"Only Show for Lite");
}
NSLog(#"This will show for both");
}
This is the method I use for all my lite/pro version apps so I can share a common codebase without copying it between projects or other complicated systems. It has worked pretty well for me so far :) Happy Coding!
Source
Figured someone may be able to use the project to look at so here it is on GitHub.

Preferences toolbar without BWToolkit

Since Xcode 4, using BWToolkit isn't really easy. In fact, it's not even possible in IB.
Is there some sample code out there how to create a "preferences-type toolbar" without BWToolkit?
Yes, MVPreferencesController as used by Camino. See here for Doxygen documentation. It displays NSPreferencePanes registered using a plist. It's pretty easy to work with and customize for your purposes. You can also use the Camino project as a source of sample prefpanes.

How to Write OS X Finder plugin

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.