Solipsism Gradient

Rainer Brockerhoff’s blog

Browsing Posts published by Rainer Brockerhoff

Cocoa musings pt.3

No comments

It’s been a while since the last post, but I’ve finally gotten past the boring parts of the new Klicko System Preference pane & background process. The latter is actually a per-user Launch Agent. I’d used launchd before in the last Quay implementation, but didn’t fully understand what I was doing.

Well, I still can’t claim I fully understand all, but I’m definitely getting better at this. As soon as Klicko 1.2 is out and working (hm, maybe that should be the other way around?) I’ll back-port everything I learned to Quay.

One important part of messing around with launchd agents is that you have to set everything up just so – permissions of the executable and of its containing folders, as well as of the controlling plist if it’s a global agent; and you have to run launchctl to load (register) the controlling plist with launchd. Only then, and if the conditions inside the plist are satisfied, launchd will run your agent for you.

Uninstalling your agent, usually for putting an updated version in its place, must also be done carefully. You have to unload it if it’s loaded (even though it may not be running at the time) before swapping in the new executable and plist. Ideally, to avoid wasting time, there should be an easy way to test if your agent is properly loaded – the user might have unloaded it manually, or whatever. Unfortunately, the only “official” way to do so is to run launchctl again, passing in the “list” parameter, and parsing the output; not ideal.

However, there’s a primitive API in place in <launch.h>, and in fact launchctl uses this API itself to communicate with launchd. Still, there’s no documentation about it; only some sample code. Also, there’s this recent reply on the launchd-dev mailing list:

Right now the <launch.h> API is only rated for
daemons checking in with launchd (ala SampleD).
It is not really designed for job management.
Rather, we recommend that folks do their job
management by fork/exec of launchctl.

Quinn “The Eskimo!”

Still, I thought it would be interesting to use this API to check on the agent; after all, if something fails you’ll get an error back, and nothing will be messed up. So here’s the code for doing so:

#include <launch.h>

static id GetFromLaunchData(launch_data_t obj);

static void Launch_data_iterate(launch_data_t obj, const char *key, void* dict) {

   if (obj) {
      id value = GetFromLaunchData(obj);
      if(value) {
         [(NSMutableDictionary*)dict setObject:value forKey:[NSString stringWithUTF8String:key]];
      }
   }
}

static NSDictionary* GetFromLaunchDictionary(launch_data_t dict) {
   NSMutableDictionary* result = NULL;
   if (launch_data_get_type(dict)==LAUNCH_DATA_DICTIONARY) {
      result = [NSMutableDictionary dictionary];
      launch_data_dict_iterate(dict, Launch_data_iterate, result);
   }
   return result;
}

static NSArray* GetFromLaunchArray(launch_data_t arr) {
   NSMutableArray* result = NULL;   
   if (launch_data_get_type(arr)==LAUNCH_DATA_ARRAY) {
      size_t count = launch_data_array_get_count(arr);
      result = [NSMutableArray arrayWithCapacity:count];
      for (size_t i=0;i<count;i++) {
         id obj = GetFromLaunchData(launch_data_array_get_index(arr, i));
         if (obj) {
            [result addObject:obj];
         }
      }
   }
   return result;
}

static id GetFromLaunchData(launch_data_t obj) {
   switch (launch_data_get_type(obj)) {
   case LAUNCH_DATA_STRING:
      return [NSString stringWithUTF8String:launch_data_get_string(obj)];
   case LAUNCH_DATA_INTEGER:
      return [NSNumber numberWithLongLong:launch_data_get_integer(obj)];
   case LAUNCH_DATA_REAL:
      return [NSNumber numberWithDouble:launch_data_get_real(obj)];
   case LAUNCH_DATA_BOOL:
      return [NSNumber numberWithBool:launch_data_get_bool(obj)?YES:NO];
   case LAUNCH_DATA_ARRAY:
      return GetFromLaunchArray(obj);
   case LAUNCH_DATA_DICTIONARY:
      return GetFromLaunchDictionary(obj);
   case LAUNCH_DATA_FD:
      return [NSNumber numberWithInt:launch_data_get_fd(obj)];
   case LAUNCH_DATA_MACHPORT:
      return [NSNumber numberWithInt:launch_data_get_machport(obj)];
   default:
      break;
   }
   return nil;
}

static NSDictionary* GetFromJobLabel(NSString* job) {
   NSDictionary* result = nil;
   launch_data_t msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY);
   if (msg&&launch_data_dict_insert(msg, launch_data_new_string([job fileSystemRepresentation]), LAUNCH_KEY_GETJOB)) {
      launch_data_t response = launch_msg(msg);
      launch_data_free(msg);
      if (response&&(launch_data_get_type(response)==LAUNCH_DATA_DICTIONARY)) {
         result = GetFromLaunchDictionary(response);
      }
      launch_data_free(response);
   }
   return result;
}

