Solipsism Gradient

Rainer Brockerhoff’s blog

Browsing Posts in Development

Just saw Rob Keniger’s Wrapster plug-in for Coda, an interesting use of event taps; it’s a plug-in that intercepts key events for its master application.

The version I looked at (1.2) uses a global tap and checks if its master application is active:

   if(![NSApp isActive]) {
      return event;
   }

Of course, it would be conceptually more reliable to obtain the event’s destination process PSN and test against that; but the best way would be to tap the current process like this:

   ProcessSerialNumber psn = {kNoProcess, kCurrentProcess};
   CFMachPortRef tapg = CGEventTapCreateForPSN(&psn, kCGTailAppendEventTap, kCGEventTapOptionDefault, CGEventMaskBit(kCGEventKeyDown), ProcessEvent, NULL);

Global event taps are a great tool, but they’re also easy to abuse, and I suppose that in a year or two we might have dozens of utilities or plug-ins tapping events; the actual results might be dependent on the order they run or load, for instance, and overall responsiveness might suffer. Therefore, please make absolutely sure that you need a global tap to do whatever you want to do, that it takes as little time as possible, and that it does careful parameter checking.

One great application to test event taps (and to check their presence) is Event Tap Testbench. Its author, Bill Cheeseman, has helped me a lot with subtle details of both event taps and the Accessibility APIs; thanks Bill! Do also check out their other products.

Rob tests if Accessibility is enabled by trying to create a tap and seeing if it succeeds. While this works, I think it’s easier to do it like this:

   if (!AXAPIEnabled()&&!AXIsProcessTrusted()) {
      // error dialog here
   }

The first call checks if Accessibility is enabled in System Preferences; the second checks if your process is allowed to set a key tap even with Accessibility disabled. To achieve this, you must (from a process running as root) call AXMakeProcessTrusted() on the tapping application’s executable – this will take effect on its next run.

In the case of Klicko, it taps only mouse button events, for which Accessibility doesn’t need to be on; however, it uses the Accessibility APIs to check out details about the clicked-on windows. Some people dislike turning Accessibility on in System Preferences, so a future version of Klicko might use AXMakeProcessTrusted() to avoid this; this means asking the user for an Administrator password and running a separate tool, like Quay does. I’m still evaluating the trade-offs for that: it precludes running Klicko from ~/Applications or from the disk image, adds to the application size, and the user has to navigate an extra dialog on first run.

Update:Klicko now uses AXMakeProcessTrusted().

Cocoa musings pt.1

No comments

One of the reasons for my taking a week or two off to work mostly on the just-released Klicko was that I like to rework, and group together, code snippets that worked well for me in earlier applications, and see if I can update them to conform to my slowly growing experience. I’m also prone to digress; one such digression (several months!) resulted in the release of the unexpectedly popular RBSplitView.

Both Klicko and Quay use code that, like RBSplitView, were destined for XRay II, the supposed successor of XRay, which sadly is not reliable under Leopard. Alas, at this writing, it sounds like XRay II will remain in the freezer, its mummy mined for code snippets and general philosophical experience… but the basic idea persists, and something quite equivalent (but also quite different in detail) is already being conceived.

Both Quay and Klicko do part of their seeming magic with a technology called “Quarz Event Taps” (PDF file). This was introduced in Tiger, and perfected in Leopard. Briefly, an event tap is a C callback routine that is called to filter low-level user input events at some points in the system’s event processing, which is actually quite complex. Events can be generated, examined, modified or even suppressed before they’re delivered to an application. Since user input events are usually routed to the foreground window (that is, to the foreground application, even if it has no window), this makes event taps quite powerful.

You can make a global event tap, or a per-process tap. Quay sets up a tap on the Dock process to intercept clicks on Dock icons. Klicko uses a global tap to check for clicks on background windows.

Tapping one application is, in principle, easy: you locate the application to be tapped by its PSN (Process Serial Number), set up the tap, tie it to your application’s main run loop, and that’s it. Here’s what a bare-bones implementation would look like:

// This is the callback routine, called for every tapped event.

CGEventRef ProcessEvent(CGEventTapProxy tapProxy, CGEventType type, CGEventRef event, void *refcon) {
   switch (type) {
      case kCGEventLeftMouseDown:
         // process a mouse down
         break;
      case kCGEventLeftMouseUp:
         // process a mouse down
         break;
   }
   return event;   // return the tapped event (might have been modified, or set to NULL)
               // returning NULL means the event isn't passed forward
}

// Here's how you set up the tap: we're catching mouse-down and mouse-up

...
   ProcessSerialNumber psn;
   // get the PSN for the app to be tapped; usually with the routines in <Processes.h>
...
   CFMachPortRef tapg = CGEventTapCreateForPSN(&psn, kCGTailAppendEventTap, kCGEventTapOptionDefault, 
      CGEventMaskBit(kCGEventLeftMouseDown)|CGEventMaskBit(kCGEventLeftMouseUp),
      ProcessEvent,NULL);
   if (!tapg) {   // bail out if the tap couldn't be created
      NSLog(@"application tap failed");
      [NSApp terminate:nil];
   }
   CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tapg, 0);
   if (!source) {   // bail out if the run loop source couldn't be created
      NSLog(@"runloop source failed");
      [NSApp terminate:nil];
   }
   CFRelease(tapg);   // can release the tap here as the source will retain it; see below, however
   CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
   CFRelease(source);  // can release the source here as the run loop will retain it

After that, all should work – in principle. The devil is in the details. Here’s how you locate a running application by its application ID and return its PSN:

BOOL GetPSNForApplicationID(NSString* appid, ProcessSerialNumber* outPSN) {
   outPSN.highLongOfPSN = outPSN.lowLongOfPSN = kNoProcess;
   while (GetNextProcess(outPSN)==noErr) {
      NSDictionary* pdict = [(NSDictionary*)ProcessInformationCopyDictionary(&psn,
         kProcessDictionaryIncludeAllInformationMask) autorelease];
      if ([[pdict stringForKey:(id)kCFBundleIdentifierKey] isEqualToString:appid]) {
         return YES;
      }
   }
   return NO;
}

To make a global tap, you don’t need a PSN. Just use the following tap creation call instead:

   CFMachPortRef tapg = CGEventTapCreate(kCGAnnotatedSessionEventTap, kCGEventTapOptionDefault, 
      CGEventMaskBit(kCGEventLeftMouseDown)|CGEventMaskBit(kCGEventLeftMouseUp),
      ProcessEvent,NULL);

More details. If you’re tapping an application, it may not be running; CGEventTapCreateForPSN will return NULL in that case. Or it may quit while you have the tap set up. You probably want to monitor that process and either quit, or rerun the application, or wait for it to come back up. In the latter cases, you’ll have to back out of the now-dead tap carefully:

   CFMachPortInvalidate(tapg);
   CFRunLoopRemoveSource(CFRunLoopGetCurrent(),source,kCFRunLoopDefaultMode);
   CFRelease(tapg);   // this CFRelease has been moved from before the CFRunLoopAddSource

supposing, of course, that you have held on to those two variables. Note how the CFRelease(tapg) should, in such a case, happen only after the source has been removed from the run loop; otherwise invalidating the tap will cause the run loop to crash. You can use the same technique to close a global event tap, though usually there’s no need; if your app crashes or quits, the tap will be closed automatically.

However, there’s a serious problem while debugging an event tap. If you’re tapping a single application, and set a breakpoint inside yours (or break into the debugger anywhere because of a crash or exception), both applications will stop. If the same happens while a global tap is active, the entire system stops accepting user input! The only way to recover is to ssh/telnet in from another machine, and kill Xcode. So even if you prefer NSLog/printf calls to breakpoints, this will be very inconvenient for all but the simplest callback code.

The solution I found was to always use an application tap while debugging. An easy way is to define, as I always do, a special macro inside the main project build configuration panel (but for the debug configuration only): inside the “Preprocessor Macros Not Used In Precompiled Headers” (aka GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS) write “DEBUG”, and then, instead of the global tap, compile in an application tap on some always-present application (like the Finder) by using #ifdef DEBUG/#else/#endif statements.

Even that isn’t always sufficient, as Xcode 3 notoriously may invoke the debugger (even on your release build!) if your app crashes. You must either get used to never clicking on “Build & Go” for your release build, or you must make a runtime check for the debugger. The latter will prevent inadvertent freezes, but if you forget to take it out before deployment, your application will behave oddly if a curious user runs it under a debugger.

This post is already too long, so I’ll talk only briefly about what you can do inside the event tap callback itself. Every possible execution path should be short and contain no long loops or wait points. If you’re just watching events, always return the same event passed in as parameter. Return NULL if you want to suppress an event; however, be careful to suppress entire event groups. For instance, if you decide to suppress a mouse-down, store the event number and also suppress subsequent mouse-dragged and mouse-up events with the same number; otherwise the destination application may behave oddly. Some apps may behave oddly when tapped, by the way.

Update: I previously said here that to intercept or generate keyboard events, your application must run with setgid 0 (the “wheel” group). I was mistaken; my apologies. Your application must run setuid root to make an event tap at the third tap point (which I didn’t mention here), which is where the events enter the window server (kCGHIDEventTap).

Blind Cook Attack

No comments

Release of Quay 1.1 has been stuck at the “one more bug” stage, as I’ve mentioned. Today I’ve finally unterstood what is happening, and a fix should be easy – I only have to rebuild most of my inter-thread communication. Herewith the cautionary tale.

Quay 1.01 was (as its help file boasted) “just a simple Cocoa app”. Indeed, clicking on a Quay item in the Dock opened the “QuayMenu” background application (which uses the NSUIElement flag). QuayMenu was optimized for quick startup – it read that item to get the folder it was pointing to, read the folder contents, showed a popup menu with them, and quit again when the menu was closed. Nothing easier; a very linear process.

Then in 1.1, all this changed. Now QuayMenu runs all the time in the background. There are two threads (plus a few more that don’t matter for this discussion). One thread listens (via Quartz Event Taps) for clicks on the Dock’s icons, finds out which icon it was, and if Quay should handle it, does a performSelectorOnMainThread: passing data about the icon to the main thread.

The main thread, in its turn, waits until it gets such an event and then pretty much does what the original 1.01 did: puts up a menu with the folder contents. It also shows an arrow over the clicked icon, both to show which icon it was and to allow the user to distinguish Quay menus from Dock menus.

Of course the arrow has to be in a window to be shown onscreen. 1.01 had an invisible window, since a popup menu has to be associated with a window – but now the window has to be shown only when the menu is onscreen, and hidden again when the menu is taken down.

Then around 1.1b2 I got complaints that, after a menu was canceled, the application that was active at the time of the initial wasn’t properly restored to the background – easy to fix by hiding the QuayMenu app after the menu went down. Soon after that, I thought that it would be prudent to imitate the Dock behavior when successive icons were clicked; that is, clicking on one icon, then on another, would produce two successive menus without any need to cancel the first one. And it also would be nice to be able to cancel a long menu while it was being built.

OK, so I introduced some inter-thread communication – mostly some flags and a cancel message – to take care of that. And after some fiddling it seemed to work, some conveniently ignored exceptions excepted. Then my MacBook Air arrived (my first multi-core Mac), and I noticed that the exceptions were more frequent; in fact, about 1 in 10 times, when I clicked on a second icon the first memu was taken down, and the second one either didn’t appear at all or it only flickered onscreen very briefly.

Well, this of course indicated a threading problem – as my experience with interthread communication was a little limited, that was to be expected – and I tried to find out where I was doing something dumb like not properly locking a shared resource or whatever. Since the bug never happened when I tried to break somewhere and single-step from there, it was indeed related to timing. But where?

Tons of NSLog()s were sprinkled about and everything seemed to be working properly. The messages from the first thread left at the proper time, the main thread caught them, the first menu was closed when necessary, the second menu was popped up as expected, but it mysteriously closed down immediately (1 in 10 attempts) or not (9 in 10 attempts). Everything else was as expected. Moreover, no canceling message was sent, or left over, that could close the second menu! It… just curled up and closed. Argh.

Finally – and it was really dumb of me not to think of that immediately – it occurred to me to look at the value returned to my Carbon Menu event handler for the kEventMenuEndTracking event. (Yes, I have a Carbon event handler because for complex UI reasons I need more control – and a shorter build time – than a Cocoa menu could give me.) Turns out that 9 out of 10 times the menu gets closed by kHIMenuDismissedBySelection (when the user picks a menu item); by kHIMenuDismissedByUserCancel (when the user closes the menu without selecting any item); or by kHIMenuDismissedByCancelMenuTracking (when my first thread canceled the menu because the user clicked on another Dock icon).

However – and here was the smoking gun – 1 out of 10 times the menu turned out to close by kHIMenuDismissedByAppSwitch (meaning QuayMenu went into hiding to put another application into the foreground). Now, that should indeed be happening briefly after the first menu was closed, but before the second menu was opened QuayMenu should jump into the foreground again. Now that I considered it, that’s a somewhat costly process that should be optimized out. But why was the application hiding after the second menu popped up instead of before? The sequence of events shouldn’t allow that at all!

