Idle Hands

A common user annoyance is having alerts popping up at bad times. The worst is when the user is typing and focus gets stolen only to have subsequent key presses going to the new window, possibly resulting in the user inadvertently confirming something they didn’t want to. Peter Hosey discusses this particular case and outlines an NSAlert-based solution.

I recently submitted a patch for Sparkle+ to deal with a similar situation. It can be annoying to get an alert about a new version when you are working. A new version is not a “drop everything and deal with this now” type of alert. With this patch, when a new update is found, it will check the user’s idle time and hold off showing the panel until a certain amount of time has elapsed. This minimizes the chance of the user being in the middle of something when the alert comes up. Of course, this is only suitable for when the alert itself is not terribly critical or time-sensitive. It should not be used if it is in response to direct user action or if the alert is something that needs to be dealt with immediately.

Since this type of thing may be useful in other contexts, I’ve decided to generalize it and put it out there for your consumption. It’s a little NSObject category with two new methods: performSelector:withObject:afterSystemIdleTime: and performSelector:withObject:afterSystemIdleTime:withinTimeLimit:. The first will call the given method on the receiver when the user has been idle for the given period of time. The second method does the same but allows you to set a limit after which it will call the method regardless of idle time, thus preventing the method from being delayed indefinitely.

Beyond the use described above, you can use it in several other situations, such as doing some internal maintenance that may get in the way if the user is actively using the machine. You can reclaim/free memory, clear out caches, compact file stores, optimize data structures or whatever.

For the time being, I am using this in my Sparkle update alerts and my scheduled evaluation period expiration nags. What will be interesting is if this leads to some sort of “refrigerator light syndrome” where users notice that it only happens when they are not looking and are somehow bothered by it. Most likely, though, users probably won’t notice and, with any luck, they will be more receptive to the alerts when they do pop up. If that leads to an extra spring in their step, then I consider it a job well done.

The project is linked below. Make sure you read the Read Me file. If you end up using it, let me know.

Update (Jan 10, 2008): The original project had a couple files specified as absolute paths (so XCode wouldn’t find them). A new project has been put up with this error fixed. It is marked as version 0.6 in the package name and Read Me file.

performwhenidletest-0.6.zip

Update (Feb 5, 2008): It appears that the CGEventSourceSecondsSinceLastEventType() function hangs on Tiger systems. I have updated the code to check for the OS version and only do the idle delay on Leopard and later.

performwhenidletest-0.7.zip

I am also looking into patching this in Sparkle+. If you are using Sparkle+ with this feature enabled, drop me a line or wait for the patch which will hopefully happen soon.

Category: Cocoa, OS X, Programming 12 comments »

12 Responses to “Idle Hands”

  1. clint

    Awesome idea. I noticed this interruption twice in the past two days, with both the Adium update and the transmission update. Would much rather that pop up at an idle time, thanks

  2. mr_noodle

    Oh, one thing I forgot to mention was that it’s not enabled in Sparkle by default. The app developer will have to enable it via the SUDelayAlertUntilSystemIdle key in their Info.plist. So, you may want to bug them about that. Though, thinking about it, I guess you could just go and set that key yourself (as long as they have the latest Sparkle+ framework in their app) though you do this at your own risk.

  3. MC

    You might want to consider following the approach used in Firefox for important popups, like the alert asking about installing an extension. (Someone blogged about it a while back.) Basically they disable the controls on the window and pop it up, and only re-enable the controls after a few seconds delay. That way the user sees it, the popup doesn’t eat any unintended keypresses, and they get a chance to react safely.

  4. mr_noodle

    Thanks for the info on how Firefox does it. That’s a bit more like the solution Peter Hosey proposed for Adium (see link in article). In that case, the alert is brought up front but not made key, meaning it won’t accept key presses until you click on the window.

    In the case I’m describing, it is not critical to have the alert come up immediately and having any sort of alert/dock icon bounce would be a distraction. In the cases I’m using it for now, it’s for alerts that are the result of some timer so from the user’s perspective, they can pop up at any time. The alert happens asynchronous to their flow of events so it can be disruptive if it happens while they are actively doing something.

    But yes, for more critical/immediate alerts, something like what you or Peter described would be more appropriate.

  5. Scrod

    Is there something I’m missing, or has no one actually read the Aqua Human Interface Guidelines where it discusses apps that check for updates?

    A stated advantage of Apple’s recommendation is that “users will appreciate receiving an update notification consistently and immediately at launch time, rather than at random times during their work.”

  6. Alexander Rauchfuss

    @Scrod:
    That is what Sparkle does by default. The problem being that certain apps like Adium are rarely launched. It would be entirely possible for a user to be unaware of an update for quite awhile. To combat this Sparkle can be set up to do periodic checks.

  7. Anon

    Scrod, the thing you’re missing is that the HIG are guidelines, not rules, and aren’t always appropriate. In this case, I would think that an “Open at Login” app like Hazel is an exception — you never really “look it in the face” and so for it to pop up an update notification at any time, including login, it is likely to feel like an interruption. Notifying on idle avoids that.

    I think it’s a good compromise, and a nice patch.

  8. Karl von L.

    Scrod: That statement in Apple’s HIG is a lie. At least, I as a user *do not* appreciate that behavior. I even blogged about it two years ago.

  9. mr_noodle

    Concerning the HIG, I don’t think Apple is eating their own dog food on this one. Software Update pops up on a schedule. As far as I understand it, there is no “app” for it to pop up for when it launches (except for the Software Update app itself).

    Also, as indicated here and by others (search for Sparkle on Daring Fireball), some people aren’t fond of being interrupted when they launch an app as they launched it to start doing something with that app. I think this may be a case where usage in the field doesn’t quite agree with the theory.

    Nonetheless, it’s a bit of an experimental feature and it’s usage is optional so it’s up to the dev (or resourceful user – see my comment above) to enable it for their app.

    Thanks for the comments, all.

  10. Rory

    @MC

    I didn’t know that. I always thought the timer was to get you to think about installing the extension and make sure you really wanted to install it.

  11. Mark Alldritt

    Thanks for the library, it has been very useful in my work. I have a question: do you know of a good way of detecting the resumption of user activity once performSelector:withObject:afterSystemIdleTime: has called the selector?

    CGEventSourceSecondsSinceLastEventType looks promising, but I would have to continually poll that call. I’m sure there is a better way.

    Thanks

  12. mr_noodle

    Sorry about the tardy reply. I’ve been in heads-down development mode and haven’t paid much attention to the blog.

    As far as I know, polling is the only way to do it but I haven’t done all that much research on it from that angle. Maybe there’s some way in IOKit but that’s just pure conjecture.


Leave a Reply



Back to top