Replying to an Apple Event in Cocoa - macos

I'm registering an Apple Event handler using NSAppleEventManager:
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self andSelector:#selector(handleGetURLEvent:withReplyEvent:)
forEventClass:kInternetEventClass andEventID:kAEGetURL];
My handler method will, of course, receive the event and a reply event:
- (void) handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
//Open this URL; reply if we can't
}
So, if I need to reply with an error, indicating that I failed in some way to open this URL, how should I use the replyEvent to do that?

I have translated the following from the old-style C procedural API described in Apple's legacy document "Apple Events Programming Guide" to Cocoa:
if ([replyEvent descriptorType] != typeNull)
{
[replyEvent setParamDescriptor:[NSAppleEventDescriptor descriptorWithInt32:someStatusCode] forKeyword:keyErrorNumber];
[replyEvent setParamDescriptor:[NSAppleEventDescriptor descriptorWithString:someErrorString] forKeyword:keyErrorString];
}
See "Returning Error Information" in the "Apple Events Programming Guide" (in the Legacy library).

Related

Links in iframe are not triggering decidePolicyForNewWindowAction

I have a iframe of youtube player in my webview macos application, and most of the links (the <a> element) inside the iframe are not triggering the decidePolicyForNewWindowAction delegate.
The only working <a> element is the channel link, others like video title, the youtube icon are all silent, and I can't tell the differences between these <a>s.
So why are some links cannot trigger decidePolicyForNewWindowAction?
Documentation of the delegate: https://developer.apple.com/documentation/webkit/webpolicydelegate/1536381-webview?language=objc
Documentation of the iframe youtube player: https://developers.google.com/youtube/iframe_api_reference
For someone maybe interested:
Turns out it's not a iframe problem, it's because some <a> are not directly changing the url, it calls some javascript to do the job for it. This was inspected from the callstack.
In such case, the first thing is that one more delegate needs to be hooked up, the createWebViewWithRequest. This delegate returns nil by default. So change it like this:
- (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request
{
return sender;
}
Not doing much but let the following codes continue.
The second thing is that the javascript url request goes to decidePolicyForNavigationAction instead. So still not firing decidePolicyForNewWindowAction but at least there's a chance to know it.
In my case I add some condition inside decidePolicyForNavigationAction to distinguish my request and iframe's request, like this:
- (void) webView: (WebView*) sender decidePolicyForNavigationAction: (NSDictionary*) actionInformation
request: (NSURLRequest*) request
frame: (WebFrame*) frame
decisionListener: (id <WebPolicyDecisionListener>) listener
{
NSString* mainDocumentURL = [[request mainDocumentURL] absoluteString];
NSString* fromYT = #"https://www.youtube.com";
if ([mainDocumentURL hasPrefix:fromYT]) {
[[NSWorkspace sharedWorkspace] openURL:url]; // fires Safari
[listener ignore];
return;
}
// normal flow here, you could open it inside the webview or something
}

Catch ALL quit events with NSAppleEventManager

When my Mac OS app quits I want it to ask the user "Are you sure you want to quit [Yes] [No]".
I have tried this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
// Install a custom quit event handler
NSAppleEventManager* appleEventManager = [NSAppleEventManager sharedAppleEventManager];
[appleEventManager setEventHandler:self andSelector:#selector(handleQuitEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEQuitApplication];
}
// Handler for the quit apple event
- (void)handleQuitEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent {
// Insert YES/NO-dialog here
// if(blahahaha..
//[NSApp terminate:self];
}
But it only catch the quit that comes when right-clicking on my app on the dock and then choosing "Quit". If I press Cmd-Q or choose Quit from the app menu my handler is not invoked...
Not all these cases involve Apple Events. Instead, handle the app delegate method -applicationShouldTerminate:.

-[NSResponder swipeWithEvent:] not called

I am writing an application targeting OS X Lion and Snow Leopard. I have a view that I want to have respond to swipe events. My understanding is that three-finger swipes will call -[NSResponder swipeWithEvent:] if that method is implemented in my custom view. I have already looked at this question and corresponding answers, and tried the following modified stub implementation of Oscar Del Ben's code:
#implementation TestView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] set];
NSRectFillUsingOperation(dirtyRect, NSCompositeSourceOver);
}
- (void)swipeWithEvent:(NSEvent *)event {
NSLog(#"Swipe event detected!");
}
- (void)beginGestureWithEvent:(NSEvent *)event {
NSLog(#"Gesture detected!");
}
- (void)endGestureWithEvent:(NSEvent *)event {
NSLog(#"Gesture end detected!");
}
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"mouseDown event detected!");
}
#end
This compiles and runs fine, and the view renders as expected. The mouseDown: event is properly registered. However, none of the other events are triggered. Neither the begin/endGestureWithEvent: methods, nor the swipeWithEvent: method. Which makes me wonder: do I need to set a project/application setting somewhere to properly receive and/or interpret gestures? Thanks in advance for the help.
To receive swipeWithEvent: messages, you have to ensure that the 3 finger swipe gesture is not mapped to anything that might cause a conflict. Go to System preferences -> Trackpad -> More Gestures, and set these preferences to one of the following:
Swipe between pages:
Swipe with two or three fingers, or
Swipe with three fingers
Swipe between full-screen apps:
Swipe left or right with four fingers
Specifically, the swipe between full-screen apps should not be set to three fingers, otherwise you will not get swipeWithEvent: messages.
Together, these two preference settings cause swipeWithEvent: messages to be sent to the first responder.
Of course, you still have to implement the actual swipe logic. And if you want to perform a fluid scroll-swipe à la iOS, then you will need to do a little more work. There is an example of how to do this in the Lion App Kit release notes under the section "Fluid Swipe Tracking."
See http://developer.apple.com/library/mac/#releasenotes/Cocoa/AppKit.html
try with [self setAcceptsTouchEvents:YES]; where it says // Initialization code here.
Not sure if it's the problem, but only the key window receives Gestures. Is your window key?
Is your view accepting first responders?
- (BOOL) acceptsFirstResponder
{
return YES;
}

How can I track opening and closing event of NSWindow?

I did try – windowDidExpose: but it didn't work. What do I have to try for this?
My window is a utility window.
-- edit for more clarity --
What I want are:
viewWillAppear
viewWillDisappear
viewDidLoad
viewDidUnload
in Cocoa Touch.
Very old question, but only for documentation purpose:
Track open:
In your windows controller override the method:
-(void)showWindow:(id)sender
{
//add this for track the window close
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(windowWillClose)
name:NSWindowWillCloseNotification
object:nil];
[super showWindow:sender];
//do here what you want...
}
Track close:
Implement the method
-(void)windowWillClose
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
//do here what you want...
}
There is windowDidClose:, but that probably only refers to closing; if you're sending your window an orderOut: message, I don't think that counts.
You probably need to either just track it from whatever code you're ordering the window in and out from, or subclass the window's class and override methods like makeKeyAndOrderFront: and orderOut: (whatever you're using, at least) to post custom notifications before calling up to super.
For Swift
Track open: In your windows controller override the method:
override func showWindow(sender: AnyObject?) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(windowWillClose), name: NSWindowWillCloseNotification, object: nil)
}
Track close: Implement the method:
func windowWillClose() -> Void {
NSNotificationCenter.defaultCenter().removeObserver(self);
//Do here what you want..
}
I came up with a hack for dealing with this. There is no notification that signals that a window has been put on screen, but there's a notification that's pretty much guaranteed to be sent when a window is put on screen. I'm speaking of NSWindowDidUpdateNotification, which indicates that a window has refreshed itself.
Of course, it's not only sent when the window appears—it's sent every time the window updates. Needless to say, this notification is sent a lot more than once. So you want to watch for it the first time, do your thing, and ignore any subsequent notifications. In my case, I wanted to add a sheet to a window that another part of my app would order in later. So I did something like this:
__block id observer = [NSNotificationCenter.defaultCenter addObserverForName:NSWindowDidUpdateNotification object:window queue:nil usingBlock:^(NSNotification *note) {
[self showSetupSheet];
[NSNotificationCenter.defaultCenter removeObserver:observer];
}];
There's no particular reason you would have to use a block-based observer—a method-based observer would work just as well.

