Using Cocoa with AppleScript - macos

Having some trouble calling Cocoa methods from within AppleScript. For example, running the following snippet of code produces an error when ran using osascript:
set sharedWorkspace to call method "sharedWorkspace" of class "NSWorkspace"
Here's the thrown exception: Expected “,” but found identifier. (-2741) Should this code be nested under a tell statement? If so, what application should I be talking to?
Thanks.

call method looks like something out of the old AppleScript Studio, which was deprecated in 10.6 Snow Leopard and has since been removed.
There are a couple of prerequisites for calling Cocoa methods - a regular script needs to declare that it uses the desired framework(s), and the various classes and enums are defined at the application level and thus need to be prefaced with current application, or an object needs to exist to send the message to.
With that said, Cocoa methods can be called in a few different ways - using your snippet, for example:
use framework "Foundation"
set sharedWorkspace to current application's NSWorkspace's sharedWorkspace
-- or --
set sharedWorkspace to sharedWorkspace of current application's NSWorkSpace
-- or --
tell current application's NSSharedWorkspace's sharedWorkspace
set sharedWorkspace to it
end tell
The first form is what you will normally see used, as it is the closest to the Objective-C form. The appendix in the Mac Automation Scripting Guide has more information about translating from the Objective-C documentation, which is what Apple expects you to use.

This should work :
set sharedWorkspace to current application's NSWorkspace's sharedWorkspace()

Related

Does the window class name change in win32api

Does the window class name you specify in lpszClassName ever change? E. g. for applications like firefox? Or is that name always the same for an application? SO once I installed Firefox, I can be sure that its classname doesnt change?
Thanks!
The only way you can guarantee the class doesn't change is with some promise from the application developer. For example Winamp guaranteed its window class so you could use FindWindow on it for automation.
But there is nothing stopping an application from using a different class name for every window it ever creates.
Note that if you are trying to automate external applications, window class name is only one of countless moving-target problems. For example you get the window handle via FindWindow. You send it a WM_COMMAND for some command ID you found. Next version the application changes this command's ID. Same issue.

How to implment drag and drop feature on Rubymotion for a OSX app

Trying to whip up something quick and dirty that mass renames and converts files, and having a rubymotion license I thought why not use it to make a simple app that you can drag and drop instead of some batch file.
However I am having trouble detecting the drag event, can't seem to find information about how to do this in rubymotion, I've only used rubymotion to do iOS apps, and find myself lost.
Any help would be appreciated.
Have you started on any Cocoa tutorials? Anything written in Objective-C can be ported easily. I would start here: Drag and Drop Programming (developer.apple.com)
If you want to support dragging onto the dock icon, you'll need to modify the project's supported document types, and I'm not sure how to do this (check rake config, it might give a clue). You'll eventually need to implement this method, too:
def application(sender, openFile: path)
# sender is an NSApplication, path is NSString
true # or false
end
You need to add something like :
app.info_plist['CFBundleDocumentTypes'] = [
{'CFBundleTypeRole': 'Viewer', 'CFBundleTypeExtensions': ['mp4','m4v','avi','*'] } ,
{'CFBundleTypeRole': 'Editor', 'CFBundleTypeExtensions': ['txt'] }
]
to the setup block in the Rakefile
And add the following method to the AppDelegate class in app_delegate.rb
def application(sender, openFile: path)
true
end
Do a rake clean before you do rake build and you should be good to go.
Note that the application method gets called for each file that you drop onto the Application icon. path contains a string of the dropped files path.

How to test the bundle version string from within the app (ASObjC)

I'm trying to implement a "check for software updates" function in my ASObjC app.
I'm more or less there, except I'm not quite sure how to query the current version number in my info.plist from within my application's script.
Suppose I have the newest version number in an html file which I call using curl. I display a dialog prompting the user to update if the version number curl returns is higher than the version number of the calling script.
How do I get the bundles current version number into a property in the script to test against what's returned by curl?
I discovered the constant "CFBundleShortVersionString" but my attempts to use it have been thwarted.
This doesn't work, for example:
set myVersionNum to current application's CFBundleShortVersionString
I tried coercing to string and text to no avail.
EDIT: it appears the following works:
set myVersionNum to current application's version
The NSBundle class does have an method objectForInfoDictionaryKey: which is an method to get the value of the given key of the bundle's info.plist file. So when we use the application's main bundle (itself) then we could easily get the value with the following method:
set appBundle to current application's class "NSBundle"'s mainBundle()
set shortVersionString to appBundle's objectForInfoDictionaryKey:"CFBundleShortVersionString"
note: I use the new Mavericks (AppleScript 2.3) AppleScriptObjC syntax.

