Provide an OSX Service Without Launching the App? - objective-c

I have successfully implemented a "faceless service" (background-only app with .service extension) and get it to work (see this question), based on Apple's documentation and other tutorials on the web.
Now, I want to advertise a service from an existing, single-window GUI app that I have.
I have setup the Info.plist file of my app to advertise the service, and it gets installed when I build the app.
But when I invoke the service from the context menu in (say) TextEdit.app (my service colours the selected text based on a certain criterion), my app gets launched, main window and everything. To make things worse, I am right-clicking on a TextEdit window that is in a secondary monitor, so my app's main window appears for an instant in the secondary monitor, then quickly repositions into the main monitor (this might have something to do with my window-centering logic, but nevermind...).
I would like to provide the service (i.e., have the class that provides the service in my app
instantiated and execute its method in response to the request), without my app appearing on the Dock or showing its window and main menu.
Is this possible? Safari advertises "Search With Google", so it should be possible...
EDIT: Now that I think about it, "Search With Google" must launch Safari every time in order to work, so this remark does not apply.
Perhaps I can put some logic in -applicationWillFinishLaunching/-applicationDidFinishLaunching to determine if the app is being launched in response to a service, and skip creating the window(notice the lack of withOptions: in OSX)?
But still, that doesn't feel right.

It does have a lame version of withOptions: -- NSApplicationLaunchIsDefaultLaunchKey tells you if your application was launched to either:
open or print a file, to perform a Service action, if the app had saved state that will be restored, or if the app launch was in some other sense not a default launch
So in your applicationDidFinishLaunching you can see if that key is in the notification and set to NO. Unfortunately, the main way to tell that it is one of the possibilities other than the Service, you have to detect and record whether or not you also got an application:openFile:, etc.

Related

Does Electron have a standard way of killing a useless renderer process?

My app creates a window with a local page that requires node integration to be enabled.
After I click a button on this page, I am navigated to a third party page.
Because I want node to be disabled in this third party page, and I can't toggle node integration in a BrowserWindow, I load this third party page in a sandboxed BrowserView that is embedded inside of the window and is stretched to fit the entire screen.
Now doing this navigates the embedded view, but the BrowserWindow is stuck pointing to the old local page that is no longer relevant.
To prevent this extra page from sitting around in the background, I navigate my BrowserWindow to "about:blank" to effectively clear it out and make room for the BrowserView.
I am realizing now that while this "clears" out the old page, it keeps the renderer process that's associated with it alive. From here:
Chromium creates a renderer process for each instance of a site the user visits
And understandably, navigating to "about:blank" doesn't signal to Electron that it should kill the other process.
I want to get rid of this renderer process, so it doesn't sit around unnecessarily and use CPU and memory when I interact with the window.
Two things that have worked:
I removed the extra navigation to "about:blank" since we're now killing the process and:
1) When my button in my renderer sends a message to the main process telling it to create a BrowserView and navigate to the new site, I do a process.exit();. I guess a part of me is nervous about the process exit interfering with the message that gets queued up for main, though it seems to work fine.
2) Instead of killing the process from the renderer, I created and navigated my BrowserView and then ran a little browserWindow.webContents.executeJavascript("process.exit()");. I find this uglier, though it does mitigate by concern above in #1.
There isn't a webcontents.destroy() type of method, and I don't know of a way to signal to Electron that it needs to destroy this unnecessary process.
I suppose I might have a pretty unique case, but is there a nicer way (or more standard way) of handling this than explicitly doing a process.exit()?
There is now a WebContents::forcefullyCrashRenderer() API that accomplishes this (introduced by this PR):
Forcefully terminates the renderer process that is currently hosting this webContents. This will cause the render-process-gone event to be emitted with the reason=killed || reason=crashed. Please note that some webContents share renderer processes and therefore calling this method may also crash the host process for other webContents as well.
As of now (July 2021, Electron v13) - there's also an undocumented webContents.destroy().
https://github.com/electron/electron/issues/10096
As the documentation mentions, some webContents share renderer processes and if you use webContents.forcefullyCrashRenderer() you may terminate them as well.
I'm not sure about how webContents.destroy() handles it, but from the name it seems to be more narrow in scope. I would assume that it kills the renderer if webContents is the only webContents attached to it (I tested this) and spares the renderer if other webContents is using it (needs confirmation).

How do I notify users of new content available in tvOS apps from the home screen?

