Sending requests to System Events from Objective-C/C? - objective-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.

Related

Filter [NSWorkspace runningApplications] to contain only user applications (no daemons/UIAgents)

Is there a way to filter the list of applications given by [NSWorkspace runningApplications] to hide all daemons, etc short of manually checking each application's plist for the UIAgent key? If an application doesn't show in the dock, I'm not interested in it.
UPDATE: If you’re using Objective-C, my original answer below still applies, but if you’re using Swift this can very easily be performed more cleanly, thanks to Swift’s filter function for collection types.
let workspace = NSWorkspace.sharedWorkspace()
let apps = workspace.runningApplications.filter { (app) -> Bool in
return app.activationPolicy == .Regular
};
In Objective-C something similar can be done with NSArray’s various predicate-based and enumeration methods, but they’ll be a little more long-winded than their Swift counterpart.
I found the answer after some searching, but it's something that might not be immediately obvious.
An easy way to only grab processes which have icons in the Dock is by doing a simple fast enumeration loop and checking each NSRunningApplication's activationPolicy, like so:
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
NSArray *apps = [workspace runningApplications];
for (NSRunningApplication *a in apps) {
if (a.activationPolicy == NSApplicationActivationPolicyRegular) {
// Do stuff here
}
}
Typically, applications with normal windows and dock icons use NSApplicationActivationPolicyRegular. Menu extras and Alfred-type applications use NSApplicationActivationPolicyAccessory. Daemons, etc with no user visibility whatsoever use NSApplicationActivationPolicyProhibited. These constants correspond with the LSUIElement and LSBackgroundOnly keys in each application's Info.plist.
This approach should catch applications which have settings that allow the user to toggle the presence of the application's dock icon through setting their activationPolicy dynamically.

Show icons in the dock contextual menus in OS X?

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.

Macos x Shortcuts of a Application

I need to read all Shortcuts of the frontmost Application in MAC OS. Is there a API or Class in Cocoa,Objective-c who provides this?
The Accessibility API is what you need. Unfortunately, it is rather convoluted, and it would be good to read through the UIElementInspector source code to see how it's used, as well as the sections relevant to assistive applications in the Accessibility Programming Guidelines for Mac.
What you want would take a good bit of code so I will just outline the steps.
Use [[NSWorkspace sharedWorkspace] runningApplications] to get a list of applications and get the application whose active property is YES.
Get the PID of that application using the NSRunningApplication's processIdentifier property.
Now we get to Accessibility, accessed through the Application Services API
Create an AXUIElement representing the active Application using AXUIElementCreateApplication, which takes pid as the argument.
Now you have an AXUIElement with the Application role, or AXApplication pseudo-class. You'll want to proceed down the hierarch of elements AXApplication -> AXMenuBar -> AXMenuBarItem -> AXMenuItem. Note that AXMenuItems can have other AXMenuItems nested under them.
To traverse the hierarchy, use AXUIElementCopyAttributeValues to get the values of kAXChildrenAttribute. This will return an array of children AXUIElements.
Finally, when you get to AXMenuItem elements, examine their Menu Item Cmd Char, Glyph, Modifiers, and Virtual Key attributes for the actual shortcuts. The constants for the attribute names, like kAXMenuItemCmdCharAttribute, are listed here.
Not trying to compete #woody :)
But I found this, and it might help too:
UI Browser

Localizing the Cut|Copy|Paste menu on iOS

Im having some issues localizing a danish app ive made. (The language, not the pastry)
I have set the CFBundleDevelopmentRegion to da_DK for danish in my info.plist, but the popup appearing for text input is still in english, even on phones running the danish OS.
How in Jobs name can i change this ?
The test device is a non-jailbroken iPhone 4S running iOS 5.1 with Danish as its iOS setting, and a danish itunes account associated.
I do not use .xibs for designs. all interfaces are programmed as viewcontrollers.
In the Xcode's file tree (Project Navigator) select your project. in the right hand pane select your project again. select Info and add your language.
I created a sample project, this is the result:
You can do this directly in the info.plist. Something like this:
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>de</string>
<string>es</string>
<string>ja</string>
</array>
Try adding/setting the "Localized resources can be mixed" flag in Info.plist to YES.
You must localize your app in Danish to make the standard UI elements appear in that language. This is to avoid having a UI with mixed languages.
If you don't use xibs, you'd usually do this by adding a Localizable.strings file to your project. In Xcode's "Add File" dialog, you can use the "Strings File" template (under "Resources") for this.
To actually localize the strings file, open the file inspector (⌘ ⌥ 1) and click the + button in the "Localization" section. You'll end up with the file being displayed as a group in the project navigator, with a sub-entry for each language.
The strings file has the format:
"Label_Text" = "Smørrebrød";
(don't forget the semicolon)
To use localized strings in your code, you can use the NSLocalizedString macro like this:
myLabel.text = NSLocalizedString(#"Label_Text", nil);
(The second parameter is for a comment. This can be useful if you use the genstrings tool to extract localizable strings from your code and give the resulting file to a professional translator.)
If you use the English strings as keys, you can leave the English version of Localizable.strings empty (but don't delete it).
Having a Localizable.strings file in the language that the user has selected will also cause standard UI elements, such as the editing menu, photo picker, and so forth, to appear in that language.
If you can't get it working the official way, as provided by #vikingosegundo, you can do this with some creative engineering (Creative as in, oh my god that is dangerous). I discovered this method when I accidentally overrode [NSBundle localizedStringForKey:value:tableName:].
1) Add a category to NSBundle with the following methods:
#import <objc/runtime.h>
+ (void) load {
Method original, swizzled;
original = class_getInstanceMethod(self, #selector(localizedStringForKey:value:table:));
swizzled = class_getInstanceMethod(self, #selector(swizzled_localizedStringForKey:value:table:));
method_exchangeImplementations(original, swizzled);
}
- (NSString*) swizzled_localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName {
NSLog(#"Key: %#. Value: %#", key, value);
return [self swizzled_localizedStringForKey: key value:value table:tableName];
}
2) Where I simply log the key/value, you want to put an if ([key isEqualToString: xxx] ) block. In there, you want to catch (at least some of) the following key values: Cut, Copy[Menu], Select, Select All, Paste, Delete[Menu], Replace..., Define, Speak, Pause. These are the default values that can appear there.
3) When you have caught the value you can look up in a custom table or use hardcoded values. If you look up in a custom table make sure you have a catch in your swizzled method to avoid infinite looping in your custom table.
NB: Why do you need to swizzle? Because this over-rides all Apple text for you app. You will still want the defaults for all the other strings, so you need to swizzle to get the defaults for the strings you aren't interested in.
Good luck.
Paul
Search if your .xib is localized (you'll find it in the inspector on the right panel) if so go to your Project/Target-Settings press the +-Sign and select "Duplicate English to Danish" or something which means the same (I can't check the right item at the moment)
Btw it's called iPhone 4S.

Opening a file using ScriptingBridge

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)