Pidgin’s plugin development

เพราะว่าต้องเดินทางระหว่างบ้าน กับที่ทำงาน มหาวิทยาลัย อยู่บ่อยๆ  แต่ก็อยากให้ Friendly Name ของ MSN เปลี่ยนตามสถานที่ที่ไป (อาจจะให้ เปลี่ยนตามวง IP) จึงต้องเพิ่มเติมความสามารถ โดยพัฒนา Plugin เอง

ภาพรวมคร่าวๆคือ ต้อง ..

  • download source code package มาจาก source forge ของ pidgin ก่อน
  • จากนั้นเริ่ม ./configure และ make (ซึ่งจะทำให้ทราบว่าต้องลง package อะไรเพิ่มขึ้นอีก)
  • เข้าไปใน pidgin/plugins/ จะพบกับ plugins ตัวอื่นๆมากมาย (อาจถูก compile แล้วด้วยขั้นตอนที่ผ่านมา)
  • เมื่อต้องการพัฒนา plugins ใดเพิ่มให้ใส่ใน dir pidgin/plugins/<new file>.c แลัว compile ด้วย make <new file>.so
  • สำหรับ <new file>.so ที่ได้ ให้ใส่ไว้ใน ~/.purple/plugins/ เมื่อ run pidgin จะได้มองเห็น


  1. Basic C Plugin How-To – Start here. This covers the core basics needed to write a plugin. ทดลองสร้าง Plugin ตัวแรก พอกด Enable plugin แล้วจะเกิด pop-up window ขึ้น
  2. Plugin Actions How-To – This covers using the Plugin Actions facilities to provide additional features. เพิ่มเมนูย่อยในเมนูเครื่องมือ
  3. Choosing Plugin ID’s – This covers how to properly select a plugin ID.  วิธีการตั้งชื่อ Plugin โดยมีกติกาว่า  “type-user_name-plugin_name” .. ลองไปสมัครใน ดู
  4. Debug API How-To – This covers using the libpurple debug API to generate debugging messages for your plugin.
  5. Notify API How-To – This covers using the notify API to inform the user of events.
  6. Command API How-To – This covers using the command API to add text-based commands to libpurple clients.
  7. Request API How-To – This covers using the Request API to request input from the user.
  8. Signals How-To – This covers using signals to detect events and act accordingly.

เอาหล่ะ .. แล้วก็มาถึงเป้าหมายของผม .. และนี่คือ code ที่ได้

/*#define PURPLE_PLUGINS*/
/*#define USE_PYTHON*/
#define PLUGIN_ID         "core-ipas-autoalias"
#define PLUGIN_NAME        "Auto alias name"
#define PLUGIN_AUTHOR     "iPAS, Pasakorn Tiwatthanont "
#define PLUGIN_VERSION     "0.1"
#define PREF_PREFIX        "/plugins/core/" PLUGIN_ID
#define PREF_NET1       PREF_PREFIX "/net1"
#define PREF_NAME1      PREF_PREFIX "/name1"
#define PREF_NET2       PREF_PREFIX "/net2"
#define PREF_NAME2      PREF_PREFIX "/name2"
#define PREF_NET3       PREF_PREFIX "/net3"
#define PREF_NAME3      PREF_PREFIX "/name3"
#define PY_MODULE         "where"
#define PY_FUNC         "where_am_i"
#define PY_PATH         ".:"
#include <glib.h>
#include <string.h>
#include <unistd.h>
#include <python2.6/Python.h>
#include <internal.h>
#include <notify.h>
#include <plugin.h>
#include <debug.h>
#include <version.h>
#include <account.h>
/* we're adding this here and assigning it in plugin_load because we need
 * a valid plugin handle for our call to purple_notify_message() in the
 * plugin_action_test_cb() callback function */
PurplePlugin *autoalias_plugin = NULL;
// ----------------------------------------------------------------------------
/* This function is the callback for the plugin action we added. All we're
 * doing here is displaying a message. When the user selects the plugin
 * action, this function is called. */
