Due to the bad weather that is expected to arrive today and remain through Friday, we will not be holding the picnic at Acredale Park on September 26. On Friday, tables will be set-up in the CSIC lobby to serve food between 12:30pm and 2:30pm. Since classes will be in session during morning hours and again at 1:30pm, no eating in CSIC will be permitted and we need to keep the noise level low so that classes are not disturbed. We ask that you return to your offices, the graduate lounge or any available conference room to eat. Unfortunately, since we will not be outdoors, there will not be any activities.So the picnic will be indoors. You can't eat at the picnic, nor can hang out there. Breaking my previous assumptions about minimum requirements for picnic-hood. I actually find this rather hilarious.
it's a radish
Thursday, September 25, 2008
Picnic Fail
Wednesday, September 17, 2008
HOWTO: Wrap a libpurple plugin for Adium
The core of the chat client Pidgin (formerly Gaim) and its command-line cousin Finch is a library called libpurple. This library handles all of the various chat protocols so that Pidgin, Finch, and other chat clients need merely to provide a GUI. It is also used by the Macintosh chat app Adium.
In this post, I'm going to discuss how to write, compile, and install a plugin for libpurple. Plugins in libpurple can extend or modify the behaviour of the library - even up to adding additional chat protocols, though in this post we'll work with a simpler example.
There's at least one good article on how to write a C plugin for libpurple that can be used with Pidgin or Finch. Installing plugins for pidgin or finch is easy - just drop the <plugin-name>.so file in ~/.purple/plugins.
Where there's a dearth of information is how to make a libpurple plugin that can be used with Adium, which is what this post will try to cover.
The Libpurple Plugin
First, let's list our libpurple plugin code. Compiled correctly, it could be used with Pidgin or Finch. Our goal, however, is to use it in Adium.
Let's create a plugin that mutes conversation - just converts all the instant messages we receive to all lowercase. There's too much shouting in chat anyway.
mute.c1 #define PURPLE_PLUGINS 2 3 #include <glib.h> 4 5 #include <plugin.h> 6 #include <version.h> 7 8 #include <signals.h> // purple_signal_connect() 9 #include <account.h> // PurpleAccount 10 #include <conversation.h> // purple_conversations_get_handle(), 11 // PurpleConversation 12 13 #include <ctype.h> // tolower() 14 15 static gboolean 16 receiving_im_msg_cb( 17 PurpleAccount * account, 18 char ** sender, 19 char ** message, 20 PurpleConversation * conversation, 21 PurpleMessageFlags * flags, 22 void * thunk 23 ) { 24 char *p; 25 26 // convert the message to lowercase 27 for (p = *message; *p; p++) { 28 *p = tolower(*p); 29 } 30 31 return FALSE; 32 } 33 34 static gboolean 35 plugin_load(PurplePlugin *plugin) { 36 37 // hook all im's before they're displayed 38 purple_signal_connect( 39 purple_conversations_get_handle(), 40 "receiving-im-msg", 41 plugin, 42 PURPLE_CALLBACK( receiving_im_msg_cb ), 43 NULL /* our thunk */); 44 45 return TRUE; 46 } 47 static gboolean 48 plugin_unload(PurplePlugin *plugin) { 49 50 purple_signal_disconnect( 51 purple_conversations_get_handle(), 52 "receiving-im-msg", 53 plugin, 54 PURPLE_CALLBACK( receiving_im_msg_cb )); 55 56 return TRUE; 57 } 58 59 static PurplePluginInfo info = { 60 PURPLE_PLUGIN_MAGIC, 61 PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, 62 PURPLE_PLUGIN_STANDARD, 63 NULL, 0, NULL, 64 PURPLE_PRIORITY_DEFAULT, 65 66 "core-mute", 67 "Mute", 68 "1.0", 69 70 "Mutes incoming IMs by converting uppercase to lowercase.", 71 "Mutes incoming IMs by converting uppercase to lowercase.", 72 "Noah Easterly <noah@mailinator.com>", 73 "http://rampion.blogspot.com", 74 75 plugin_load, // our startup hook 76 plugin_unload, // our shutdown hook 77 NULL, 78 79 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL 80 }; 81 82 static void 83 init_plugin(PurplePlugin *plugin) { } 84 85 // magic macro that gets us run 86 PURPLE_INIT_PLUGIN(mute, init_plugin, info)
Normally, we would compile this for Pidgin just by
- downloading a copy of the pidgin source
- running ./configure
- placing a copy of mute.c in libpurple/plugins/
- running make mute.so in libpurple/plugins/
and install it by placing mute.so in ~/.purple/plugins/
Notice that this plugin hooks into all incoming IMs by using a signal. In libpurple (and Pidgin and Finch), messages are passed around by signals. Each signal has an emitter (which is just a unique id), an id (which is just a string), and zero or more receivers (again, just a unique id).
Since the signals documentation is still in progress, I'll take a moment to talk about them. Here's the complete list of signals in pidgin 2.4.2. If you're using a later version, you can find this list by just grepping through the source for purple_signal_register(), which is how signals are registered by their emitters.
Each call to purple_signal_register() specifies:
- the emitter,
- the signal id,
- the type of the callback function,
- the type of the return value of the callback function,
- the number of arguments to pass to the callback function (other than the thunk, which I'll discuss in a second),
- the types of the arguments to pass to the callback function.
Each callback gets one extra argument - a thunk. This thunk is just a (void *) pointer that was specified when you connected to the signal. It's how you can maintain state between calls without using globals.
For example, compare the signature of the "receiving-im-msg" signal:
purple_signal_register(handle, "receiving-im-msg",
purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_POINTER,
purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
purple_value_new(PURPLE_TYPE_SUBTYPE,
PURPLE_SUBTYPE_ACCOUNT),
purple_value_new_outgoing(PURPLE_TYPE_STRING),
purple_value_new_outgoing(PURPLE_TYPE_STRING),
purple_value_new(PURPLE_TYPE_SUBTYPE,
PURPLE_SUBTYPE_CONVERSATION),
purple_value_new_outgoing(PURPLE_TYPE_UINT));
with our callback function:
static gboolean
receiving_im_msg_cb(
PurpleAccount * account,
char ** sender,
char ** message,
PurpleConversation * conversation,
PurpleMessageFlags * flags,
void * thunk
) { ... }
It's a little tricky to figure out how we got all those types from that signature. The easy way to do it is of course to cheat. Grep through the source, and look for purple_signal_emit() calls with the desired signal id - that will tell you exactly what's getting passed in (except for the thunk).
Wrapping it for Adium
There is some documentation on how to write Adium plugins, but it's certainly not as complete as it needs to be, or could be.
To create an Adium plugin, we're going to need a copy of the Adium source. Adium and Adium plugins are written in Objective C. If you're not familiar with Objective C, and you don't feel like learning it right now, be not afraid. It's really just a couple concepts tossed on top of regular C.
- classes are declared in @interface ... @end blocks
- and implemented in @implementation ... @end blocks
- methods are called using [object method:arg1 with:arg2 and_with:arg3] notation (where the instance method is method:with:and_with:).
- classes can have super classes and can implement protocols (abstract collections of declared but undefined methods).
So, we'll need to fire up XCode. First, we'll open the Adium project from our Adium source (in adium-1.2.5/Adium.xcodeproj), and build that (for Development). This will give us the necessary headers and such that our plugin will need. Then we'll create a new project (File→New Project...), in this case a Cocoa bundle:
We'll go ahead and stick our new project in the same root directory as our adium source:
(So in this case our adium source is in ~/Projects/adium-1.2.5).
First, we'll create our wrapper code. We'll create a new cocoa class (File→New File):
We'll be using this new class to invoke our existing C code, as a wrapper, so name it appropriately
This code is mostly skeletal. In the .h file we'll be declaring our MuteWrapper class - it's subclass of AIPlugin (Adium Plugin) that implements the AILibpurplePlugin protocol. What that does is provide hooks for Adium to run the plugin when libpurple is ready.
MuteWrapper.h1 #import <Adium/AIPlugin.h> 2 #import <AdiumLibpurple/AILibpurplePlugin.h> 3 4 @interface MuteWrapper : AIPlugin <AILibpurplePlugin> {} 5 @end
In our .m file, we need to implement the necessary hooks expected: installLibpurplePlugin (which is called before libpurple is fully ready - so we can't register for signals yet) and loadLibpurplePlugin (which is called after libpurple is ready).
MuteWrapper.m1 #import "MuteWrapper.h" 2 3 @implementation MuteWrapper 4 - (void) installLibpurplePlugin 5 { 6 } 7 - (void) loadLibpurplePlugin 8 { 9 purple_init_mute_plugin(); 10 } 11 @end
The function purple_init_mute_plugin() was defined in mute.c by the PURPLE_INIT_PLUGIN(mute, init_plugin, info) call at the end when PURPLE_STATIC_PRPL is defined at compile time.
The last piece of code we'll need is mute.c - which we already wrote, so we'll just import that into the project (Project→Add to Project):
Now, we need to add a bunch of Frameworks to our project, so we they're available to link with:
From adium-1.2.5/build/Developement/ add
- Adium.framework
- AdiumLibpurple.framework
- AIUtilities.framework
and from adium-1.2.5/Frameworks/ add
- FriBidi.framework
- libglib.framework
- libgmodule.framework
- libgobject.framework
- libgthread.framework
- libintl.framework
- libmeanwhile.framework
- libpurple.framework
The last bit we'll need to do is a bit strange - in order to link correctly, we need to hack in a change to the linker paths. We can do this by adding a new script phase to our target:
The script we need to add is fairly simple. Just copy and paste the following. However, be certain to update it if your version of Adium uses a version of libpurple besides 0.4.1, as this is hardcoded into the script
script.sh#Make this change for every new version of libpurple in Adium source /usr/bin/install_name_tool -change "@executable_path/../Frameworks/libpurple.framework/Versions/0.4.1/libpurple" "@executable_path/../Frameworks/libpurple.framework/libpurple" "$TARGET_BUILD_DIR/$TARGET_NAME.$WRAPPER_EXTENSION/Contents/MacOS/$TARGET_NAME"
Next, we'll need to change some project settings, so go to Project→Edit Active Target:
First we'll need to set some information in the Properties tab. The important things to do here is to
- set Creator to AdIM - not sure why yet, but if I figure out why, I'll let you know.
- set Principal Class name as MuteWrapper, the class we be used to wrap our libpurple plugin.
Now we move over to the Build tab, and change more settings (beware trailing spaces):
- add a new User-defined setting for ADIUM as ../adium-1.2.5
- we'll be using this a bunch, so it's convenient. - set Architectures (ARCHS) as $(NATIVE_ARCH_32_BIT)
- I get bugs otherwise, not sure why. - set SDK Path (SDKROOT) as $(DEVELOPER_SDK_DIR)/MacOSX10.4u.sdk
- Adium is a universal binary, so we need to use the right sdk. - set Header Search Paths (HEADER_SEARCH_PATHS) as $(ADIUM)/Frameworks/libpurple.framework/Versions/0.4.1/Headers $(ADIUM)/Frameworks/libglib.framework/Headers/glib-2.0
- most of our headers are in adium. - set Framework Search Paths (FRAMEWORK_SEARCH_PATHS) as $(SDKROOT)/System/Library/Frameworks $(ADIUM)/build/Development $(ADIUM)/Frameworks
- same for our frameworks. - set Wrapper extension (WRAPPER_EXTENSION) as AdiumLibpurplePlugin
- different than the normal AdiumPlugin extension, this loads us after libpurple is ready, and makes sure the AILibpurplePlugin hooks are called. - set Installation Directory (INSTALL_PATH) as "$(HOME)/Library/Application Support/Adium 2.0/PlugIns/"
- this is where Adium plugins live. - set Other C Flags (OTHER_CFLAGS) as -DPURPLE_STATIC_PRPL
- this makes mute.c generate purple_init_mute_plugin(), so we can call it from MuteWrapper.m.
That's it. Now we just build, and double-click on our completed product to install the Mute.AdiumLibpurplePlugin in Adium.
Thursday, March 01, 2007
Sunday, February 25, 2007
February Reading List
Friday, February 09, 2007
Nesting C functions on OSX
#include <stdio.h>
void outerfunc(void (*innerfunc)(void))
{
printf("in outermost\n");
innerfunc();
}
int main(int argc, char * argv)
{
int x = 0;
void callback(void)
{
printf("in callback\n");
}
outerfunc(callback);
return 0;
}
compiles and runs fine, and this:
#include <stdio.h>
void outerfunc(void (*innerfunc)(void))
{
printf("in outermost\n");
innerfunc();
}
int main(int argc, char * argv)
{
int x = 0;
void callback(void)
{
printf("in callback(x = %d)\n", x);
}
outerfunc(callback);
return 0;
}
gives me this:
% gcc -fnested-functions test3c.c /usr/bin/ld: Undefined symbols: ___trampoline_setup collect2: ld returned 1 exit statusThe only article I've found about ___trampoline_setup explains the reason for trampolines, and gives a .asm file that defines ___trampoline_setup, but knowing these things hasn't helped me. Either I'm supposed to compile the .asm file in somehow, which I'm not sure how to do, or I have a different problem.