How to handle with a default URL scheme

I want to build URI (or URL scheme) support in my app.
I do a LSSetDefaultHandlerForURLScheme() in my + (void)initialize and I setted the specific URL schemes also in my info.plist. So I have URL schemes without Apple Script or Apple Events.
When I call myScheme: in my favorite browser the system activates my app.
The problem is, how to handle the schemes when they are called. Or better said: How can I define what my app should do, when myScheme: is called.
Is there a special method that I have to implement or do I have to register one somewhere?
As you are mentioning AppleScript, I suppose you are working on Mac OS X.
A simple way to register and use a custom URL scheme is to define the scheme in your .plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>URLHandlerTestApp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>urlHandlerTestApp</string>
</array>
</dict>
</array>
To register the scheme, put this in your AppDelegate's initialization:
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self
andSelector:#selector(handleURLEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
Whenever your application gets activated via URL scheme, the defined selector gets called.
A stub for the event-handling method, that shows how to get the URL string:
- (void)handleURLEvent:(NSAppleEventDescriptor*)event
withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
NSString* url = [[event paramDescriptorForKeyword:keyDirectObject]
stringValue];
NSLog(#"%#", url);
}
Apple's documentation: Installing a Get URL Handler
Update
I just noticed a problem for sandboxed apps that install the event handler in applicationDidFinishLaunching:. With enabled sandboxing, the handler method doesn't get called when the app is launched by clicking a URL that uses the custom scheme.
By installing the handler a bit earlier, in applicationWillFinishLaunching:, the method gets called as expected:
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
[[NSAppleEventManager sharedAppleEventManager]
setEventHandler:self
andSelector:#selector(handleURLEvent:withReplyEvent:)
forEventClass:kInternetEventClass
andEventID:kAEGetURL];
}
- (void)handleURLEvent:(NSAppleEventDescriptor*)event
withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
NSString* url = [[event paramDescriptorForKeyword:keyDirectObject]
stringValue];
NSLog(#"%#", url);
}
On the iPhone, the easiest way to handle URL-scheme activation is, to implement UIApplicationDelegate's application:handleOpenURL: - Documentation
All credits should go to weichsel and kch
I'm just adding swift(2.2/3.0) code for your convenience
func applicationWillFinishLaunching(_ notification: Notification) {
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleGetURL(event:reply:)), forEventClass: UInt32(kInternetEventClass), andEventID: UInt32(kAEGetURL) )
}
#objc func handleGetURL(event: NSAppleEventDescriptor, reply:NSAppleEventDescriptor) {
if let urlString = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue {
print("got urlString \(urlString)")
}
}
The problem is, how to handle the schemes when they are called.
That's where the Apple Events come in. When Launch Services wants your app to open a URL, it sends your app a kInternetEventClass/kAEGetURL event.
The Cocoa Scripting Guide uses this very task as an example of installing an event handler.
I'm just adding slightly different Swift 4/5 version of the code:
func applicationWillFinishLaunching(_ notification: Notification) {
NSAppleEventManager
.shared()
.setEventHandler(
self,
andSelector: #selector(handleURL(event:reply:)),
forEventClass: AEEventClass(kInternetEventClass),
andEventID: AEEventID(kAEGetURL)
)
}
#objc func handleURL(event: NSAppleEventDescriptor, reply: NSAppleEventDescriptor) {
if let path = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue?.removingPercentEncoding {
NSLog("Opened URL: \(path)")
}
}
You can define the “get URL” command in a scripting terminology SDEF and implement the corresponding method. For example, Terminal’s SDEF contains the following command definition for handling URLs
<command name="get URL" code="GURLGURL" description="Open a command an ssh, telnet, or x-man-page URL." hidden="yes">
<direct-parameter type="text" description="The URL to open." />
</command>
and declares that the application responds to it:
<class name="application" code="capp" description="The application's top-level scripting object.">
<cocoa class="TTApplication"/>
<responds-to command="get URL">
<cocoa method="handleGetURLScriptCommand:" />
</responds-to>
</class>
The TTApplication class (a subclass of NSApplication) defines the method:
- (void)handleGetURLScriptCommand:(NSScriptCommand *)command { … }

Resources