it's a radish

Thursday, September 25, 2008

Picnic Fail

I just got an email from the CS dept:
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.

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.c
 1  #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:

cocoa-bundle

We'll go ahead and stick our new project in the same root directory as our adium source:

mute-directory

(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):

cocoa-class

We'll be using this new class to invoke our existing C code, as a wrapper, so name it appropriately

mute-wrapper

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.h
1  #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.m
 1  #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):

import-file

Now, we need to add a bunch of Frameworks to our project, so we they're available to link with:

add-existing-frameworks

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:

add-script-phase

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:

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.
target-info

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.
build-settings

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

In which Noah learns how to embed quicktime...

This looks pretty neat...

Sunday, February 25, 2007

February Reading List

Viriconium by M. John Harrison. Started it in January, but never finished. More a collection of related novelletes than anything else. Well written, but dense, which made it hard to hold my interest on my commute. The ... I guess baroqueness is the word I want http://www2.blogger.com/img/gl.link.gifto use, reminds me a lot of China Mieville. Who has a coming out. Which is a little weird. The Last Continent by Terry Pratchett. A reread. Pratchett's always fun. I suspect I would find this one funnier if I knew more about Australia. Myth Conceptions by Robert Asprin. I'd read another one in this series a long time ago (Myth-ing Persons, since I still own it), but I think the time when I would have really enjoyed this has passed. Ah, childhood days spent reading & rereading Xanth books.... To Reign in Hell by Steven Brust. I really enjoyed Brokedown Palace, so when I saw this on Brian & Becky's shelves, I picked it up. I did ask first. A good religious fantasy. I'd use a reference to "Sympathy for the Devil" here, since that what it is, in effect, but I think Brust uses the reference himself. This is all out of order and stuff, but I forgot to put down The Stupidest Angel, by Christopher Moore, which Greg lent me. A christmas story, with zombies. Not to mention a post-apocalyptic warrior woman, talking fruitbat, and a good way to create masochistic rats. Funny stuff, as always. Just finished The Book of Jhereg, which is more Steven Brust. Actually three books: Jhereg, Yendi, and Teckla. Not as good as the rest of his stuff I've read recently, but fun, and actiony. Plus, it's kind of interesting to be reading a fantasy main character who's, you know, married. And working at it.

Friday, February 09, 2007

Nesting C functions on OSX

Frustrated. Why? Because 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\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 status
The 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.

Tuesday, January 16, 2007

biting the bullet

Ok, so, if my end goal is web access to my tivo (series 1) programs, then I need something like this. (Found via this mostly content-less blog post. I mean, really, am I missing something? Having to read the screenshot is totally uncool). To install that, first I need telnet/ftp access to my tivo, and here's a guide for that. To set up telnet/ftp access I need to back up the drive, which requires a new drive (which might as well be larger, while I'm at it), and a pc running linux.

Sunday, January 14, 2007

Movies I've seen this weekend.

Children of Men: Saw it last night (friday the 12th) w/ Karlee, J.R., Jenny, Josh, & Deborah. Good, smart science fiction. Not the rockets and blasters type, the make you think type. Which is cool. I like this trend of "make you think" sci-fi movies. Although, all I can think of right now is Eternal Sunshine. One of the cool directorial aspects was that each and every death came as a shock. Despite the violence, the director never let you numb to the deaths. Pan's Labyrinth: Cool fantasy from Guillermo Del Toro. It's in spanish, subtitled, and limited release, so I know that'll prevent some folks I know from seeing it, but it's really good. It's got amazing effects, and the story is like a fairy tale, in that it's bloody and beautiful, and horrible and beautiful things happen. Saw it at the AFI, which I should go to more often. Come to think of it, I should go to more movies, cause I like that too.