waffle

Gettin’ Wiggly With It

Nokia, Sony Ericsson, Samsung, Motorola, HTC, Blackberry and LG could work for a thousand years and still not come up with UI like this.

How To Make Safari 3′s WebKit Start Handing You Favicons (Site Icons) Again

Needless to say, when WebKit in Leopard (and, it turned out, WebKit in any system where Safari 3 exists) stopped doling out “hey, you have a favicon” delegate messages (webView:didReceiveIcon:forFrame:), and you make a search tool whose only form of distinguishing between different search engines are in many places by icon and color, you get pretty pissed.

The WebKit ticket is #16296, and it turns out that the backing icon database was disabled by default since driving WebKit into multithreading for the sake of the few apps that want to use icons was deemed unsavory. The fix in WebKit itself is to start the requisite icon database engine if the delegate method is being implemented, but until this bubbles up to Safari-level distribution, one has to call +[WebIconDatabase sharedIconDatabase] to force the database to allocate a singleton instance and thus start itself. Here’s how:

@try {
    Class webIconDatabaseClass = NSClassFromString(@"WebIconDatabase");
    // Since this is private API, it may go away; only call if the class exists.
    if (webIconDatabaseClass != Nil) {
        [webIconDatabaseClass performSelector:@selector(sharedIconDatabase)];
    }
} @catch (NSException *exc) {
#pragma unused(exc)
}

MacBook Diminutive Speculation

Here’s my bet. MacBook Diminutive will be announced at MWSF. (It won’t be named that, it’d be named MacBook nano or MacBook mini or MacBook thin, with a Pro in there perhaps.)

It’ll be small (smaller than the current MacBook, and thinner than an inch), but not awesomely tiny (say, like, 7″ screen). Under no circumstances will it weigh more than 1.5 kg (3.3 archaic pounds) at its minimum bootable configuration (with battery but without any other removable part). (The MacBook is currently the lightest Mac at 2.27 kg or 5 lb.)

It will probably feature a 1.8″ hard drive (such as is used in the iPod classic), a new smaller hard drive form factor or a solid state disk around 64 GB. It won’t feature a built-in optical drive. The optical drive will be delivered either via an external drive (bundled), or with a dockable bottom plate. The bottom plate could also possibly hold a battery and extra ports – the smaller the laptop itself gets, the more valuable the “border space” gets. Larger ports, like DVI, take up a lot of space, and the Ethernet jack is higher than the audio and USB ports.

It may have a new form of FireWire, or a new FireWire plug.

It will come in any of, or a mixture of, aluminum, carbon fiber and black plastic.

It will not be the same product as the “tablet” or “Newton II” or “Mac touch” or what have you. Its trackpad or keyboard may incorporate multi-touch, though. (The current scrolling trackpad already uses two-finger sensing.)

Speculate in the comments.

Setting a Spaces Window Collection Behavior when using the 10.4 SDK

Not three minutes after sending out the first Monocle danger build, I got a complaint about how it doesn’t work well with Spaces. I had made a mental note earlier in the day of fixing this before sending out the build, but it had gotten swapped out. This presents a problem.

You can specify how a window is supposed to behave when using Spaces. You do this by setting a collection behavior. Your choices are:

  • Default. Stay on one space like every other window.
  • Join all spaces. Stay on all spaces simultaneously. (The menu bar does this and never moves.)
  • Move to active space. The window will move between spaces as you switch spaces. As you switch between this window and a window technically only in the current space, you will stay in the current space.

These choices are integer constants in an enum. There’s no porting implications here, but to actually set the behavior, you have to call a method. Objective-C is dynamic and you never call methods, you “send messages”. This is all well and good, but if your project is set to treat warnings as errors (and Monocle is – keeps me honest), it becomes a problem. And if you add the methods via a category, then there’ll be more warnings when you start linking to the 10.5 SDK instead.

So, since Objective-C is so dynamic, let’s move this from compile-time (where you can get warnings thrown at you) to run-time (where the worst thing that can happen is that a method doesn’t exist). We do this by building an invocation and invoking it. Update: Or you can correctly implement what I planned yesterday at 3 in the morning but didn’t get to work. See the comments. The rest of this post isn’t going nowhere, though – it’s factually correct and shows how to construct invocations.

So let’s look at the method signature: - (void)setCollectionBehavior:(NSWindowCollectionBehavior)collectionBehavior. If we dig deeper into the NSWindowCollectionBehavior, we’ll see that it is, in fact, like all enums in 10.5, due to 64-bit considerations an anonymous enum and a typedef where the enum “name” is defined to have the type of the size of the enum – in this case NSUInteger. NSInteger and NSUInteger are two types defined with macros to be the 64-bit (unsigned) long when building 64-bit and the 32-bit (unsigned) int when building 32-bit. Because they are used in places like -[NSArray count], this means that arrays in 64-bit apps can automatically hold more items. The Foundation release notes has more on this.

The NSInteger macros are short and well-suited to directly copy and paste, so let’s do this:

#ifndef NSINTEGER_DEFINED
#if __LP64__ || NS_BUILD_32_LIKE_64
typedef long NSInteger;
typedef unsigned long NSUInteger;
#else
typedef int NSInteger;
typedef unsigned int NSUInteger;
#endif
#define NSINTEGER_DEFINED 1
#endif

Thanks to the NSINTEGER_DEFINED guard, you can even switch to the 10.5 SDK now and build and you won’t get any multiple definition issues.

To build an invocation, you need an existing selector known to the Objective-C runtime to have the same type. Therefore, we insert an innocuous noop — - (void)selectorForNSWindow_setCollectionBehavior:(NSUInteger)behavior { } — that has the same signature as the real method. Then you can make a method to simply build the invocation and invoke it:

- (void)prepareWindow:(NSWindow *)window
forSpacesUsingCollectionBehavior:(NSUInteger)mode {
    BOOL allocatedBuffer = NO;
    NSUInteger *bufferPtr = NULL;
    @try {
        NSMethodSignature *sig = [self methodSignatureForSelector:
           @selector(selectorForNSWindow_setCollectionBehavior:)]
        NSInvocation *setSpacesBehaviorInvocation = 
          [NSInvocation invocationWithMethodSignature:sig];
        [setSpacesBehaviorInvocation setSelector:@selector(setCollectionBehavior:)];
        [setSpacesBehaviorInvocation setTarget:window];
        bufferPtr = (NSUInteger *)malloc(sizeof(NSUInteger));
        allocatedBuffer = YES;
        *bufferPtr = mode;
        [setSpacesBehaviorInvocation setArgument:bufferPtr atIndex:2];
        [setSpacesBehaviorInvocation invoke];
    } @catch (NSException *ex) {
        ;
    } @finally {
        if (allocatedBuffer) {
            free(bufferPtr);
        }
    }
}

The reason argument 2 is being set is because every Objective-C method implementation takes two arguments when being called by the runtime – id self and SEL _cmd. The exception handling is there because if this runs on 10.4, an exception will be thrown since we’re invoking something that’s not already there.

Using this method, you can then put any NSWindow in any mode you’d like.

Older posts »