Trac is being migrated to new services! Issues can be found in our new YouTrack instance and WIKI pages can be found on our website.

Changes between Initial Version and Version 1 of DbusHowto


Ignore:
Timestamp:
Jan 16, 2008, 8:33:16 PM (16 years ago)
Author:
shreevatsa
Comment:

Writing D-Bus scripts to work with Pidgin

Legend:

Unmodified
Added
Removed
Modified
  • DbusHowto

    v1 v1  
     1= D-Bus Howto =
     2
     3[[TOC(inline)]]
     4
     5== Introduction ==
     6
     7D-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.
     8
     9There are two kinds of communication/interaction that can happen over D-Bus:
     10 * Emitting signals / listening to signals
     11 * Calling methods / replying to method calls
     12
     13You do the listening and calling; Pidgin does the emitting and replying.
     14
     15== Listening to signals ==
     16
     17Many Pidgin events generate corresponding signals, and by listening to them, you can gain a fair idea of what's going on, and react appropriately.
     18
     19To 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):
     20{{{
     21#!python
     22bus.add_signal_receiver(my_func,
     23                        dbus_interface="im.pidgin.purple.PurpleInterface",
     24                        signal_name="ReceivedImMsg")
     25}}}
     26
     27[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.]
     28
     29This 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 [http://developer.pidgin.im/doxygen/dev/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:
     30{{{
     31#!python
     32   def my_func(account, sender, message, conversation, flags):
     33       print sender, "said:", message
     34}}}
     35
     36There 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:
     37{{{
     38#!python
     39#!/usr/bin/env python
     40
     41def my_func(account, sender, message, conversation, flags):
     42    print sender, "said:", message
     43
     44import dbus, gobject
     45from dbus.mainloop.glib import DBusGMainLoop
     46dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
     47bus = dbus.SessionBus()
     48
     49bus.add_signal_receiver(my_func,
     50                        dbus_interface="im.pidgin.purple.PurpleInterface",
     51                        signal_name="ReceivedImMsg")
     52
     53loop = gobject.MainLoop()
     54loop.run()
     55}}}
     56
     57Obviously,
     58 * you should name your function something more descriptive than `my_func` (`received_im_msg_cb` is a good convention, cb for "callback"); and
     59 * a D-Bus script that simply prints what you could see in the client anyway is rather useless.
     60
     61But you get the idea.
     62
     63Here's a sample list of signals you can receive. For the full list, look in pages named "Signals" in the [http://developer.pidgin.im/doxygen/dev/html/pages.html list of documentation pages].
     64
     65 * [http://developer.pidgin.im/doxygen/dev/html/account-signals.html Account signals]:
     66    * account-status-changed, account-connecting, account-error-changed, account-authorization-requested
     67 * [http://developer.pidgin.im/doxygen/dev/html/blist-signals.html Buddy list signals]:
     68    * buddy-status-changed, buddy-idle-changed, buddy-signed-on, buddy-signed-off, buddy-icon-changed
     69 * [http://developer.pidgin.im/doxygen/dev/html/connection-signals.html Connection signals]:
     70    * signing-on, signed-on, signing-off, signed-off, connection-error
     71 * [http://developer.pidgin.im/doxygen/dev/html/conversation-signals.html Conversation signals]:
     72    * 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.
     73    *  writing-im-msg, wrote-im-msg,
     74    *  sending-im-msg, sent-im-msg,
     75    *  receiving-im-msg, received-im-msg,
     76    *  buddy-typing, buddy-typing-stopped,
     77    * 
     78    *  writing-chat-msg, wrote-chat-msg,
     79    *  sending-chat-msg, sent-chat-msg,
     80    *  receiving-chat-msg, received-chat-msg,
     81    *  chat-buddy-joining, chat-buddy-joined, chat-buddy-leaving, chat-buddy-left,
     82    *  chat-buddy-flags, chat-inviting-user, chat-invited-user,
     83    *  chat-invited, chat-joined, chat-left,
     84    *  chat-topic-changed,
     85    *  conversation-created, conversation-updated, deleting-conversation,
     86    *  conversation-extended-menu
     87 * [http://developer.pidgin.im/doxygen/dev/html/core-signals.html Core signals]:
     88    * quitting
     89 * [http://developer.pidgin.im/doxygen/dev/html/gtkconv-signals.html GtkConv signals]:
     90    *  displaying-im-msg, displayed-im-msg,
     91    *  displaying-chat-msg, displayed-chat-msg,
     92    *  conversation-switched,
     93    *  conversation-hiding, conversation-displayed
     94 * [http://developer.pidgin.im/doxygen/dev/html/gtkimhtml-signals.html GtkIMHTML signals]:
     95    * GtkIMHTML is the widget used for the input area in Pidgin
     96    * url_clicked, format_buttons_update, format_function_clear, format_function_toggle, format_function_update
     97 * [http://developer.pidgin.im/doxygen/dev/html/notify-signals.html Notification signals]:
     98    * displaying-userinfo, displaying-email-notification, displaying-emails-notification
     99 * [http://developer.pidgin.im/doxygen/dev/html/plugin-signals.html Plugin signals]:
     100    * plugin-load, plugin-unload
     101 * [http://developer.pidgin.im/doxygen/dev/html/savedstatus-signals.html Saved status signals]:
     102    * savedstatus-changed
     103 * [http://developer.pidgin.im/doxygen/dev/html/sound-signals.html Sound signals]:
     104    * playing-sound-event
     105 * [http://developer.pidgin.im/doxygen/dev/html/xfer-signals.html File transfer signals]:
     106    * file-recv-complete, file-recv-request, file-send-accept, file-send-start, file-send-cancel, file-send-complete
     107
     108=== Aside: Signals in Pidgin plugins ===
     109In 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.
     110
     111For example, the signal "playing-sound-event" is emitted just before a sound is played. Its documentation says
     112{{{
     113#!c
     114gboolean (*playing_sound_event)(PurpleSoundEventID event, PurpleAccount *account);
     115}}}
     116and says that if your registered function returns TRUE, Pidgin will cancel playing the sound.
     117
     118As another example, the signal "chat-invited" is emitted when you are invited to a chat.  Its documentation says its type is
     119{{{
     120#!c
     121gint (*chat_invited)(PurpleAccount *account, const char *inviter,
     122                     const char *chat, const char *invite_message
     123                     const GHashTable *components);
     124}}}
     125and 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.
     126
     127Your registered callback function in a plugin might also have an effect on Pidgin by changing the values of parameters it gets.
     128
     129You can't do any of this in a D-Bus script. Look at [http://developer.pidgin.im/doxygen/dev/html/signal-howto.html Signals HOWTO] to see how to use signals in plugins.
     130
     131Even with read-only plugins, there is a lot you can do:
     132 * 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.)
     133 * When a status message is set in Pidgin, connect to Twitter and update a Twitter account with the Pidgin status message.
     134 * Log all the status messages of a particular buddy.
     135 * Calculate the average length of (or delay between) messages sent and received.
     136 * ''Et cetera'': I'm sure you can think of a lot of semi-useful things to do.
     137
     138Of course, your script doesn't have to be read-only; you can also actually ''do'' things:
     139
     140== Calling Pidgin methods ==
     141
     142Most 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.
     143
     144To start calling Pidgin functions, you need to get a reference to Pidgin's D-Bus interface. This is in Python:
     145{{{
     146#!python
     147bus = dbus.SessionBus()
     148obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
     149purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
     150}}}
     151
     152Now you can call Pidgin functions as if they were members of the `purple` object. For example, to send a message to all your IM windows, you can do:
     153{{{
     154#!python
     155for conv in purple.PurpleGetIms():
     156    purple.PurpleConvImSend(purple.PurpleConvIm(conv), "Ignore.")
     157}}}
     158
     159Sometimes things are not direct. To set your status message without changing the status, for example, you need to use five calls:
     160{{{
     161#!python
     162def set_message(message):
     163    # Get current status type (Available/Away/etc.)
     164    current = purple.PurpleSavedstatusGetType(purple.PurpleSavedstatusGetCurrent())
     165    # Create new transient status and activate it
     166    status = purple.PurpleSavedstatusNew("", current)
     167    purple.PurpleSavedstatusSetMessage(status, message)
     168    purple.PurpleSavedstatusActivate(status)
     169}}}
     170And this still does unexpected things when your current status is a complex one.
     171
     172The only reference for the available functions is the [http://developer.pidgin.im/doxygen/dev/html/files.html documentation for the header files], so you want to look there to figure out which functions you need to call.
     173
     174Here are a few of the useful ones:
     175
     176 * [http://developer.pidgin.im/doxygen/dev/html/account_8h.html Your accounts]:
     177   * Get a particular account: `PurpleAccountsFind(name, protocol)`
     178   * Get all active accounts: `PurpleAccountsGetAllActive()`
     179   * Get an account's username: `PurpleAccountGetUsername(account)`
     180   * Get an account's protocol: `PurpleAccountGetProtocolName(account)`
     181   * Enable/disable an account: `PurpleAccountSetEnabled(account, ui, True/False)`
     182   * Get its icon: `PurpleAccountGetBuddyIconPath(account)`
     183   * Set its icon: `PurpleAccountSetBuddyIconPath(account, path)`
     184
     185 * [http://developer.pidgin.im/doxygen/dev/html/blist_8h.html Your buddies]:
     186   * Get list of all buddies: `PurpleFindBuddies(account,screenname)` [Second argument NULL, to search for all]
     187   * Get a particular buddy: `PurpleFindBuddy(account, screenname)`
     188   * Check if a buddy is online: `PurpleBuddyIsOnline(buddy)`
     189   * Get a buddy's icon: `PurpleBuddyGetIcon(buddy)`
     190   * Get a buddy's alias: `PurpleBuddyGetAlias(buddy)`
     191   * Get a buddy's name: `PurpleBuddyGetName(buddy)`
     192   * Get the account a buddy belongs to: `PurpleBuddyGetAccount(buddy)`
     193
     194 * [http://developer.pidgin.im/doxygen/dev/html/savedstatuses_8h.html Your own status] ([http://developer.pidgin.im/doxygen/dev/html/status_8h.html also]):
     195   * Some constants you can't get through the D-Bus interface yet:
     196{{{
     197#!python
     198STATUS_OFFLINE = 1
     199STATUS_AVAILABLE = 2
     200STATUS_UNAVAILABLE = 3
     201STATUS_INVISIBLE = 4
     202STATUS_AWAY = 5
     203STATUS_EXTENDED_AWAY = 6
     204STATUS_MOBILE = 7
     205STATUS_TUNE = 8
     206}}}
     207   * Get your status with: `PurpleSavedstatusGetCurrent()`
     208   * Get your status message with: `PurpleSavedstatusGetMessage(status)`
     209   * Create a status with: `PurpleSavedstatusNew(title, type)`
     210   * Set a status message with: `PurpleSavedstatusSetMessage(status, message)`
     211   * Actually set the status with: `PurpleSavedstatusActivate(status)`
     212
     213 * [http://developer.pidgin.im/doxygen/dev/html/conversation_8h.html Your conversations]:
     214   * Get all conversations: `PurpleGetConversations()`
     215   * Get all IMs: `PurpleGetIms()`
     216   * Create a new conversation: `PurpleConversationNew(type,account,name)`
     217   * Get the type of a conversation: `PurpleConversationGetType(conv)` [Returns PURPLE_CONV_TYPE_IM = 1, PURPLE_CONV_TYPE_CHAT = 2]
     218   * Which account of yours is having that conversation: `PurpleConversationGetAccount(conv)`
     219   * Get a conversation's title: `PurpleConversationGetTitle(conv)`
     220   * Get a conversation's name: `PurpleConversationGetName(conv)`
     221   * Get the im of a conversation: `PurpleConvIm(conv)`
     222   * Send a message with: `PurpleConvImSend(im, message)`
     223
     224 * [http://developer.pidgin.im/doxygen/dev/html/prefs_8h.html Preferences]:
     225   * Get a preference: `PurplePrefsGetBool`, `PurplePrefsGetString`, etc.
     226   * Set a preference: `PurplePrefsSetInt`, `PurplePrefsSetPath`, etc.
     227
     228There are other things you can do; see the [http://developer.pidgin.im/doxygen/dev/html/main.html documentation] and the source files.
     229
     230== Further reading ==
     231
     232 * [http://dbus.freedesktop.org/ D-Bus home page]
     233 * [http://www.freedesktop.org/wiki/IntroductionToDBus Introduction To DBus]
     234 * The `purple-remote` and `purple-url-handler` scripts that are installed with Pidgin
     235 * The Finch docklet plugin in `finch/plugins/pietray.py`
     236 * Some examples from [http://arstechnica.com/reviews/apps/pidgin-2-0.ars/4 Ars Technica's review of Pidgin]
     237 * [http://www.amazon.com/Open-Source-Messaging-Application-Development/dp/1590594673 Sean Egan's book] is a good reference on many things.
     238 * [http://pidgin.im/~elb/cgi-bin/pyblosxom.cgi/architecture.html Elb's drawing of the Pidgin architecture] might be useful too.
     239 * The most authoritative reference is the source code. Dig in.
     240
     241== Troubleshooting D-Bus ==
     242
     243Ideally, everything should just work fine. If you have a broken or non-standard system, though, here are some things to check for:
     244
     245 * 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.
     246
     247 * 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
     248{{{
     249dbus: Failed to get connection: Failed to execute dbus-launch to autolaunch D-Bus session
     250}}}
     251in 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
     252{{{
     253dbus: okkk
     254}}}
     255near the top of Pidgin's debug output.
     256
     257 * 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
     258{{{
     259DBusException: org.freedesktop.DBus.Error.ServiceUnknown: The name im.pidgin.purple.PurpleService was not provided by any .service files
     260}}}
     261it means that either Pidgin didn't start with `dbus: okkk`, or your script is using a different bus.
     262
     263----
     264
     265Good luck!
All information, including names and email addresses, entered onto this website or sent to mailing lists affiliated with this website will be public. Do not post confidential information, especially passwords!