Well, at least now I had a clue of where to put my NSLog()s and after some hours of sweating I had the answer: putting all that window hiding and showing in with application hiding and showing, and trying to optimize that by balancing the load over two threads – in brief, putting all urgent stuff into the first thread and all the potentially slow stuff and UI into the main thread – was fundamentally broken. It shouldn’t have worked at all, ever! So why was it failing only 1 in 10 times?

At this point I was visiting my mother and I was attempting to explain to her what was going on. Now, my mom is 95 – she’s still very sharp but knows absolutely zilch about programming. On the other hand, she’s an excellent work optimizer. I suppose 80+ years of kitchen duty will do that to you. At any rate, I was trying to explain what I was doing by resorting to a metaphor.

Assume two blind cooks working in a restaurant. The specialty is pancakes. An order for a pancake comes in to the first cook. He checks out (by Braille I suppose) what goes into the pancake, throws the ingredient into his pan, waves them briefly over his stove, then with a practiced flip of his hand he flicks the pancake to the second blind cook who’s supposed to finish the pancake.

Since they’ve practiced this a lot, and worked out some sort of signals, the second cook knows when to put up his frying pan just in time and in the right position to catch the pancake flying into his direction, and after that it’s easy. He tosses the pancake to a waiter who in turn serves it to the client.

Now, what was happening is that when a second order comes in before the first pancake is finished the second cook has to clean his pan. OK, we may have ridden the metaphor here until it breaks down (hehe) but let’s assume that he usually manages to do so, and proceeds to finish the second pancake in peace. Maybe the client has changed his mind about what sort of pancake he wanted, or something.

Only 1 times in 10 something weird happens and the second pancake is also thrown out unfinished. The first cook notices nothing amiss; he’s getting orders, setting up, and throwing the pancakes. The second cook notices nothing amiss; he’s catching the pancakes like he’s supposed to, cleaning his pan and all when necessary. It’s just 1 in 10 clients that complain because they never get their pancake!

Well, said my mom, so who was stealing the pancake then? The only explanation I found was that the kitchen shouldn’t have worked at all – it was working just by coincidence! In other words, the second cook wasn’t tossing the pancake to the waiter; he was tossing it out of the window! It just happened that at that crucial time a providential windmill wing swung by in front of the window, the pancake bounced off that and, who’da thought, right on the waiter’s plate!

Of course such a thing can’t work reliably and now and then one of the cook’s timing was off just enough to make the pancake miss the windmill wing, and the pancake would be gone with none of them noticing.

So my mom and I had a good laugh (“I can’t believe you actually think of your work that way!”, she said) and I went home. Tomorrow I must redo the whole kitchen routine. Stand by for news.

Via John Siracusa, I just read this great rant from Jens Alfke about Adobe’s Photoshop Elements 6 Installer. Money quotes:

First things first: After inserting the disc, I had no idea how to start the installation. I can’t remember the last time that’s happened.

It’s hard to get too worked up about an installer, one way or the other, but it’s annoying when it insists on installing over 2 gigabytes of stuff on my disk (most of which seems to be clip-art) without any choice to skip the inessentials. Nor would it even tell me ahead of time what it was installing, besides the ominously-named “system components”.

Is there an Adobe Updater Updater that puts up that alert and updates the updater? And what if the Updater Updater needs an update? (I can start to see where that 2 gigabytes went, now.)

Well, Adobe (and Microsoft, and…) are notorious for weird, complex and/or unusable installers and updaters. I myself dislike installers and Apple Installer packages; whenever I see an application with an installer package, I first use Charles Srstka’s excellent Pacifist to see what it install where, and what the installer scripts do.

So why does Quay, in its 1.1 betas, use a proprietary built-in installer? (Quay 1.0x was drag & drop.)

It was a difficult choice. Quay 1.0x was a pair of simple applications that could run from anywhere, and the background application ran by demand. Quay 1.1 still has a foreground and a background application, but the background application runs as a per-user launch agent, meaning it’s started by the system and runs all the time. It needs special permissions to use the accessibility API, and there’s an on-demand tool inside which also needs special permissions.

Of course, I could have done that with an Apple Installer package. However, writing shell scripts is not my cup of tea, and I feel more comfortable doing that myself; especially as my installer seeks out and uninstalls all previous versions, generates a cache file for the foreground app’s popup menu (the one with all the icons), and does other things hard or impossible to do in a shell script.

So, I reluctantly decided on a proprietary installer that runs only from the disk image. Indeed, the installer app is the same app as Quay itself; it runs as the installer if you run it from anywhere but the “installed” location (inside /Library/Application Support). What remains is a documentation and usability challenge, as Jens’ post shows. Here’s what the user sees when the Quay disk image mounts:

The upper icon explains what it does and asks the user to run it, the lower icon is the in-app help file. Notice that both are aliases (or, rather, symbolic links) and the originals are hidden inside an invisible folder. This hopefully prevents less-technical users from doing a drag-install that will behave oddly from their point of view, and also prevents them keeping a copy around in a unpredictable location – this makes the uninstaller’s job, later, much simpler. The installer also unmounts the disk image afterwards. All this is also explained in the installation screens.

I’m still tweaking the help file to be more, erhm, helpful – but few people seem to read help files nowadays. Hopefully this approach represents the lesser evil. So far, no user has complained!

Posted by …from every angle:
…from every angle linked to this post

The Hubbub About iPhone SDK

I’ve been thinking about the possibility of writing an app for the iPhone ever since it appeared on the news. Two problems, though: I don’t really know how to program anything other than some basic php stuff, and some SQL…

Good extra argument against background processes from Craig Hockenberry:

The heart of the problem are the radios. Both the EDGE and Wi-Fi transceivers have significant power requirements. Whenever that hardware is on, your battery life is going to suck. My 5 minute refresh kept the hardware on and used up a lot of precious power.

Two interesting developments came up while I’m studying for my next post about code signing.

Firstly, every developer who has signed up for the $99/year program has gotten a letter which says, in part:

We have received your enrollment request. At this time, the iPhone Developer Program is available to a limited number of developers and we plan to expand during the beta period. We will contact you again regarding your enrollment status at the appropriate time.

The message for non-US developers also mentions that the program will be implemented in the US first. At this point, some developers went off in a huff, interpreting it as a rejection, and numerous complaints and conspiracy theories have been aired. So far (as I know) nobody who applied to the $99 program has been formally accepted – the ones who said they were are apparently newbies interpreting their ability to download the SDK as “acceptance”. On the other hand, a handful of companies have stated they’ve been accepted into the $299 enterprise program.

Well, my understanding of English may be faulty (it’s only my fourth language, after all) but let’s look at the 3 sentences I quoted above:

We have received your enrollment request.

Nice of them to reply.

At this time, the iPhone Developer Program is available to a limited number of developers and we plan to expand during the beta period.

This is just repeating what Steve Jobs said during the iPhone event… nobody would expect Apple to accept all applicants immediately and at the same time. There certainly are several thousands at the very least.

We will contact you again regarding your enrollment status at the appropriate time.

Meaning, as soon as we dig out from under, we’ll check your application. I see no problem there either.

Apparently, some people are indeed interpreting this as a poorly worded rejection, or at best as an ambiguous directive that you have to go to the end of the line or try again. Now, I’m the type of person who has difficulty in understanding the logic of “I’m afraid I’ll have to tell you that your credit card has been declined” – the first time I heard that in person I couldn’t follow at all (heh! he’s afraid of what? will he have to say what in some unspecified future? Why doesn’t he then say whatever he will have to say, then?) But I can’t see anything like that buried in that last line either.

Second item is that the SDK’s restriction on background process has been much-commented on, among others by John Gruber and Mike Ash. Along the way, there have been complaints about this and other restrictions imposed on third-party applications. Some have even pointed out (I can’t confirm or deny) that the APIs published in the SDK are just a small subset of the APIs ferreted out by people examining the previous iPhone firmware. Some commenters even seem to believe that Apple not publishing certain APIs is illegal in some way… witness the comments about unpublished APIs called by WebKit/Safari some weeks ago.

Well, Apple can certainly opt to tell people not to use certain APIs. In fact, on the Mac in the past, some large software publishers went ahead and used some of those “hidden” APIs anyway, with the result that Mac OS X still has to support lots of legacy calls that are broken, but can’t even be properly fixed without breaking those applications!

So, on the iPhone, they’re certainly making sure to avoid the legacy/unsupported/hidden API problem at all costs. Apparently the SDK license says that applications may not use any unpublished API, period. Note that this does NOT mean that Apple will have to examine your precious source code line-by-line; no, they’ll just run a tool that looks at your executable, sees what symbols in which frameworks are referenced, and produce a listing of whatever non-documented stuff you’re calling.

