Interfacing With (Hacking) iChat in Leopard

Before you consider using any of the unsupported hacks I’m about to discuss, check to see if existing frameworks , or iChat’s AppleScript interface, will do what you need. Any software update can break unsupported code in unpredictable ways at any time. When Leopard came out, all the hacks I had been using to interface with iChat broke — causing a deadlock, that was harder to track down then a crash. Hacks can have catastrophic consequences.

(I’m not going to go in-depth into the hacks I was using under Tiger. But for the benefit of people who’s code has suddenly broken: The FZDaemon/FZDaemonListener protocols have been significantly changed in Leopard. Every function in FZDaemon is now oneway void for example. My attempts to get a connection to com.apple.iChatAgent were deadlocking. The same approach will not work on both Leopard and Tiger, as far as I can tell. The good news is that the Leopard APIs are a lot cleaner.)

Reverse Engineering iChat and InstantMessage.framework

The first thing I did was check to see if Leopard introduced a supported way of doing what I needed. There were some, like the IMMyStatusChangedNotification, but still no way to set the user’s status message. After reading, I knew what frameworks I needed to poke at. I also perused the iChat, and iChatAgent bundles for further clues.

I used class-dump, otx and strings on: iChat, iChatAgent, InstantMessage.framework, and IMUtils.framework (inside InstantMessage.framework). This gave me private interface declarations, disassembled code, and portentous strings to pour over.

I also tried using gdb on iChat, to see just how it was communicating with iChatAgent. This was not very productive for me at all. I got much further by statically analyzing disassembled code and interfaces. GDB can be a great tool, but you have to know a lot about what you are looking for before you can find it — otherwise you don’t know where to set breakpoints. I’ve noticed myself using gdb less and less over the last few years. (As silly as it sounds, gdb just hasn’t been as much fun after the intel switch, now that all the assembler is x86 goobely-garb.)

Trying things out in my own “tester” project really helped me puzzle out exactly what was going on, why, and how. I had to explicitly add the IMUtils.framework to the “Linked Frameworks” group to get some stuff to link.

I learned a lot by listening to any - (NSNotificationCenter*) notificationCenter that an object exposed. For example,

[[IMService notificationCenter] addObserver:self selector:@selector(iChatNotification:) name:nil object:nil];

where,
- (void) iChatNotification:(NSNotification*)notification{
NSLog(@"iChatNotification: %@", notification);
}

Will print every notification passing through the IMService notification center. Passing in a name: or object: parameter filters what notifications your method receives. Different classes often share the same notification center, so I would verify that one was distinct before printing all it’s notifications.

It turns out that the public [IMService notificationCenter] sends an undocumented notification named “IMMyInfoChangedNotification” when the user’s status message changes. However, the documented IMMyStatusChangedNotification appears to always be sent in such cases — I could not contrive an example where the status message was changed, and it was not sent.

InstantMessagePrivate.h will let you use access private interfaces in InstantMessage.framework. It is a refinement o the class-dump of InstantMessage.framework.

IMServiceAgentImpl is the most feature-rich class for controlling iChat (see also: IMServiceAgent). [IMServiceAgentImpl sharedAgent] will give you the shared instance. Some of the most useful methods are:
- (void)setMyStatus:(IMPersonStatus)statusType message:(NSString*)statusMessage;
- (NSString*) myStatusMessage;
- (NSData*) myPictureData;
- (void) setMyPictureData:(NSData*)newPictureData;
- (NSString*) myProfile;
- (void) setMyProfile:(NSString*)newProfile;
- (NSArray*)myAvailableMessages;
- (NSArray*)myAwayMessages;

Accessor methods will not work correctly, unless you have a connection to the iChatAgent daemon.
[[IMServiceAgentImpl sharedAgent] connectWithLaunch:YES];
[[IMServiceAgentImpl sharedAgent] _blockUntilConnected];
ensures that there is a connection to the daemon. Only calling connectWithLaunch: or _blockUntilConnected didn’t do it for me, I had to call both in order. There may be a better way to get a connection, but I’m not aware of it

Unfortunately, I have not yet figured out how to get iChat to set the status to invisible. Right now I’m concentrating to getting IMLocation working on Leopard, so I’ll look into this later.

iChat’s a damn big program for “just a chat client”, It’s executable and class-dump are actually about 2x as big as Safari’s.

InstantMessagePrivate.h

Advertisements
Explore posts in the same categories: Cocoa, Leopard, MacOSX, Objective-C, Programming, Research, Reverse Engineering, Sample Code

3 Comments on “Interfacing With (Hacking) iChat in Leopard”

  1. Michael Bishop Says:

    That’s a lot of work to go through just to set a status message. You could do the same thing with Applescript. AppleEvents might seem like a pain in the ass, but if you investigate the Scripting bridge, you can send apple events just like you were writiing cocoa code. And no hacks!

    http://awkward.org/2007/10/26/scriptingbridge
    http://www.apple.com/applescript/features/scriptingbridge.html
    http://developer.apple.com/documentation/Cocoa/Conceptual/ScriptingBridgeConcepts/Introduction/chapter_1_section_1.html

    Happy coding!


  2. “Before you consider using any of the unsupported hacks I’m about to discuss, check to see if existing frameworks , or iChat’s AppleScript interface, will do what you need. ”

    In previous versions of IMLocation I used NSAppleScript, and it worked, although it has issues.

    The reason I had to do all of this was because AppleScript can’t notify me when status changes in iChat — I have to constantly poll for it.

    Polling frequently wasted too much battery time.

    Polling infrequently was unacceptable, because if you changed your status message in iChat, it would change behind your back in a few seconds, when the change was finally detected.

    Even polling “just right” wasn’t good enough, mainly because there were still noticeable delays, but also because 5-minutes less battery life is the difference between being able to send an email or not.

    Unfortunately, this is the only way I know of to get the level of integration with iChat that I needed.


  3. […] the link between Xcode and Interface Builder. Very interesting, I learned a lot. I’ve done essentially the same thing with iChat. (And in retrospect it might have been a bad idea, because it’s broken on Snow […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: