Changing shared library information
⚓ 2009-12-11
I needed to manage some GStreamer plugins’ shared library locations in their binary. I looked into using libtool but it’s incredibly complicated. Fortunately, it seems that Apple provides a very useful tool to achieve this on the command line.
If you’re the type to think “shared libraries? pssh, you mean install names!” then this is the tool for you (also, you’re weird). It’s called install_name_tool. You can add, remove or change a shared library entry.
For example, a library has the following otool -L output:
$ otool -L libwhatchamajig.so
libwhatchamajig.so:
A/otherlib.so
…
Let’s say we wanted to change otherlib’s location from A/ to B/. This can be achieved by running the following command:
install_name_tool -change A/otherlib.so B/otherlib.so libwhatchamajig.so
This will change the otherlib location, and it results in the following:
$ otool -L libwhatchamajig.so
libwhatchamajig.so:
B/otherlib.so
…
Definitely saved me a world of pain to otherwise manage these plugins automatically.
⚓ 2009-12-08
The Gmail UI for managing Offline settings is atrocious. I can’t tell what label is set to which setting unless I read over the four columns.
Sure, it’s decently easy with 5 labels shown, but with even a few more it’s just a huge wall of text. Just using a radio <input> would make it leaps and bounds easier.
Exploiting an already-released API
⚓ 2009-12-04
Adium is a behemoth. With over 280,000 lines of code, Adium is the largest, most well-known open source project for the Mac.
Its API sucks.
If you’re working with Adium’s internals, trying to alter behavior in some way, you’re going to run into brick walls. Recently the principal controller AIContactController was rewritten for 1.4 by David Smith. This cleaned up a lot of the half-implemented APIs and cleaned up the problems in a Google Summer of Code student’s work. However, a lot of Adium’s API is still undocumented or poorly documented, it makes getting into development hard and is causing a world of troubles in the long run.
One of the more prominent problems present on instant messaging networks is spim. It’s an ever-growing, ever-problematic situation that should be dealt with on the network level. But it’s not; users still see it, predominately on MSN and Yahoo!. So I set out to try and combat it with a simple demonstrative plugin for Adium.
The problem is there’s no clear-cut way to monitor incoming messages in Adium other than using the filtering API. However, Adium does make use of NSNotifications in a wide variety of places that weren’t directly intended for the use of blocking messages.
Inside Adium’s AIContentController (what handles message display and filtering), it posts the following at the beginning of the process:
[[NSNotificationCenter defaultCenter] postNotificationName:Content_WillReceiveContent
object:chat
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:inObject, @"Object", nil]];
“Aha!” you might say. Yes, indeed. We’re going to exploit the fact that the notification is posted early to do some crazy trickery to Adium.
AIContentObject has a few properties, one of which is BOOL displayContent. When this is set to NO, a particular piece of content is run through the system but not displayed. Usually it’s used to prevent an already-made content object from being displayed.
One example in Adium proper code is for sending silently Direct IM messages after a connection was established. When you send an image to an AIM user, Adium tries to negotiate a Direct IM session; it waits until that session is established before it tries to send the image. However, when it’s established, the message is already displayed in the window; to prevent displaying it again, a silent-but-processed outgoing message is created.
In our mysterious content-blocking plugin, which evolved finally into Challenge/Response, we register to receive Content_WillReceiveContent notifications, and flip the displayContent bit where appropriate.
Eventually, the message will run through the various filters, and hit the check:
//Check if the object should display
if ([inObject displayContent] && ([[inObject message] length] > 0)) {
At which point, it gets silently dropped into the nether. Not so, however, in C/R. The object is tracked until a certain event happens, at which point the object is re-introduced back into Adium as though it were new:
[[adium contentController] receiveContentObject:contentObject];
The end result is a pretty nice flow:
-
The user receives a message from a stranger.
This is where the message is set to not displayContent. Normally it would be lost, but C/R maintains it.
- The stranger is forced to answer a “challenge”.
-
If the stranger ever successfully responds to the challenge, all messages sent before are delivered to the user with displayContent set to YES.
If the stranger never successfully responds to the challenge, the message is dropped.
“Oh no! Not lost!” No, not lost. Here comes another round of tricking Adium’s API into doing something it doesn’t quite want to do. Incoming hidden messages are silently logged into an invisible group chat.
Adium’s logging system largely depends upon having an AIChat and a path to write to. We mitigate these requirements by giving it something to work with.
Our first step is to create a fake chat with a given name. We end up needing to create one per account, so we store the open chats in an NSArray:
AIChat *chat = [openChats objectForKey:[account internalObjectID]];
// Chat doesn't already exist, we have to make our own!
if(!chat) {
chat = [AIChat chatForAccount:account];
[chat setName:CHALLENGE_RESPONSE_CHAT_NAME];
[chat setIsGroupChat:YES];
[openChats setObject:chat forKey:[account internalObjectID]];
}
By calling the -chatForAccount: method, instead of one of the more glorious, direct and proper AIInterfaceController methods, we skip the opening-a-chat step and just get our object to work with. Silent, unknown chat; horrible if ever exposed, decent for our purpose.
The problem is, if we introduce our original content object back into the normal AIContentController code, we’re going to have it open and display in a chat; this is far from what we want. These messages need to remain hidden!
Once again, we trick Adium into doing our bidding. The logging plugin, AILoggerPlugin, listens to notifications for when to log data. This is our way in without using even more hacky methods:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contentObjectAdded:)
name:Content_ContentObjectAdded
object:nil];
It turns out, this is exactly where we need to be. -contentObjectAdded: does the appropriate logging.
With a little hacking (setting displayContent to YES) and posting a Content_ContentObjectAdded notification, the logging plugin will log our content object in the “wrong” place but for the chat we want:
[[adium notificationCenter] postNotificationName:Content_ContentObjectAdded
object:chat
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:contentObject, @"AIContentObject", nil]];
If the content object ever needs to be displayed again, we introduce it normally. Nice, clean, and simple to maintain.
Exposing a public API to do these things would be in Adium’s best interest. Fun things like Bayesian filters could be used to do the heavy lifting and heuristic work. However, with marginal developer power and a large task of things to do, I wanted a quick and dirty plugin to get the job done, without having to do a new release of Adium to get the support working.
It’s not always important to go the clean route when doing something; if you’re able to sleep at night, pragmatism will get code out the door faster than layer upon layer of indirection.