So, there are no documented APIs that let you run an application in the background, or start a background process. I’m sure we all agree that having any GUI application continue in the background after another GUI application starts will be too resource-hogging – after all, if you allow one, you have to allow any. From what I’ve read, no Apple application does that either; a few seem to have a background process or daemon running to take care of communications, but that’s of course much more lightweight.

I agree with Mike that obviously the iPhone OS does support multitasking in a generic way. I also agree that the relatively limited RAM (I heard that 64M are available to a third-party application) and CPU speed are no obstacles. To mention an even older example than he gives, I managed to write an embedded system running on a 4MHz Z80A CPU, with 32K of RAM, that handled a GUI, a foreground thread, and two background threads tracing several realtime signals on a screen.

But make no mistake, embedded systems are hard to keep responsive, even if you have no arbitrary code running at all. Apple apparently elected not to have full virtual memory swapping in the iPhone OS; here again, it’s not a limitation of the hardware (Flash memory can support swapping) or of the OS (it’s Unix-based after all) or of the CPU (it does have page tables etc.).

No, I think Apple simply is trying to keep “teh snappy” always happening, keep battery duration as high as possible, and avoid Flash RAM filling up by swapping. Also, swapping pages takes time and burns battery power. A wait of 2 or 3 seconds may be shrugged off if you sit at a desktop computer but may be unacceptable if you’re trying to answer a call on the iPhone.

Consider the MacBook Air with SSD. It has 64GB of flash and 2GB of RAM, with full swapping virtual memory of course. I’ve seen people leaving 20 or 30 applications running on a Mac and not noticing that their swap files grew to 12 or 20GB… but even that may already become a problem on the Air. I suppose Apple didn’t want to face telling people that they should leave 2 or 3GB free on their 8GB iPhone… and risk crashing or hanging when it suddenly fills up.

Still, I feel that if you do have a compelling application that absolutely depends on having a background process running, you’ll probably be able to ask Apple for an exemption… if you can prove that your process won’t hog the system. Also, Leopard (for one) has facilities to start processes on-demand – using launchd, you can have the system watch a certain port or socket and start up your process when data arrives there. No doubt we’ll see more such facilities being deployed.

Before getting to the actual discussion of code signing on the iPhone, here’s an interesting tidbit I discovered while researching details about Apple’s certificates: a PDF file detailing the iPhone certificate conditions – officially, it’s a CPS (“Certification Practice Statement”). This is publicly available, and linked to from Apple’s Root Certificate Authority page, by the way.

Some interesting parts:

This CPS is applicable to the following certificates issued by the WWDR Sub-CA:

? WWDR iPhone Software Development Certificates (“Development Certificates”)

? WWDR iPhone Software Submission Certificates (“Submission Certificates”)

? WWDR CRL Certificates (“CRL Certificates”)

That means, in order, certificates for testing an application on actual iPhone/iPod Touch hardware during development; for actuallly submitting an application for publishing on the App Store; and finally, for submitting a CRL (“Certificate Revocation List”). This shows that development and publishing keys are different, as I’d thought. It remains to be seen whether the publishing keys will be applied by developers themselves before submitting their apps, or (more likely) by Apple after verifying the apps for compliance. Also, CRLs are the technical explanation for Steve Jobs’ comment that “if [developers] write a malicious app we can track them down and tell their parents”… meaning, certificates can be revoked and those apps will cease to function.

No fees are charged for this service. Digital certificates are available at no additional cost to

members of the iPhone Developers? Program. Certificates are valid for the duration of the membership period unless otherwise revoked.

This clears up details about the $99 fee. This is an annual fee, then, for membership in the “iPhone Developer Program”, distinct from the “Mac Developer Program”. See the new http://developer.apple.com/ page, which is now split left/right between these two programs. The certificates themselves are free of charge. Elsewhere, expiration and renewal are discussed, but no actual expiration period is mentioned.

There doesn’t seem to be an equivalent document for signing Mac OS X applications, although a separate page says that you can apply to Apple for the purpose of getting your own root certificate recognized. The conditions (especially regarding auditing) appear to be much more stringent, so this looks to be directed at larger companies. Even so, there’s no fee for that either.

Photos licensed by Creative Commons license. Unless otherwise noted, content © 2002-2024 by Rainer Brockerhoff. Iravan child theme by Rainer Brockerhoff, based on Arjuna-X, a WordPress Theme by SRS Solutions. jQuery UI based on Aristo.