osx and objc - running a second run loop for a plugin? - macos

C programmer here. I'm trying to make a plugin to a running OSX app open its own window, accept a button press, close the window and come back to the plugin - not quit the app.
I've been able to get a window open using NSApp, and I've even got a button in it, but it won't DO anything.
[button setTarget: nil];
[button setAction: #selector(fauxAction:)];
Is how I set it up. Normally, you [button setTarget: self], BUT, this is inside a normal c function, and there is no "self." I don't know how to call an objc method from c, if that's the problem I need to solve. I just want the method fauxAction to be called when the button is pressed.
This is the plugin's window -- not the main application. I can't have them crossing wires. [NSApp run] and putting the quit menu on it quits the main application, I guess because the run loop wraps the executing thread.
Any help would be appreciated. I feel like I'm drowning here.

I got it - NSApp runModalForWindow. That runs just the window loop, leaves the main loop unmolested.

Related

Open Spotify main window from another app when closed

Iam working on an application that tries to extend some Spotify functionality (not another client). I would like to show Spotify's main window when my icon is clicked on the dock - even if the main window in Spotify is closed.
This is my code now:
- (void) applicationDidBecomeActive:(NSNotification *)notification {
// Causes Spotify to hit the front when selecting it!
[[[NSRunningApplication
runningApplicationsWithBundleIdentifier:#"com.spotify.client"] lastObject]
activateWithOptions:NSApplicationActivateAllWindows];
}
It works when the window is open but not in focus (background), but not when I close the Spotify main window (which I people tend to do). Is there any way to re-open this window if it's closed from another application?
The Spotify icon can do this (obviously). Try to hit the close button (the red x) and press the icon (it will reappear). Is that possible from another app?
The Dock sends the reopen Apple Event to the application you clicked on, which then executes code as it sees fit. To simulate a Dock click, you need to send the reopen event to the application manually.
The Apple Documentation on the matter states that the reopen event has the ID of rapp code (kAEReopenApplication) and is part of the kCoreEventClass class.
From there, it's relatively simple to construct the Apple Event in code and send it off to the application. Note that you really should do some error checking from the AESendMessage call, as the event will likely fail if the application isn't launched, etc — I haven't really tested those cases.
Here is my solution. Note that you'll need to keep your original code to bring the application to the front — the Apple Event doesn't change application order.
NSAppleEventDescriptor *target = [[NSAppleEventDescriptor alloc]
initWithDescriptorType:typeApplicationBundleID
data:[#"com.spotify.client" dataUsingEncoding:NSUTF8StringEncoding]];
NSAppleEventDescriptor *event = [[NSAppleEventDescriptor alloc]
initWithEventClass:kCoreEventClass
eventID:kAEReopenApplication
targetDescriptor:target
returnID:kAutoGenerateReturnID
transactionID:kAnyTransactionID];
AESendMessage(event.aeDesc, NULL, kAEWaitReply | kAENeverInteract, kAEDefaultTimeout);

Quitting an app using the Red X button?

I've made my first Mac app and it only has one window and I want it to quit when your press the red button. This button closes the window as opposed to quitting the app at the moment and I cant seem to find a way to edit the behaviour of this button any tips? Note this is in Xcode 4 and is an Applescript based app.
I don't know AppleScriptObjC but here's how you do it in objective-c. I'm sure you can easily convert these.
Basically you don't have to modify anything. Just put this method in your app delegate. It is called automatically by the app when the last window is closed because it's an NSApplication Delegate method. There's many other NSApplication Delegate methods so you should look at those methods to see what else you can do. Good luck.
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
return YES;
}
By the way, alternative methods would have been to use NSWindowDelegate methods. You just need to set your AppDelegate (or any other class) as the window's delegate with...
[[self window] setDelegate:self];
Specifically you could have used either of these NSWindowDelegate methods to modify the behavior of the red close button. For example you could issue the terminate: method of NSApp in there to do as you asked but you can do anything in them.
- (BOOL)windowShouldClose:(id)sender
- (void)windowWillClose:(NSNotification *)notification
But applicationShouldTerminateAfterLastWindowClosed: is the one you want. I just want to show there's lots of ways to accomplish tasks.
Congrats on your first app!
The answer provided by #regulus6633 did NOT work for me. The Xcode build failed.
This is because, like the OP, my Xcode app is a "Cocoa-AppleScript" app, and the AppDelegate is written in AppleScript.
So, the solution is:
script AppDelegate
--- YOUR OTHER CODE ---
--- QUIT APP WHEN WINDOW IS CLOSED ---
on applicationShouldTerminateAfterLastWindowClosed_(sender)
return true
end applicationShouldTerminateAfterLastWindowClosed_
end script
I put this handler at the end of the script block, but it probably doesn't matter.
I found this solution at http://www.macscripter.net/viewtopic.php?pid=117933#p117933

- (NSWindow *)window doesn't work to show a window while loadWindow does

I am trying to open a nib file called EasyWindow.xib when a button it pressed in my MainMenu.xib. I have the button connected to this IBAction method, but when I click on the button it doesn't open. When I change the "window" in this init part to "loadWindow" it works perfectly fine, but the Mac Developer Library say "You should never directly invoke this method." How do I make the window method work?
- (IBAction)loadEasyWindow:(id)sender
{
[[[NSWindowController alloc] initWithWindowNibName:#"EasyWindow"] window];
}
- (NSWindow *)window only gives you a reference to the actual window object that the NSWindowController manages. It doesn't actually do anything with that window. It's more of a getter than anything else.
Meanwhile, -loadWindow is a method that's called when your program actually loads the window from the nib file and has little to do with opening it, closing it, and showing it. That's why you shouldn't call it. I'm guessing it works because a side effect is the window showing itself.
What you are looking for is probably NSWindowController's - (IBAction)showWindow:(id)sender. [reference] It should do exactly what you want:
Displays the window associated with the receiver

Forcing an LSUIElement "Accessory" application to the front

I have an LSUIElement application that needs to keep it's status as an LSUIElement (it cannot have a dock icon) this application is launched and handled by a plugin.
When I change the app so it is a "Regular" app (without LSUIElement), [NSApp activateIgnoringOtherApps:YES] works perfectly. However when I make it an LSUIElement it shows the window, but the window is stuck behind safari (where the plugin is running) but on top of everything else. I am calling -[NSWindow makeKeyAndOrderFront:self]; but that doesn't make any difference.
To be clear the "plugin" side is irrelevant as the plugin and application are two separate processes. The plugin sends an event (over a mach port) to the application which calls some code to open the window and bring it to focus. The problem is the code to bring it to focus only works when it's not an LSUIElement application.
I've exhaustively searched for an existing call to make this work, I'm open to suggestions on how to fake a mouse click, or even run some applescript to make this work, maybe there's an undocumented method of doing this.
The Apple docs for LSUIElement state "The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically" but I cannot find the magic that enables this.
You need to activate your app first:
[NSApp activateIgnoringOtherApps:YES];
and then make your window become key window:
[NSWindow makeKeyAndOrderFront:nil];
This works in my project.
The following approach works for me in an app with "LSUIElement" set to "1", even if the entire application is using Cocoa:
ProcessSerialNumber psn;
if (noErr == GetCurrentProcess(&psn))
{
(OSStatus)SetFrontProcess(&psn);
}
(This is part of the Carbon API.)

NSApplicationDelegate prevent window if command line present

I have a simple single window app, with a drop target on it for files. This works fine. However, I also want that when it is started with a command line, it just processes those files instead, rather than showing a window.
Is there a delegate method in NSApplicationDelegate that I can prevent the window from showing, process the files and quit the application in?
You can set the window not to show when loaded from the nib file. There's a setting in the inspector in the Interface builder.
Then you can show the window if necessary, using methods described in this documentation.
But this will still show the icon in the Dock while your app is processing the file.
You can prevent showing the main window like Yuji said. There's also a notification called applicationDidFinishLaunching:
There you can process your files and quit the application with [[NSApplication sharedApplication] terminate:self];
The most maintainable and simple approach I've seen to this is to put your commandline version in a separate executable as a simple Foundation program, and then make your Cocoa program just call it, handling the UI. Doing it this way gets rid of many of the things you'd need to work around. You can then install links to your commandline version where you like.
If you dislike using NSTask for this, then put your logic into a shared framework, and layer GUI and commandline apps on top of it.

Resources