To use it, just call the last function like this:

   NSDictionary* dictionary = GetFromJobLabel(@"com.yourcompany.agentlabel");

and examine the dictionary. If it’s nil, some error happened, or your agent isn’t loaded into launchd.

If you do get a dictionary back, it will be autoreleased, and the “Label” key’s object should be equal to the string you passed in. There are two more keys that might be interesting to check: “PID” will contain your agent’s pid if it’s running, and will be missing if it’s not; and “LastExitStatus” will show you what your agent’s main() returned on its last run.

Note that most of the code above is actually based on the launchd source, converted to use Foundation objects instead of CFTypes.

Re: Week off

No comments

We’re back, and I’m catching up on mail, sorting pictures, the usual post-trip routine. Normal life should resume next Monday.

PS: gasp. icon_eek.gif

Week off

No comments

Early tomorrow we’re leaving for a short (6-day) trip to Tocantins, or more specifically, the Jalapão State Park.

It should be good to unwind a little from 2008, and I plan to bring back some good pictures. For the first time in several years, I won’t take a laptop; there’s supposed to be very little infrastructure at the campground. Hopefully the withdrawal symptoms will be mild… icon_wink.gif

Unfortunately, this means I’ll be unable to answer email, do product support, or even approve new users here at the forum. All will back to normal on the 16th, so please bear with me.

Re: More tradeoffs

No comments

While most of yesterday’s Macworld keynote was uninteresting to me personally – I rarely use any of the updated software, and don’t have iTunes store access – the new 17″ MacBook Pro was mostly as I expected: the additional space was used for a larger battery, and more USB ports.

It was gratifying to see Apple’s battery page practically repeat my reasoning about the infrastructure (and wasted space) needed to accommodate removable batteries. (See also Chuq Von Rospach‘s discussion of the issue.) Not that the 15″ model’s infrastructure is quite as large as they show in the video, of course… but it makes the point well.

Judging from the pictures, the hard drive is inside a space that’s almost exactly square. In theory, they could have rotated the drive 90 degrees and made the battery 30mm wider, for maybe 10% more capacity. So why didn’t they do that? It would have meant extra weight, a beefed-up charging circuit, more heat dissipation, and either a larger power supply (which already is at 85W) or a longer charging time.

I don’t doubt that we’ll see incremental upgrades for iMacs and Mac minis rolled out without much fanfare over the next two months. My tip for the iMacs is that they’ll use the exact same front and back bezels as the corresponding-sized displays, with only a 10mm shim module inserted to accommodate the CPU and drives. The advantage, of course, would be that you could add a second display to your iMac with the exact same styling, screen parameters, and height; a good selling point. It would be a no-brainer for the 24″ model. I’m not sure that a 20″ display from Apple would sell well by itself, but if it used bezels and nearly all components from the 20″ iMac, it might be price competitive, and the styling advantage would be worth paying a few bucks extra. A 28″ iMac has been rumored; would that, then, mean that the current high-end 30″ display would be shrunk to 28″ too? I hope we’ll have the answer soon.

Only logical

No comments

Finally an interesting test to open the year. Can you think logically?

I got 14 out of 15 right. The last one (which I got wrong) is declared “controversial” in the answers. Bah.

Year++

No comments

It’s been a couple of weeks since my last post, but I haven’t been idle. Well, a few of the holidays excepted, of course. Here’s what’s on my radar for the new year, or at least for the first months.

I’ve finally had time to look at the SnowLeopard (Mac OS X 10.6) beta. Can’t say much about it, except that there are interesting and significant changes in the infrastructure – important for programmers. I really hope that, once out, a majority of users will adopt it.

A consequence of seeing the 10.6 APIs is that I decided to do a serious rework of Klicko and Quay, so they’ll be ultimately easier to migrate to 10.6 and a 64-bit environment. Klicko inherited a lot of code, and I’m really glad that I decided to do it as a training exercise, since I’ll very soon back-port much of that back to Quay, after enhancing and optimizing.

If all goes well, Klicko 1.1 will be out soon. I’ve got all but installation and updating procedures done. Most notably, it’s now a System Preferences panel that installs a background process. While this is a departure from the easy-to-run, simple-Cocoa-app mantra, splitting this type of application into a faceless background process and a foreground GUI will soon be mandatory for all practical purposes, and there are advantages; for the user, once properly installed and configured, Klicko will “just work” automatically and in the background, and use very few resources.