static void
plugin_action_test_cb(PurplePluginAction *action)
 purple_debug_info(PLUGIN_ID, "Emit signed-on signal\n");
 purple_signal_emit(purple_plugins_get_handle(), "signed-on", autoalias_plugin);
/* we tell libpurple in the PurplePluginInfo struct to call this function to
 * get a list of plugin actions to use for the plugin.  This function gives
 * libpurple that list of actions. */
static GList *
plugin_actions(PurplePlugin *plugin, gpointer context)
 /* some C89 (a.k.a. ANSI C) compilers will warn if any variable declaration
 * includes an initilization that calls a function.  To avoid that, we
 * generally initialize our variables first with constant values like NULL
 * or 0 and assign to them with function calls later */
 GList *list = NULL;
 PurplePluginAction *action = NULL;
 /* The action gets created by specifying a name to show in the UI and a
 * callback function to call. */
 action = purple_plugin_action_new("Signed-on Test", plugin_action_test_cb);
 /* libpurple requires a GList of plugin actions, even if there is only one
 * action in the list.  We append the action to a GList here. */
 list = g_list_append(list, action);
 /* Once the list is complete, we send it to libpurple. */
 return list;
// ----------------------------------------------------------------------------
static void
get_public_alias_success_cb(PurpleAccount *account, const char *alias)
/*    char new_name[2000];*/
/*    sprintf(new_name, "%s @ world", alias);*/
/*    purple_account_set_public_alias(account, new_name, NULL, NULL);*/
/*    return; // There's some error, so skip this for now*/
 char *oldpath, *newpath;
 PyObject *py_name, *py_func, *py_arg, *py_module, *py_val;
 if ((oldpath = getenv("PYTHONPATH")) == NULL) 
 oldpath = "";        
 newpath = (char *)malloc(strlen(oldpath) + 80 );
 strcpy(newpath, PY_PATH);
 strcat(newpath, purple_user_dir());
 strcat(newpath, "/plugins");    
 strcat(newpath, oldpath);
 setenv("PYTHONPATH", newpath, 1);
 Py_Initialize(); // Python interpreter initial
 py_name   = PyString_FromString(PY_MODULE); // Load module
 py_module = PyImport_Import(py_name);
 if (py_module == NULL) // Check module loading
 purple_debug_info(PLUGIN_ID, "Cannot load Python module: \'"PY_MODULE".py\'\n");
 purple_debug_info(PLUGIN_ID, "Python path> %s\n", Py_GetPath());
 newpath = (char *)malloc(1024);        
 if (getcwd(newpath, 1024) == newpath)                    
 purple_debug_info(PLUGIN_ID, "Working dir> %s\n", newpath);        
 purple_debug_info(PLUGIN_ID, "purple_user_dir()> %s\n", purple_user_dir());        
 Py_Finalize(); // Close Python interpreter        
 py_func = PyObject_GetAttrString(py_module, PY_FUNC); // Check function loading
 if (py_func == NULL || !PyCallable_Check(py_func))
 purple_debug_info(PLUGIN_ID, "Cannot load Python function: \'"PY_FUNC"\'\n");
 Py_Finalize(); // Close Python interpreter        
 py_arg = Py_BuildValue("s", alias); 
 if (py_arg == NULL)
 purple_debug_info(PLUGIN_ID, "Python argument error\n");
 py_val = PyObject_CallFunctionObjArgs(py_func, py_arg, NULL); // Call function 
 if (py_val != NULL) // Check return value         
 {   // Return value from Python        
 char *ret;
 ret = PyString_AsString(py_val); // A new alias                                 
 purple_debug_info(PLUGIN_ID, "Have set public_alias> \'%s\'\n", ret);        
 purple_account_set_public_alias(account, ret, NULL, NULL);
 purple_debug_info(PLUGIN_ID, "Python call error\n");
 Py_Finalize(); // Close Python interpreter        
// ----------------------------------------------------------------------------
static void
signed_on_cb(PurpleConnection *gc)
 char *str;
 PurpleAccount *account        = purple_connection_get_account(gc);
 const char    *user_name   = purple_account_get_username(account);
 const char    *protocol_id = purple_account_get_protocol_id(account);
 str = (char *)malloc(strlen(user_name) + strlen(protocol_id) + 40);
 sprintf(str, "Account connected: \"%s\" (%s)\n", user_name, protocol_id);
//    purple_notify_message(autoalias_plugin, PURPLE_NOTIFY_MSG_INFO, PLUGIN_ID, 
//            str, NULL, NULL, NULL);
 purple_debug_info(PLUGIN_ID, "%s", str);
 purple_account_get_public_alias(account, get_public_alias_success_cb, NULL);
// ----------------------------------------------------------------------------
static gboolean
plugin_load(PurplePlugin *plugin) {
 static int handle;
 purple_signal_connect(purple_connections_get_handle(), "signed-on", &handle,
 PURPLE_CALLBACK(signed_on_cb), NULL);
 autoalias_plugin = plugin; /* assign this here so we have a valid handle later */
 return TRUE;
// ----------------------------------------------------------------------------
static gboolean
plugin_unload(PurplePlugin *plugin)
 return TRUE;
// ----------------------------------------------------------------------------
static PurplePluginPrefFrame *
get_plugin_pref_frame(PurplePlugin *plugin)
 PurplePluginPrefFrame *frame;
 PurplePluginPref      *pref;
 frame = purple_plugin_pref_frame_new();
 pref = 
 purple_plugin_pref_new_with_name_and_label(PREF_NET1, _("Net1:"));
 purple_plugin_pref_frame_add(frame, pref);
 pref = 
 purple_plugin_pref_new_with_name_and_label(PREF_NAME1, _("Name1:"));
 purple_plugin_pref_frame_add(frame, pref);
 return frame;
// ----------------------------------------------------------------------------
static PurplePluginUiInfo prefs_info = {
 /* padding */
static PurplePluginInfo info = {
 PURPLE_PLUGIN_MAGIC,    /* Plugin magic, this must always be
 PURPLE_MAJOR_VERSION,   /* This is also defined in libpurple.  It helps
 libpurple's plugin system determine which version
 of libpurple this plugin was compiled for, and
 whether loading it will cause problems. */
 PURPLE_MINOR_VERSION,   /* See previous */
 PURPLE_PLUGIN_STANDARD, /* PurplePluginType: There are 4 different values for
 this field.  The first is PURPLE_PLUGIN_UNKNOWN,
 which should not be used.  The second is
 PURPLE_PLUGIN_STANDARD; this is the value most
 plugins will use. Next, we have PURPLE_PLUGIN_LOADER;
 this is the type you want to use if your plugin will
 make it possible to load non-native plugins.  For
 example, the Perl and Tcl loader plugins are of this
 type.  Last, we have PURPLE_PLUGIN_PROTOCOL.  If your
 plugin is going to allow the user to connect to
 another network, this is the type you'd want to use. */
 NULL,                   /* This field is the UI requirement.  If you're writing
 a core plugin, this must be NULL and the plugin must
 not contain any UI code.  If you're writing a Pidgin
 plugin, you need to use PIDGIN_PLUGIN_TYPE.  If you
 are writing a Finch plugin, you would use
 0,                      /* This field is for plugin flags.  Currently, the only
 flag available to plugins is invisible
 (PURPLE_PLUGIN_FLAG_INVISIBLE). It causes the plugin
 NOT to appear in the list of plugins. */
 NULL,                   /* This is a GList of plugin dependencies.  In other words,
 it's a GList of plugin id's that your plugin depends on.
 Set this value to NULL no matter what.  If your plugin
 has dependencies, set them at run-time in the
 plugin_init function. */
 PURPLE_PRIORITY_DEFAULT,/* This is the priority libpurple will give your plugin.
 There are three possible values for this field,
 PLUGIN_ID,                 /* This is your plugin's id.  There is a whole page dedicated
 to this in the Related Pages section of the API docs. */
 PLUGIN_NAME,             /* This is your plugin's name.  This is what will be
 displayed for your plugin in the UI. */
 PLUGIN_VERSION,            /* This is the version of your plugin. */
 "Automatic change alias name when logon.",
 /* This is the summary of your plugin.  It should be a short
 blurb.  The UI determines where, if at all, to display
 this. */
 "Automatic change alias name when logon.",
 /* This is the description of your plugin. It can be as long
 and as descriptive as you like.  And like the summary,
 it's up to the UI where, if at all, to display this (and
 how much to display). */
 PLUGIN_AUTHOR,             /* This is where you can put your name and e-mail
 address. */
 PLUGIN_HOMEPAGE,        /* This is the website for the plugin.  This tells users
 where to find new versions, report bugs, etc. */
 plugin_load,            /* This is a pointer to a function for libpurple to call when
 it is loading the plugin.  It should be of the type:
 gboolean plugin_load(PurplePlugin *plugin)
 Returning FALSE will stop the loading of the plugin.
 Anything else would evaluate as TRUE and the plugin will
 continue to load. */
 plugin_unload,          /* Same as above except it is called when libpurple tries to
 unload your plugin.  It should be of the type:
 gboolean plugin_unload(PurplePlugin *plugin)
 Returning TRUE will tell libpurple to continue unloading
 while FALSE will stop the unloading of your plugin. */
 NULL,                   /* Similar to the two above members, except this is called
 when libpurple tries to destory the plugin.  This is
 generally only called when for some reason or another the
 plugin fails to probe correctly.  It should be of the type:
 void plugin_destroy(PurplePlugin *plugin) */
 NULL,                   /* This is a pointer to a UI-specific struct.  For a Pidgin
 plugin it will be a pointer to a PidginPluginUiInfo
 struct, for example. */
 NULL,                   /* This is a pointer to either a PurplePluginLoaderInfo
 struct or a PurplePluginProtocolInfo struct, and is
 beyond the scope of this document. */
 &prefs_info,            /* This is a pointer to a PurplePluginUiInfo struct.  It is
 a core/ui split way for core plugins to have a UI
 configuration frame.  You can find an example of this
 code in libpurple/plugins/pluginpref_example.c */
 plugin_actions,         /* This is a function pointer where you can define "plugin
 actions".  The UI controls how they're displayed.  It
 should be of the type:
 GList *function_name(PurplePlugin *plugin, gpointer context)
 It must return a GList of PurplePluginActions. */
 NULL,                   /* This is a pointer reserved for future use.  We set it to
 NULL to indicate we don't need it. */
 NULL,                   /* This is a pointer reserved for future use.  We set it to
 NULL to indicate we don't need it. */
 NULL,                   /* This is a pointer reserved for future use.  We set it to
 NULL to indicate we don't need it. */
 NULL                    /* This is a pointer reserved for future use.  We set it to
 NULL to indicate we don't need it. */
// ----------------------------------------------------------------------------
static void
init_plugin(PurplePlugin *plugin) // Initialize at first time pidgin met plugin
 purple_prefs_add_string(PREF_NET1, "");        
 purple_prefs_add_string(PREF_NET2, "");        
 purple_prefs_add_string(PREF_NET3, "");        
// ----------------------------------------------------------------------------
PURPLE_INIT_PLUGIN(auto_alias, init_plugin, info)

ตั้งใจไว้ว่าหากว่างๆ วันใดจะแก้ให้ดีขึ้น แล้ว contribute ไว้ใน Google Code



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

You are commenting using your account. Log Out / เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out / เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out / เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out / เปลี่ยนแปลง )

Connecting to %s