Show icons in the dock contextual menus in OS X? - objective-c

My question is quite simple :
To use a custom menu for the apps icon on the dock, - (NSMenu*) applicationDockMenu: (id) sender; of the NSApplicationDelegate has to return the menu that the dock will display.
Using setImage on a NSMenuItem, you can normaly add icons to the menu. They show up on the normal menu, but not on in contextual menu of the app's dock icon.
Then how did Apple manage QuickTime, XCode, Preview to show icons in the list of recent opened files accessible in their dock contextual menu ?
Thx.

The recent files list is actually part of the standard Dock icon menu. To use it in your app, you should build an NSDocument-based application. By using NSDocument, you will get the recent files menu/behaviour for free.
If your application cannot be based on NSDocument, you can instruct Cocoa to maintain a recent documents list based on URLs:
NSDocumentController *docController = [NSDocumentController sharedDocumentController];
[docController noteNewRecentDocumentURL:locationOfMyRecentFile1];
[docController noteNewRecentDocumentURL:locationOfMyRecentFile2];
[docController noteNewRecentDocumentURL:locationOfMyRecentFile3];
Note that currently, -noteNewRecentDocumentURL: only supports file:// URLs (which you can create from a path with +[NSURL fileURLWithPath:].) In the future, its behaviour will presumably change to allow URLs with other schemes.