Push notifications have been left out of tvOS (understandably so) but the docs seem to contradict themselves in alerting users to the fact that there is something new available in your tvOS app.
Here it seems to say that you can add an app badge: https://developer.apple.com/library/prerelease/tvos/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/WhatAreRemoteNotif.html
Here it says they've been removed from UIKit: https://developer.apple.com/library/prerelease/tvos/releasenotes/General/tvOS90APIDiffs/Objective-C/UIKit.html
Removed UIApplication.applicationIconBadgeNumber
Assuming the badge approach is not supported in this release, does anyone know the best practice for alerting a user that there is new content in your app without the user taking an explicit action? ie focusing on the app and showing them something in TopShelf?
I encountered the same problem and dived into this. Probably your best way is to update the topshelf with latest items, which is my way to solve this for now. You can use network calls to update the topshelf with content from your backend.
This depends on the type of application. E.g. showing the latest top movies for a movies app.
You can trigger an update of the topshelf after your network call completed using the following code:
NSNotificationCenter.defaultCenter().postNotificationName(TVTopShelfItemsDidChangeNotification, object: nil)
Make sure to implement the TVTopShelfProvider which should be clear using the following documentation:
This protocol is adopted by the principal class of an app’s TV Services extension. Apps that implement this extension can provide dynamic content to the Top Shelf element rather than having the system use the static image submitted with the app. The topShelfStyle property specifies the interface style you want, and the topShelfItems property specifies the content items to display. Whenever you change the content provided by the extension, post a TVTopShelfItemsDidChangeNotification notification to prompt the system to reload your content.
Icon badges are removed for app icons, push notifications as well (except for silent push notifications).

Long-running task performed in foreground is suspended when app enters background

When a user first opens my app, I need to download and install some content from a server before they can begin using the app. The problem is that this takes around 5 minutes on wifi, during which time the app goes into the background and the download is suspended.
Is there any way to either:
prevent an iOS app from entering the background whilst I perform my download
or continue peforming the task in the background (i.e. perform the task irrespective of whether the app is in the foreground or background)
Thanks
It really doesn't matter, if the user presses the home button it will go to background. Although you can do two things to mitigate the problem:
Use beginBackgroundTaskWithExpirationHandler, to give you a bit more time to download. Which you can read here.
Don't allow the device to become iddle, with [UIApplication sharedApplication].idleTimerDisabled = YES;. You can read more about that here.
Either way, the best thing you can do is to tell the user, that is an important download and he shouldn't quit the application.
Can't you include some or all of the content in your app bundle instead, and just download changes on first run?
I can't imagine this is a good first user experience, and it may not pass App Store review like this.
The only third party apps that are allowed to download in the background are newsstand apps loading issue content, and Apple are pretty strict about what they allow as newsstand apps.
You can't do what you want, in this situation. One way, and I think the best and only, is to resume your download when you app becomes active (returns to foreground state). Also, don't forget to register for connectivity notifications (Reachability class can be used for this purpose from this Apple sample app http://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html). Good Luck!

Know if the user launched an app

Alright, this title might seem strange, but bear with me. I have an app which can be set on its preferences by the user to launch at login. That means I can expect sometimes the app will be launched by the user (clicking on the Dock/Finder, etc), but some other times the app will be launched automatically by the system, on login.
I would like to show a window when the app is launched by the user, but not when it is launched automatically (as I imagine that would be a pain for the user). How can I do that?
Although it may depend on how you intend to automate the launch of the app, you could use command line arguments to distinguish between system launch vs. user launch.
So, the command line launch might like like this:
MyApp -autoLaunch "Y"
To parse the command line args in a Cocoa app, you could use NSUserDefaults (Yes, you can!):
if( ![[NSUserDefaults standardUserDefaults] objectForKey:#"autoLaunch"] isEqualToString:"Y"] ) {
// do something for user initiated launch
}
I don't have an exact answer to your question. However, may I suggest that your app should show the window if the window was visible when the user last quit your app?
This may be more in-line with the Mac UI guidelines' suggestion on restoring apps and windows, and is within the user's expectations.
Also, a user who set your app to launch at login will probably understand to close the window and not have it restored the next time, or make the system also hide your app during login.

How to add login Items by code to mountain lion osx

I want to add login items programmatically in Mountain Lion (10.8).
Until now I was able to add login items by editing this plist:
/Users/test/Library/Preferences/loginwindow.plist
and adding items (path,name,hide) to AutoLaunchedApplicationDictionary dictionary
in the OS doesn't work anymore. Items that are added to this dictionary are not launched on login. I see that the login items are saved in a file called: com.apple.loginitems.plist
but I don't understand how to add an item to this file. I tried to add the item to CustomListItems dictionary with parameters like name,path, hide but they were not launched on login.
Does anyone know how can I add from code login item?
I understand you want to start your program automatically when your user logs in.
In older versions of OS X, it was possible to add login items manually by editing loginwindow.plist. Apple deprecated this approach when they added LaunchAgent and LaunchDaemon functionality to the OS.
Since you are using Mountain Lion, the correct way to have a program launch is to create a launchagent for it. This is a .plist file that you can use to tell OS X to a) perform some action (e.g.: launch /some/program.app) when b) a specific event occurs (e.g.: logging in, logging out, etc)
You will find Apple's official document on creation of LaunchAgents over here.
This looks like a great tutorial on the modern way of doing things: The launch at login sandbox project
It starts with a paragraph buried in the App Sandbox design guide:
Creating a Login Item for Your App
To create a login item for your sandboxed app, use the SMLoginItemSetEnabled function (declared in ServiceManagement/SMLoginItem.h) as described in “Adding Login Items Using the Service Management Framework” in Daemons and Services Programming Guide.
(With App Sandbox, you cannot create a login item using functions in the LSSharedFileList.h header file. For example, you cannot use the function LSSharedFileListInsertItemURL. Nor can you manipulate the state of launch services, such as by using the function LSRegisterURL.)
And rolls from there...