ApplescriptObjC call to NSMakePoint results in exc_bad_access error

This problem really puzzles me, since AppleScriptObjC should handle garbage collection just fine on its own.
Anyway, the steps to reproduce this error are:
Create a new Cocoa-AppleScript-Application project
Insert single call to NSMakePoint anywhere in the project's nameAppDelegate.applescript file. The file should now look something like this:
script testAppDelegate
property parent : class "NSObject"
on applicationWillFinishLaunching_(aNotification)
-- Insert code here to initialize your application before any files are opened
set single_point to NSMakePoint(5, 10) of current application
display alert "X-value of point: " & x of single_point
end applicationWillFinishLaunching_
on applicationShouldTerminate_(sender)
-- Insert code here to do any housekeeping before your application quits
display alert "Terminating"
return current application's NSTerminateNow
end applicationShouldTerminate_
end script
Run the application
The above application will launch and display the alert "X-value of point: 5.0" (as expected), but immediately after the alert it will crash with the following EXC_BAD_ACCESS error in the file main.m: main.m error
Thus, somewhere a call is being made to an object that no longer exists. If we profile the application with NSZombies enabled, it confirms that a zombie has been messaged and gives this additional info: zombie info
I've also tried the same thing with NSMakeRange and NSMakeRect instead, and it gives exactly the same result. I suspect every call to a Core Foundation function will crash the application this way. However, if we as an example call stringValue() of a NSTextField, it works just fine.
Putting the call outside of applicationWillFinishLaunching_ does not solve it either. So what am I doing wrong?
I'm using XCode 4.3.3 on OSX Lion 1.7.4
EDIT: After some further research, I realized I do not need to call NSMakePoint, I could just create an AppleScript record like {|x|:0, |y|:0} and it will work just like an NSPoint object. So that solves it, I guess.

Xcode 4.2 Template Changes - UIApplication & MainWindow.xib

Background: Up until Xcode 4.2, new projects created using any of the templates would contain a MainWindow.xib and therefore pass nil as the fourth argument of UIApplicationMain(). Starting in Xcode 4.2 all the templates instantiate the application delegate by passing the class string as the fourth argument and do not build the application's window in a xib.
It is trivial to accomplish this setup in 4.2, and of course it works as expected: create xib setting File's Owner to UIApplication and wire up the delegate, specify it in Info.plist, nil fourth argument in main().
Question: Why is Apple encouraging instantiating the application delegate and building the UIWindow in code now instead of the "old way?" What are the benefits?
Considerations: I would expect this new template behavior if you elect to use storyboarding as a way to manage the UI, but if you uncheck "Use Storyboards" I would have expected the old pass-nil-and-use-MainWindow.xib template.
This question was asked in a roundabout way here, but the answers are a little thin on discussion.
You're asking why Apple is doing something? There can be no definitive answer, unless Apple has spoken out explicitly, which they have not done.
Personally I find the new approach considerably more elegant, transparent, and bulletproof. As you rightly say, in the old approach the main nib was loaded automatically by the runtime in response to the Info.plist setting, and everything else that happened was done through the nib, in particular the instantiation of the app delegate and the window and the associated wiring (the app delegate must be made the application delegate, the window must be made the app delegate's window), except that then we come back to the code in the app delegate for final presentation of the interface.
This was hard to understand; it took a great deal of verbiage for me to describe it in my book. It was also easy to break. The nib has to know the name of the app delegate class, so if you didn't like those funny long names that were created by default, you could easily mess everything up when you changed them.
Now, however, the app delegate is simply named App Delegate and is instantiated in code by UIApplicationMain(), as you rightly say; and everything else is also done in code as a direct follow-on: the app delegate is instantiated and didFinishLaunching is called, whereupon we create the window in code, assign it to our property in code, load the nib if there is one in code, set the window's rootViewController in code, and show the interface in code as before.
Thus the bootstrapping is directly exposed to view because it's all code. This makes it easier to understand and to modify without breaking anything. It's almost as if previously the template designer was just showing off as to how much stuff could be made to happen magically and automatically behind the scenes; now everything happens out in the open, explicitly.

Resources