Here's my understanding, which is partly conjectural and related to implementation details:
The Dock runs in a separate process, and you can't pass an arbitrary NSImage trivially across the process boundary from your application to the Dock. There are only two kinds of images that can be passed properly: standard system icons, and icons in your resource bundle. But I don't think NSImage does the necessary incantations for either of these to work.
So you're going to have to use Carbon. Specifically, you need to use SetMenuItemIconHandle with either kMenuSystemIconSelectorType (covers Carbon IconRefs, obtained with GetIconRef) or kMenuIconResourceType (CFStrings that refer to an .icns file in your application bundle's Resources folder).
The relevant headers are <HIToolbox/MacApplication.h> (for GetApplicationDockTileMenu), <HIToolbox/Menus.h> (for SetMenuItemIconHandle) and <HIServices/Icons.h>, (for GetIconRef, if you're using system icons).
Untested, but it should look something like this:
#include <Carbon/Carbon.h>
SetMenuItemIconHandle(
GetApplicationDockTileMenu(),
[dockMenu indexOfItem:dockMenuItem],
kMenuIconResourceType,
(Handle) CFSTR("icon.icns")
);
It may not be this simple; some of this may be 32-bit only.

Related

In Obj-C, how to programmatically set the default "open with" property of a file in Mac OS X

In creating some .mov files using Cocoa (Obj-C), I'd like to set them to be opened by default by a specific program, instead of the default. This should be a file level property, I do not wish to change the default program for all files with the same extension. This is to be done from Cocoa itself, as opposed to manually in "context menu">>"Get Info">>"Open With".
There's an undocumented function call that sets this:
// undocumented function call
extern OSStatus _LSSetStrongBindingForRef(const FSRef *inItemRef,
FSRef *inAppRefOrNil);
*If you use this in your application and submit it to the AppStore it will probably get rejected.
As an intermediate between doing it by hand and doing it from Cocoa, there is an Automator action called "Set Application for Files".
I don't think there is a supported way to do it programmatically, but some people have figured out what Finder is doing: Adding a resource of type 'usro' that contains a full path to the application. See for example this discussion. Note: the Resource Manager is deprecated as of 10.8.

Launch screens supporting iOS6 and iOS7 - forced to splash screen

When it comes to the launch screen I can't find a unifying way to mimic the look of the application on both iOS6 and iOS7 (supporting both). Are we forced to make a regular splash screen of a logo or similar if we have a toolbar menu or do you guys have any great ideas how to solve it?
Short answer
In iOS 7, an app can use a different launch image depending on which version of iOS it’s running in. To provide different launch images, add the UILaunchImages key to the Info.plist file and use a dictionary to describe each launch image.
Background
It uses the following keys:
UILaunchImageName - A string containing the name of the PNG image file. The image file must reside at the top level of the app bundle.
The name you specify for this key should not include a filename
extension, nor should it include modifiers such as #2x, -568h,
~iphone, or ~ipad.
On disk, your image filenames may still include the #2x, -568h,
~iphone, or ~ipad modifiers as appropriate, although they are not
required. The system automatically accounts for such modifiers when
choosing which file to load.
UILaunchImageMinimumOSVersion - for iOS7 this should be a string “7.0”.
UILaunchImageOrientation – String containing one of: Portrait, PortraitUpsideDown, Landscape, LandscapeLeft, LandscapeRight.
UILaunchImageSize – String specifying width and height, ex: “{320, 480}”. You must specify the width and height with respect to
the device in a portrait orientation. In other words, portrait and
landscape images targeting the same device would have the same width
and height.
If this key is present, iOS 7 uses it exclusively to obtain launch
images.
BUT: I found that sticking to the naming convention also for iOS7 helped a lot!
This key is supported in iOS 7.0 and later.
OK – so now what?
Because I already had launch images for iOS6 and with all their specific naming conventions. I chose to make a copy of all of them and prefix the name with ”iOS7-” so as to limit my own confusion about all the different sizes and names. Making a prefix should prove to come in handy as then most of the images would immediately be loaded correctly.
The filenames:
I had these for iOS6 already, I also list the file sizes for those in need:
Default.png (320x480)
Default#2x.png (640x960)
Default#2x~ipad.png (2048x1496)
Default~ipad.png (768x1004)
Default1024x768.png (1024x768)
Default1024x768#2x.png (2048x1536)
Default-568h#2x.png (640x1136)
Default768x1024.png (768x1024)
Default768x1024#2x.png (1536x2048)
Default-Landscape~ipad.png (1024x748)
Default-Portrait#2x~ipad.png (1536x2048)
So I made a copy of all of these filenames for iOS7 (same sizes) prefixing them with "iOS7-":
iOS7-Default.png
iOS7-Default#2x.png
...
In XCode
Now to create your entry in PLIST. Go to your-name-of-application.plist. In a blank area, right-click and choose ”Add Row”. Make sure it becomes a top item and not a sub-item of some other information in the .plist.
Write:
UILaunchImages
Right-click on this UILaunchImages and select value type ”Array”.
Use the illustration below as a guide to the text and for how it will look when it is all finished:
If you open up this array so the little indicator triangle to the left points down, it is empty the first time, but if you choose ”add row” while it is open it will create a sub-line. Do that now:
Right-click on the UILaunchImages and select ”Add row”.
Right-click on this new line (item 0) and select value type ”Dict”
Continue opening this items with the triangle indicator and right-click and ”Add row”
This item you will name UILaunchImageMinimumOSVersion and set value type to “string” and the string to “7.0”
Now the following are all strings and should be at the same level as the UILaunchImageMinimumOSVersion item. In the same dict (dictionary). Create these by just choosing “Add row” for each:
UILaunchImageName – base-name-of-iOS7-launch-image. In my case this was ”iOS7-Default”
UILaunchImageOrientation - example: Portrait
UILaunchImageSize - the size of the elementary base iOS7-Default.png: "{320, 480}". The program will find all the files with permutations of the base name. Remember to select the base name of the file without ipad/iphone/portrait/landscape or .png specifications.
Note:
Xcode had already made the following items in the .plist for me after first adding iOS6 images in all available slots :-)
UILaunchImageFile~ipad … = ”Default” – so this was OK
UILaunchImages~ipad … Had two items that needed to be updated to iOS7 versions, because they where now incorrectly holding the iOS6 version. Those I had named Default1024x768 and Default768x1024 and now I just prefixed ”iOS7-” to each of the names and I was done.
Example of how it may look for those wanting to edit plist directly:
<key>UILaunchImages</key>
<array>
<dict>
<key>UILaunchImageMinimumOSVersion</key>
<string>7.0</string>
<key>UILaunchImageName</key>
<string>iOS7-Default </string>
<key>UILaunchImageOrientation</key>
<string>Portrait</string>
<key>UILaunchImageSize</key>
<string>{320, 480}</string>
</dict>
</array>
[edit by jd: fixed spelling of "UILaunchImages"]
Highlight the project in the project browser, select "General", scroll down to "App Icons", click on "Use Asset Catalog", and select "Migrate". Your existing icons and splash screens will be automagically migrated into an asset catalog. You can then select the catalog to add further images.
To add new images you simply drag from Finder and drop into the squares for each image type.
(Caution: The catalog editor inexplicably uses a non-scrollable wide format, and you can be missing stuff off the right side if your screen isn't wide enough.)
You can also use the new image catalogue feature in Xcode 5 to manage multiple versions of launch images.
Now you can directly add the app icons and splash images in the images.xcassets,
Click on + button to add the respective image set for iphone5 with iOS 5,6,7 ,iphone4, iPad.
now no need to set the images name like default.png,default#2x.png
Be warned when using an images.xcassets repository it will not allow you to localize your splash screens.
I'm currently trying to get a French and English version of our app.
WWW> Will this 'plist' method work if you need to localize your splash screens?
I also had the same issue with an older app that I developed for iOS 7. It Archived and Uploaded fine with Xcode 6, but the "binary not optimized for iPhone5" error returned with Xcode 7. After trying a myriad of other solutions, I was only successful by removing all references to any Launch Image (since I was using a universal .xib) AND setting the deployment target from 7.0 to 8.0

OSX / Objective-C Window Management: manipulate the frames & visibility of other applications

I would like to create a system tool / application which has the capacity to aid in window management. I'm trying to find documentation about the following topics, if they are indeed possible given the security sandboxing of OSX.
Show a list of running applications with the name & icon, and allow the user to choose one
Manipulate the frame(s) of said application's windows (eg, resize, reposition) from my app (with animations -- though I assume this will be trivial once I can perform the actual change)
Hide or show these applications from task managers, etc.
Be able to launch (or terminate) instances of the given application
It seems to me that Quicksilver accomplishes many of these things, but the lack of AppStore availability makes me wonder if it possible to do this while remaining in the OSX sandbox.
There are a lot of pieces of software out there that do window management. You can check out a tiling window manager I've been hacking on called Amethyst. The basic idea behind software like this relies on Accessibility (which you can find documentation for here). As a quick overview the APIs work by acquiring references to accessibility elements (applications, windows, buttons, text fields, etc.) which have properties (hidden, position, size, etc.), some of which are writable.
As an example let's say that you wanted to move all windows in every running application to the upper left corner of the screen. That code might look like
for (NSRunningApplication *runningApplication in [[NSWorkspace sharedWorkspace] runningApplications]) {
AXUIElementRef applicationRef = AXUIElementCreateApplication([runningApplication processIdentifier]);
CFArrayRef applicationWindows;
AXUIElementCopyAttributeValues(applicationRef, kAXWindowsAttribute, 0, 100, &applicationWindows);
if (!applicationWindows) continue;
for (CFIndex i = 0; i < CFArrayGetCount(applicationWindows); ++i) {
AXUIElementRef windowRef = CFArrayGetValueAtIndex(applicationWindows, i);
CGPoint upperLeft = { .x = 0, .y = 0 };
AXValueRef positionRef = AXValueCreate(kAXValueCGPointType, &upperLeft);
AXUIElementSetAttributeValue(windowRef, kAXPositionAttribute, positionRef);
}
}
Which illustrates how you get references to applications and their windows, how to copy attributes from an accessibility element, and how to set attributes of an accessibility element.
There are a variety of notifications documented in NSWorkspace for the launching and termination of applications, and the accessibility framework also has a sense of notifications for things like an application creating or destroying windows, or a window miniaturizing or deminiaturizing.
Animating the window changes is non-trivial and I haven't figured out how to do it yet, though it may be possible. It may not be possible at all without hitting private APIs. But the other things you have listed should be possible. Hiding an application, for example, could be done by setting the kAXHiddenAttribute on the application accessibility element. Launching an application can actually be done via -[NSWorkspace launchApplication:].
Note that the use of accessibility necessitates that the user have Enable access for assistive devices turned on in System Preferences > Accessibility.

Sending requests to System Events from Objective-C/C?

Is there any way to convert the following applescript to Objective-C/C?
tell application "System Events" to set visible of process "Safari" to false
I know I could execute this applescript in Objective-C using the NSAppleScript class or calling system("osascript -e '...'"), however isn't there another way?
How does applescript do this?
Alternatively can I hide a window from another application from Objective-C/C?
Update:
I have found out that you can use SBApplication class to do this:
SBApplication *SystemEvents = [SBApplication applicationWithBundleIdentifier:#"com.apple.systemevents"];
/*SystemEventsApplicationProcess*/ id Safari = [[SystemEvents performSelector:#selector(applicationProcesses)] objectWithName:#"Safari"];
[Safari setVisible:NO]; // Doesn't work!
However this doesn't work as setVisible probably doesn't do what I think.
This is the class hierarchy of SystemEventsApplicationProcess:
SystemEventsApplicationProcess : SystemEventsProcess : SystemEventsUIElement : SystemEventsItem : SBObject : NSObject
And here are the methods available for these SystemEventsXXX classes:
SystemEventsApplicationProcess
applicationFile
SystemEventsProcess
setVisible:
visible
unixId
totalPartitionSize
shortName
partitionSpaceUsed
name
id
hasScriptingTerminology
setFrontmost:
frontmost
fileType
file
displayedName
creatorType
Classic
bundleIdentifier
backgroundOnly
architecture
acceptsRemoteEvents
acceptsHighLevelEvents
windows
menuBars
SystemEventsUIElement
select
clickAt:
setValue:
value
title
subrole
setSize:
size
setSelected:
selected
roleDescription
role
setPosition:
position
orientation
name
minimumValue
maximumValue
help
setFocused:
focused
entireContents
enabled
objectDescription
objectClass
accessibilityDescription
windows
valueIndicators
UIElements
toolBars
textFields
textAreas
tables
tabGroups
staticTexts
splitterGroups
splitters
sliders
sheets
scrollBars
scrollAreas
rows
relevanceIndicators
radioGroups
radioButtons
progressIndicators
popUpButtons
popOvers
outlines
menuItems
menuButtons
menuBarItems
menuBars
menus
lists
incrementors
images
growAreas
groups
drawers
comboBoxes
columns
colorWells
checkboxes
buttons
busyIndicators
browsers
attributes
actions
SystemEventsItem
setName:
name
id
removeActionFromUsingActionName:usingActionNumber:
pick
keyUp
keyDown
increment
editActionOfUsingActionName:usingActionNumber:
doScript
doFolderActionFolderActionCode:withItemList:withWindowSize:
decrement
confirm
cancel
attachedScripts
attachActionToUsing:
stop
start
saveAs:in:
moveTo:
exists
duplicateTo:withProperties:
delete
closeSaving:savingIn:
setProperties:
properties
objectClass
SBObject
// ...
NSObject
// ...
You can use NSRunningApplication, which represents (as its name implies) a running application, and has a -hide method.
NSWorkspace will give you a list of all the running apps: [[NSWorkspace sharedWorkspace] runningApplications], which you can filter, or you can get the object representing Safari using its bundle identifier: +[NSRunningApplication runningApplicationsWithBundleIdentifier:] (note that actually returns an array in case there are multiple running instances of the same app).
The code won't work unless you add the scripting bridge framework to your project and a couple other things. Have you done that... I can't tell. This link seems to have a good explanation of what is required if you need instructions.
By the way, "set visible" means hide the application just like if you hid it from the application menu. However if you want to hide an application I'm sure there's an NSWorkspace method.
Last bit of advice... for only a few lines of applescript code NSApplescript would be your best option. If you intend to use lots of applescript script code then the scripting bridge is the better choice, although I myself often just put a compiled script in my project and then use NSApplescript to initiate the handlers from that script. You can also use the ApplescriptObjC language too. You have lots of choices.

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.