One new Klicko feature was requested by several users (and others who have emailed me in the past). There will be a (configurable) preference to have the window’s “zoom” button do a true maximize – or at least attempt to, not all applications will support it properly. While I find the importance that Windows users attach to maximizing everything all the time a little puzzling, trying it out convinced me that it’s useful now and then. I always maximize NetNewsWire, Xcode, and a few other application windows, for instance. I also decided that it may be useful in pointing users that are new to the Mac to shareware software in general and to my own applications.

Once that is done – hopefully with very few build updates – it’s back to reimplementing everything I learned into Quay (probably 1.2). I’m not decided whether that will become a preferences panel too, but it may be possible. More importantly, I’m now pretty fluent with the event tap and accessibility APIs that both Quay and Klicko use.

The major new feature of Quay will be that Quay menus will also pop up for Finder icons. Ultimately, I’d like to make this work in any and all Finder window modes – icon, list and column – and in all circumstances where the Finder’s own contextual menus pop up. I’ve done some preliminary testing and it looks like it might be possible. There are a few edge cases where I’m not sure that I’ll be able to compute the correct path for the icon.

Anyway, if all works as planned, I’ll be able to introduce more flexibility through plug-ins. A plug-in would get a file or folder handed to it and would produce either a popup menu, or an information window. This would allow me to finally declare XRay entirely defunct (it already runs very poorly on Leopard), and replace it with many small, specialized plug-ins. My older contextual menu plug-ins, like Zingg! and Nudge would also be trivial to rewrite as Quay plug-ins.

A similar, much more ambitious plug-in scheme, was planned for XRay II, and I can reuse some of that code… there are still some complex issues to decide, however. Opening the plug-in interface to other developers is of course what I’d like to do, but licensing, updating, keeping plug-ins from interfering with each other will be very tricky.

All in all there’s lots of ideas to implement and this should keep me busy for most of the year. I’m not considering going into iPhone/iPod development for now; there’s a glut of $0.99 applications and the way the App Store is working seems overly opaque to me.

More soon! Stay tuned.

Klicko 1.0.1 (123)

No comments

Well… let’s hope this one will last more than a few hours…? icon_smile.gif

Cocoa musings pt.2

No comments

One of the nice things Objective-C inherited from C is the switch statement. If you have set different tags in all your menu items, you often find you can do things like:

- (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
   switch ([menuItem action]) {
   case 1:
      ...
   case 2:
      ...
   }
   return YES;
}

Those tags have to be set in Interface Builder, and you probably will have an enum somewhere to use symbolic constants instead. However, the standard C switch() is restricted to integer values; you can’t switch on strings or selectors. So, if you don’t want tags and need to switch on the menu item’s selectors, you’ll have to do:


- (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
   SEL action = [menuItem action];
   if (action==@selector(firstAction:)) {
      ...
   } else if (action==@selector(secondAction:)) {
      ...
   } else...
   return YES;
}

Bearable if you have two, or even four or five cases, but what if there are dozens? Or suppose you have to compare strings instead of selectors:

- (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
   NSString* title = [menuItem title];
   if ([title isEqualToString:@"firstTitle"]) {
      ...
   } else if ([title isEqualToString:@"secondTitle"]) {
      ...
   } else...
   return YES;
}

Not that it’s recommendable, in practice, to compare menu item titles, but it’s a good example.
Well, there are other ways to make this more readable, or even more efficient. But here’s one neat way to convert a group of strings into integers for use in a switch(). First, let’s write an utility C function to do so:

NSUInteger CaseCodeForString(NSString* string) {
   static NSArray* array = nil;
   if (!array) {
      array = [[NSArray alloc] initWithObjects:
               @"zeroth string",
               @"first string",
               @"second string",
               @"third string",
               ...
            nil];
   }
   return [array indexOfObject:string];
}

Note the standard lazy allocate-once trick of declaring array static, initialize it to nil, and test before using. Anyway, this function will return 0 if you feed it @”zeroth string”, 1 for @”first string” and so forth… and return NSNotFound if the string isn’t in the array. So you could, in our last example, do:

- (BOOL)validateMenuItem:(NSMenuItem*)menuItem {
   switch (CaseCodeForString([menuItem title])) {
   case 0:
      ...
   case 1:
      ...
   }
   return YES;
}

If there are many strings, this will be faster than a series of isEqualToString: calls; this is because NSArray uses a hash table to find the index of a particular object, and only goes into the actual string comparison if the two string’s -hash methods return the same value.

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.