= D-Bus Howto =

[[TOC(inline)]]

== Introduction ==

D-Bus is a way for applications to communicate with one another, and with the rest of the world. It is a [http://www.freedesktop.org/ freedesktop.org] project; see [http://www.freedesktop.org/wiki/Software/dbus here] and see the [http://www.freedesktop.org/wiki/IntroductionToDBus Introduction to D-Bus]. A great amount of Pidgin's functionality is exposed through D-Bus, so you can do a lot with a script that uses D-Bus to communicate with a running Pidgin. D-Bus has bindings for many languages; this means you can write such a script in many languages including Python, Perl, C++, Java, Ruby, and Haskell. Using D-Bus instead of a Pidgin plugin makes it easier to communicate with programs other than Pidgin, and it is harder to crash Pidgin from a D-Bus script. The methods of Pidgin's D-Bus interface have the same names as the functions in the Pidgin source; so any familiarity with Pidgin gained from writing D-Bus scripts can be carried over to writing plugins as well.

There are two kinds of communication/interaction that can happen over D-Bus:
 * Emitting signals / listening to signals
 * Calling methods / replying to method calls

You do the listening and calling; Pidgin does the emitting and replying.

== Setup ==

To interact with Pidgin you need to get a reference to Pidgin's D-Bus interface.

{{{
#!python
from pydbus import SessionBus

bus = SessionBus()
purple = bus.get("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
}}}
== Listening to signals ==

Many Pidgin events generate corresponding signals, and by listening to them, you can gain a fair idea of what's going on, and react appropriately.

To be notified when a signal (e.g. the "received-im-msg" signal) is emitted, you must "register" with D-Bus as a receiver of that signal. You do this with something like this (the code is in Python; consult your language's D-Bus bindings for the equivalent):
{{{
#!python
purple.ReceivedImMsg.connect(my_func)
}}}

[Note that the signal is called "received-im-msg" in the Pidgin documentation but the D-Bus signal is called "!ReceivedImMsg"; do this simple transformation for all signals you see.]

This registers a function, called `my_func`, which will be called when the signal is emitted. Now you look up the documentation to see what parameters come with the signal, and you define `my_func` to work on those parameters. In this case, the [/doxygen/dev2.x.y/html/conversation-signals.html#received-im-msg documentation] tells you that the parameters are (account, sender, message, conversation, flags), so you define `my_func` accordingly:
{{{
#!python
def my_func(account, sender, message, conversation, flags):
    print(sender, "said:", message)
}}}

There is a little more code you'll have to write to set up D-Bus etc. You put everything together in a D-Bus script like this:
{{{
#!python
#!/usr/bin/env python

def my_func(account, sender, message, conversation, flags):
    print(sender, "said:", message)

from pydbus import SessionBus
from gi.repository import GObject

bus = SessionBus()
purple = bus.get("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
purple.ReceivedImMsg.connect(my_func)

GObject.MainLoop().run()
}}}

Obviously,
 * you should name your function something more descriptive than `my_func` (`received_im_msg_cb` is a good convention, cb for "callback"); and
 * a D-Bus script that simply prints what you could see in the client anyway is rather useless. 

But you get the idea.

Here's a sample list of signals you can receive. For the full list, look in pages named "Signals" in the [/doxygen/dev2.x.y/html/pages.html list of documentation pages].

 * [/doxygen/dev2.x.y/html/account-signals.html Account signals]:
    * account-status-changed, account-connecting, account-error-changed, account-authorization-requested
 * [/doxygen/dev2.x.y/html/blist-signals.html Buddy list signals]:
    * buddy-status-changed, buddy-idle-changed, buddy-signed-on, buddy-signed-off, buddy-icon-changed
 * [/doxygen/dev2.x.y/html/connection-signals.html Connection signals]:
    * signing-on, signed-on, signing-off, signed-off, connection-error
 * [/doxygen/dev2.x.y/html/conversation-signals.html Conversation signals]:
    * These are emitted for events related to conversations you are having. Note that in Pidgin, "im" means a conversation with one person, and "chat" means a conversation with several people.
    *  writing-im-msg, wrote-im-msg,
    *  sending-im-msg, sent-im-msg,
    *  receiving-im-msg, received-im-msg,
    *  buddy-typing, buddy-typing-stopped,
    *  
    *  writing-chat-msg, wrote-chat-msg,
    *  sending-chat-msg, sent-chat-msg,
    *  receiving-chat-msg, received-chat-msg,
    *  chat-buddy-joining, chat-buddy-joined, chat-buddy-leaving, chat-buddy-left,
    *  chat-buddy-flags, chat-inviting-user, chat-invited-user,
    *  chat-invited, chat-joined, chat-left,
    *  chat-topic-changed,
    *  conversation-created, conversation-updated, deleting-conversation,
    *  conversation-extended-menu
 * [/doxygen/dev2.x.y/html/core-signals.html Core signals]:
    * quitting
 * [/doxygen/dev2.x.y/html/gtkconv-signals.html GtkConv signals]: 
    *  displaying-im-msg, displayed-im-msg,
    *  displaying-chat-msg, displayed-chat-msg,
    *  conversation-switched,
    *  conversation-hiding, conversation-displayed
 * [/doxygen/dev2.x.y/html/gtkimhtml-signals.html GtkIMHTML signals]: 
    * GtkIMHTML is the widget used for the input area in Pidgin
    * url_clicked, format_buttons_update, format_function_clear, format_function_toggle, format_function_update
 * [/doxygen/dev2.x.y/html/notify-signals.html Notification signals]:
    * displaying-userinfo, displaying-email-notification, displaying-emails-notification
 * [/doxygen/dev2.x.y/html/plugin-signals.html Plugin signals]:
    * plugin-load, plugin-unload
 * [/doxygen/dev2.x.y/html/savedstatus-signals.html Saved status signals]: 
    * savedstatus-changed
 * [/doxygen/dev2.x.y/html/sound-signals.html Sound signals]: 
    * playing-sound-event
 * [/doxygen/dev2.x.y/html/xfer-signals.html File transfer signals]: 
    * file-recv-complete, file-recv-request, file-send-accept, file-send-start, file-send-cancel, file-send-complete

=== Aside: Signals in Pidgin plugins ===
In an external D-Bus script, the only use of these signals is for getting information. In a plugin loaded by Pidgin, if you register a function as listening to a particular signal, your function will be called at the time the signal is emitted, and in some cases Pidgin might use the return value of your function to decide what to do. 

For example, the signal "playing-sound-event" is emitted just before a sound is played. Its documentation says
{{{
#!c
gboolean (*playing_sound_event)(PurpleSoundEventID event, PurpleAccount *account);
}}}
and says that if your registered function returns TRUE, Pidgin will cancel playing the sound.

As another example, the signal "chat-invited" is emitted when you are invited to a chat.  Its documentation says its type is
{{{
#!c
gint (*chat_invited)(PurpleAccount *account, const char *inviter,
                     const char *chat, const char *invite_message
                     const GHashTable *components);
}}}
and says that if you return an integer less than 0, the invitation will be rejected, if you return an integer greater than 0 it will be accepted, and if you return 0 it will proceed with the default, which is to ask the user.

Your registered callback function in a plugin might also have an effect on Pidgin by changing the values of parameters it gets.

You can't do any of this in a D-Bus script. Look at [/doxygen/dev2.x.y/html/signal-howto.html Signals HOWTO] to see how to use signals in plugins. 

Even with read-only plugins, there is a lot you can do:
 * When messages are received, notify the user using a system-wide notification system (e.g., [http://growl.info Growl] on OS X, or passing the message through a text-to-speech system and playing it.)
 * When a status message is set in Pidgin, connect to Twitter and update a Twitter account with the Pidgin status message.
 * Log all the status messages of a particular buddy.
 * Calculate the average length of (or delay between) messages sent and received.
 * ''Et cetera'': I'm sure you can think of a lot of semi-useful things to do.

Of course, your script doesn't have to be read-only; you can also actually ''do'' things:

== Calling Pidgin methods == 

Most of Pidgin's functions can be called through the D-Bus interface. The D-Bus functions have similar names as the C functions, and this is not unlike writing actual Pidgin plugins in C or Perl or Tcl instead.

For example, to send a message to all your IM windows, you can do:
{{{
#!python
for conv in purple.PurpleGetIms():
    purple.PurpleConvImSend(purple.PurpleConvIm(conv), "Ignore.")
}}}

Sometimes things are not direct. To set your status message without changing the status, for example, you need to use five calls:
{{{
#!python
def set_message(message):
    # Get current status type (Available/Away/etc.)
    current = purple.PurpleSavedstatusGetType(purple.PurpleSavedstatusGetCurrent())
    # Create new transient status and activate it
    status = purple.PurpleSavedstatusNew("", current)
    purple.PurpleSavedstatusSetMessage(status, message)
    purple.PurpleSavedstatusActivate(status)
}}}
And this still does unexpected things when your current status is a complex one.

The only reference for the available functions is the [/doxygen/dev2.x.y/html/files.html documentation for the header files], so you want to look there to figure out which functions you need to call.

Here are a few of the useful ones:

 * [/doxygen/dev2.x.y/html/account_8h.html Your accounts]: 
   * Get a particular account: `PurpleAccountsFind(name, protocol)`
   * Get all active accounts: `PurpleAccountsGetAllActive()`
   * Get an account's username: `PurpleAccountGetUsername(account)`
   * Get an account's protocol: `PurpleAccountGetProtocolName(account)`
   * Enable/disable an account: `PurpleAccountSetEnabled(account, ui, True/False)`
   * Get its icon: `PurpleAccountGetBuddyIconPath(account)`
   * Set its icon: `PurpleAccountSetBuddyIconPath(account, path)`

 * [/doxygen/dev2.x.y/html/blist_8h.html Your buddies]:
   * Get list of all buddies: `PurpleFindBuddies(account,screenname)` [Second argument NULL, to search for all]
   * Get a particular buddy: `PurpleFindBuddy(account, screenname)`
   * Check if a buddy is online: `PurpleBuddyIsOnline(buddy)`
   * Get a buddy's icon: `PurpleBuddyGetIcon(buddy)`
   * Get a buddy's alias: `PurpleBuddyGetAlias(buddy)`
   * Get a buddy's name: `PurpleBuddyGetName(buddy)`
   * Get the account a buddy belongs to: `PurpleBuddyGetAccount(buddy)`

 * [/doxygen/dev2.x.y/html/savedstatuses_8h.html Your own status] ([/doxygen/dev2.x.y/html/status_8h.html also]):
   * Some constants you can't get through the D-Bus interface yet:
{{{
#!python
STATUS_OFFLINE = 1
STATUS_AVAILABLE = 2
STATUS_UNAVAILABLE = 3
STATUS_INVISIBLE = 4
STATUS_AWAY = 5
STATUS_EXTENDED_AWAY = 6
STATUS_MOBILE = 7
STATUS_TUNE = 8
}}}
   * Get your status with: `PurpleSavedstatusGetCurrent()`
   * Get your status message with: `PurpleSavedstatusGetMessage(status)`
   * Create a status with: `PurpleSavedstatusNew(title, type)`
   * Set a status message with: `PurpleSavedstatusSetMessage(status, message)`
   * Actually set the status with: `PurpleSavedstatusActivate(status)`

 * [/doxygen/dev2.x.y/html/conversation_8h.html Your conversations]:
   * Get all conversations: `PurpleGetConversations()`
   * Get all IMs: `PurpleGetIms()`
   * Create a new conversation: `PurpleConversationNew(type,account,name)`
   * Get the type of a conversation: `PurpleConversationGetType(conv)` [Returns PURPLE_CONV_TYPE_IM = 1, PURPLE_CONV_TYPE_CHAT = 2]
   * Which account of yours is having that conversation: `PurpleConversationGetAccount(conv)`
   * Get a conversation's title: `PurpleConversationGetTitle(conv)`
   * Get a conversation's name: `PurpleConversationGetName(conv)`
   * Get the im of a conversation: `PurpleConvIm(conv)`
   * Send an IM message with: `PurpleConvImSend(im, message)`

 * [/doxygen/dev2.x.y/html/prefs_8h.html Preferences]:
   * Get a preference: `PurplePrefsGetBool`, `PurplePrefsGetString`, etc.
   * Set a preference: `PurplePrefsSetInt`, `PurplePrefsSetPath`, etc.

There are other things you can do; see the [/doxygen/dev2.x.y/html/main.html documentation] and the source files.

== Further reading ==

 * [http://dbus.freedesktop.org/ D-Bus home page]
 * [http://www.freedesktop.org/wiki/IntroductionToDBus Introduction To DBus]
 * The `purple-remote` and `purple-url-handler` scripts that are installed with Pidgin
 * The Finch docklet plugin in `finch/plugins/pietray.py`
 * Some examples from [http://arstechnica.com/reviews/apps/pidgin-2-0.ars/4 Ars Technica's review of Pidgin]
 * [http://www.amazon.com/Open-Source-Messaging-Application-Development/dp/1590594673 Sean Egan's book] is a good reference on many things. 
 * [http://pidgin.im/~elb/cgi-bin/pyblosxom.cgi/architecture.html Elb's drawing of the Pidgin architecture] might be useful too.
 * The most authoritative reference is the source code. Dig in.

== Troubleshooting D-Bus ==

Ideally, everything should just work fine. If you have a broken or non-standard system, though, here are some things to check for:

 * Firstly, Pidgin should have been built with D-Bus support. You can check this in Help -> About. If your Pidgin was not built with D-Bus support, you have to build Pidgin yourself if you want D-Bus working.

 * In addition, a D-Bus daemon should be running, and the variable `DBUS_SESSION_BUS_ADDRESS` should be set, in the environment from which Pidgin is started. If Pidgin cannot find `DBUS_SESSION_BUS_ADDRESS`, it will try to execute dbus-launch with the --autolaunch option. This might either successfully start a new session bus, or Pidgin might continue with only
{{{
dbus: Failed to get connection: Failed to execute dbus-launch to autolaunch D-Bus session
}}}
in the debug output. Either way, you do not want this to happen. Make sure Pidgin gets the `DBUS_SESSION_BUS_ADDRESS`` of an existing bus. Check that you have
{{{
dbus: okkk
}}}
near the top of Pidgin's debug output.

 * Your D-Bus script must also use the same bus that Pidgin is using. The easiest way to ensure this is to run the script in the same shell that you started Pidgin from; you could also get the values of `DBUS_SESSION_BUS_ADDRESS` and `DBUS_SESSION_BUS_PID` from the shell and export them in the other shell. If you get an error like
{{{
DBusException: org.freedesktop.DBus.Error.ServiceUnknown: The name im.pidgin.purple.PurpleService was not provided by any .service files
}}}
it means that either Pidgin didn't start with `dbus: okkk`, or your script is using a different bus.

== DBUS glib example ==
To use DBUS glib library to listen to pidgin signals, things are little bit different from what you will do with Python. 

 * First of all, you need to define marshaller functions for the signal handler.[[BR]]I.e., if you want to listen to signal '''''!ReceivedImMsg''''', the first thing to do is to find out the correct parameters associated with the signal. Since there is no any document specifying each Pidgin signal's parameter format, one good way to find out the secret is to use dbus-monitor. Start the dbus-monitor like
{{{
$dbus-monitor type=signal interface="im.pidgin.purple.PurpleInterface"
}}}

 And then ask your buddy to send you some IM messages. The dbus-monitor will print out something like:
{{{
signal sender=:1.21 -> dest=(null destination) path=/im/pidgin/purple/PurpleObject; interface=im.pidgin.purple.PurpleInterface; member=ReceivedImMsg
   int32 1097
   string "mybuddy@hotmail.com"
   string "<FONT FACE="Times"><FONT COLOR="#000000">Hi!</FONT></FONT>"
   int32 8728
   uint32 0
}}}

 Now you know that the parameters associated with signal '''''!ReceivedImMsg''''' are:
{{{
INT32,
STRING,
STRING,
INT32,
UINT32
}}}

 Then we can create file '''marshal.list''' with the one line marshaller function definition:
{{{
VOID:INT,STRING,STRING,INT,UINT
}}}

 * '''Please note, apparently the types for these functions may have changed accidentally at one point. The original examples here had the types {{{ VOID:UINT,STRING,STRING,UINT,UINT }}}. Make sure to check the types with dbus-monitor and use the appropriate types or your callbacks will mysteriously not be triggered! '''

 * Second, generate '''marshal.h''' and '''marshal.c'''[[BR]]After create your own file '''marshal.list''', you can then use tool '''glib-genmarshal''' to generate '''marshal.h''' and '''marshal.c''' in the following way:
{{{
$glib-genmarshal --header --prefix=marshal marshal.list > marshal.h
$glib-genmarshal --body --prefix=marshal marshal.list > marshal.c
}}}

 * Third, write your own signal handler '''pidgin_dbus_signal_hndl.c''':
{{{
#include <glib.h>
#include <glib-object.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>

#include "marshal.h"  /* The file generated from marshal.list via tool glib-genmarshal */

/* pidgin dbus station name */
#define DBUS_SERVICE_PURPLE      "im.pidgin.purple.PurpleService"
#define DBUS_PATH_PURPLE         "/im/pidgin/purple/PurpleObject"
#define DBUS_INTERFACE_PURPLE    "im.pidgin.purple.PurpleInterface"

/* global dbus instance */
DBusGConnection *bus;
DBusGProxy *purple_proxy;


/* Main event loop */
GMainLoop *loop = NULL;

/* Signal callback handling routing */
void received_im_msg_cb (DBusGProxy *purple_proxy, int account_id, 
                         const char *sender, const char *message, 
                         int conv_id, unsigned int flags,
                         gpointer user_data)
{
    g_print("Account %d receives msg \"%s\" from %s\n",   
            account_id, message, sender);
}

/*
 * The main process, loop waiting for any signals
 */
int main (int argc, char **argv)
{
    GError *error = NULL;
    
    g_type_init ();

    /* Get the bus */
    bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
    if (bus == NULL) {
        g_printerr("Failed to open connection to bus: %s", error->message);
        g_error_free(error);
        return -1;
    }

    /* Create a proxy object for the bus driver */
    purple_proxy = dbus_g_proxy_new_for_name (bus,
                                              DBUS_SERVICE_PURPLE,
                                              DBUS_PATH_PURPLE,
                                              DBUS_INTERFACE_PURPLE);
    
    if (!purple_proxy) {
        g_printerr("Couldn't connect to the Purple Service: %s", error->message);
        g_error_free(error);
        return -1;
    }

    /* Create the main loop instance */
    loop = g_main_loop_new (NULL, FALSE);

    /* Register dbus signal marshaller */
    dbus_g_object_register_marshaller(marshal_VOID__INT_STRING_STRING_INT_UINT, 
                                      G_TYPE_NONE, G_TYPE_INT, G_TYPE_STRING, 
                                      G_TYPE_STRING, G_TYPE_INT, G_TYPE_UINT, 
                                      G_TYPE_INVALID);
        
    /* Add the signal to the proxy */
    dbus_g_proxy_add_signal(purple_proxy, "ReceivedImMsg", 
                            G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, 
                            G_TYPE_INT, G_TYPE_UINT, G_TYPE_INVALID);

    /* Connect the signal handler to the proxy */
    dbus_g_proxy_connect_signal(purple_proxy, "ReceivedImMsg",
                                G_CALLBACK(received_im_msg_cb), bus, NULL);

    /* Main loop */
    g_main_loop_run (loop);

    return 0;
}
}}}

 Then compile both '''marshal.c''' and '''pidgin_dbus_signal_hndl.c''':
{{{
$ gcc -MMD -Wall -ggdb -O `pkg-config --cflags dbus-1 glib-2.0 dbus-glib-1` `pkg-config --libs dbus-1 glib-2.0 dbus-glib-1` marshal.c pidgin_dbus_signal_hndl.c -o sighndl
}}}

 And then run '''sighndl''':
{{{
./sighndl
}}}
 When your buddy sends you any IM message, if everything goes right, your program will print out something like
{{{
Account 1097 receives msg "<FONT FACE="Times"><FONT COLOR="#000000">Hi</FONT></FONT>" from mybuddy
}}}

== Important Note ==
If you see an issue with the received-im-message signal(ReceivedImMessage for people making a C / Python Plugin) that your application/plugin doesn't get invoked for the first received message, then this is not a bug in your code or pidgin, rather is a feature. Use displayed-im-message (DisplayedImMessage) for better accuracy. See this post for details on "why": [http://tech.shantanugoel.com/2008/09/23/tip-a-post-for-pidgin-plugin-developers.html TIP: A Post For Pidgin Plugin Developers]
----

Good luck!
