> I'm now an uber-conservative law student. I should get mad props for
> knowing what svn is, much less running my panel out of it ;)
Then props you shall have.
> > I'll look into supporting both tomorrow. Tonight I have an important
> > football match to watch.
> Football-is that like soccer?
We call it the Beautiful Game my friend and the result has made me very
happy.
This patch adds support for BOTH Gaim2 and Pidgin
Creates a new file gimmie/gimmie_pidgin.py incidentally.
--
James Ogley
[EMAIL PROTECTED] http://usr-local-bin.org
GNOME for openSUSE: http://software.opensuse.org/download/GNOME:/
Help end poverty: http://oxfam.org.uk/in
diff -ruN gimmie.orig/ChangeLog gimmie/ChangeLog
--- gimmie.orig/ChangeLog 2007-05-01 19:31:54.000000000 +0100
+++ gimmie/ChangeLog 2007-05-02 08:56:10.000000000 +0100
@@ -1,3 +1,8 @@
+2007-05-02 James Ogley <[EMAIL PROTECTED]>
+
+ * gimmie/gimmie_pidgin.py: Add Pidgin support.
+ * gimmie/gimmie_people.py: Use Pidgin as well as Gaim.
+
2007-05-01 Alex Graveley <[EMAIL PROTECTED]>
* gimmie/gimmie_recent.py
diff -ruN gimmie.orig/gimmie/gimmie_people.py gimmie/gimmie/gimmie_people.py
--- gimmie.orig/gimmie/gimmie_people.py 2007-04-19 13:54:24.000000000 +0100
+++ gimmie/gimmie/gimmie_people.py 2007-05-02 15:09:37.000000000 +0100
@@ -10,6 +10,7 @@
from gimmie_base import Item, ItemSource, DisabledItemSource, Topic
from gimmie_util import icon_factory, ToolMenuButton, ImplementMe
from gimmie_gaim import gaim_reader, gaim_dbus
+from gimmie_pidgin import pidgin_reader, pidgin_dbus
#
@@ -30,9 +31,13 @@
filter_by_date=filter_by_date)
# Listen for changes in the Gaim files
+ pidgin_reader.connect("reload", lambda x: self.emit("reload"))
gaim_reader.connect("reload", lambda x: self.emit("reload"))
def get_items_uncached(self):
+ for buddy in pidgin_reader.get_all_buddies():
+ yield buddy
+
for buddy in gaim_reader.get_all_buddies():
yield buddy
@@ -50,10 +55,17 @@
# Listen for changes in the Gaim files. This isn't a good indicator of
# presence changes, but it works sometimes.
+ pidgin_reader.connect("reload", lambda x: self.emit("reload"))
gaim_reader.connect("reload", lambda x: self.emit("reload"))
def get_items_uncached(self):
try:
+ for buddy in pidgin_reader.get_all_buddies():
+ if buddy.get_is_online():
+ yield buddy
+ except (dbus.DBusException, TypeError):
+ print " !!! Error accessing Pidgin D-BUS interface. Is Pidgin running?"
+ try:
for buddy in gaim_reader.get_all_buddies():
if buddy.get_is_online():
yield buddy
@@ -101,6 +113,13 @@
item.show()
self.append(item)
+ for acct in pidgin_reader.get_accounts():
+ item = gtk.CheckMenuItem(acct.get_name())
+ if acct.get_auto_login():
+ item.set_active(True)
+ item.show()
+ self.append(item)
+
for acct in gaim_reader.get_accounts():
item = gtk.CheckMenuItem(acct.get_name())
if acct.get_auto_login():
@@ -126,6 +145,12 @@
gtk.ToolButton.__init__(self, img, _("New Person"))
self.set_tooltip(tooltips, _("Add a new contact person"))
+ def _pidgin_get_default_account_obj(self):
+ acct = pidgin.PurpleAccountsFindConnected("", "")
+ if not acct:
+ acct = pidgin_dbus.PurpleAccountsFindAny("", "")
+ return acct
+
def _gaim_get_default_account_obj(self):
acct = gaim_dbus.GaimAccountsFindConnected("", "")
if not acct:
@@ -134,10 +159,14 @@
def do_clicked(self):
try:
- acct = self._gaim_get_default_account_obj()
- gaim_dbus.GaimBlistRequestAddBuddy(acct, "", "", "")
- except dbus.DBusException:
- print " !!! Gaim2 D-BUS interface not available. Is Gaim2 running?"
+ acct = self._pidgin_get_default_account_obj()
+ pidgin_dbus.PurpleBlistRequestAddBuddy(acct, "", "", "")
+ except:
+ try:
+ acct = self._gaim_get_default_account_obj()
+ gaim_dbus.GaimBlistRequestAddBuddy(acct, "", "", "")
+ except dbus.DBusException:
+ print " !!! Pidgin or Gaim2 D-BUS interface not available. Is either Pidgin or Gaim2 running?"
class AvailableToolMenuButton(ToolMenuButton):
@@ -164,6 +193,9 @@
from gimmie_running import RunningChats
self.set_running_source_factory(lambda: RunningChats())
+ # Reload the sidebar when Pidgin's files change
+ pidgin_reader.connect("reload", lambda x: self.emit("reload"))
+
# Reload the sidebar when Gaim's files change
gaim_reader.connect("reload", lambda x: self.emit("reload"))
@@ -183,6 +215,9 @@
source_list += [DisabledItemSource(_("Friendster"), "stock_people"),
None]
+ for acct in pidgin_reader.get_accounts():
+ source_list.append(acct)
+
for acct in gaim_reader.get_accounts():
source_list.append(acct)
diff -ruN gimmie.orig/gimmie/gimmie_pidgin.py gimmie/gimmie/gimmie_pidgin.py
--- gimmie.orig/gimmie/gimmie_pidgin.py 1970-01-01 01:00:00.000000000 +0100
+++ gimmie/gimmie/gimmie_pidgin.py 2007-05-02 08:41:39.000000000 +0100
@@ -0,0 +1,536 @@
+
+import datetime
+import re
+import os
+from gettext import gettext as _
+from xml.dom.minidom import parse
+from xml.sax import saxutils
+
+import gobject
+import dbus
+
+from gimmie_base import Item, ItemSource
+from gimmie_util import bookmarks, launcher, icon_factory, FileMonitor, DBusWrapper
+
+
+class PidginBuddy(Item):
+ def __init__(self, node):
+ self.account_name = node.getAttribute("account")
+ self.proto = node.getAttribute("proto")
+ self.screenname = node.getElementsByTagName("name")[0].childNodes[0].data
+ try:
+ self.alias = node.getElementsByTagName("alias")[0].childNodes[0].data
+ except IndexError:
+ self.alias = ""
+
+ self.buddy_icon = None
+
+ icondir = os.path.expanduser("~/.purple/icons")
+
+ for setting in node.getElementsByTagName("setting"):
+ if setting.getAttribute("name") == "buddy_icon":
+ path = setting.childNodes[0].data
+ if os.path.split(path)[0] == "":
+ path = os.path.join(icondir, path)
+ if os.path.exists(path):
+ self.buddy_icon = path
+ break
+
+ Item.__init__(self,
+ uri="aim:goim?screenname=\"%s\"" % self.screenname,
+ mimetype="purple/buddy")
+
+ self.idle_time = None
+
+ def get_name(self):
+ return self.get_displayname()
+
+ def get_displayname(self):
+ return self.get_alias() or self.get_screenname()
+
+ def get_screenname(self):
+ return self.screenname
+
+ def get_alias(self):
+ return self.alias
+
+ def get_icon(self, icon_size):
+ icon = None
+ if self.buddy_icon:
+ icon = icon_factory.load_icon(self.buddy_icon, icon_size)
+ if icon:
+ icon = icon_factory.make_icon_frame(icon, icon_size, True)
+ if not icon:
+ acct_type = self.get_account().get_nice_protocol()
+ if acct_type == "aim" and self.get_screenname()[:1].isdigit():
+ # ICQ screennames start with a number
+ acct_type = "icq"
+ icon = icon_factory.load_icon("im-" + acct_type, icon_size)
+ if not icon:
+ icon = icon_factory.load_icon("stock_person", icon_size)
+
+ try:
+ if not self.get_is_online():
+ icon = icon_factory.greyscale(icon)
+ except (dbus.DBusException, TypeError):
+ icon = icon_factory.greyscale(icon)
+
+ return icon
+
+ def get_comment(self):
+ comment = ""
+
+ if pidgin_reader.get_buddy_has_name_clash(self):
+ if self.get_alias():
+ comment = self.get_screenname()
+ else:
+ comment = self.get_account_name()
+
+ try:
+ if self.get_is_online():
+ status_id = pidgin_dbus.PurplePresenceGetActiveStatus(self.get_presence_dbus_obj())
+ msg = pidgin_dbus.PurpleStatusGetAttrString(status_id, "message")
+ if msg:
+ if comment: comment += "\n"
+ comment += re.sub('<.*?>', '', msg)
+ elif not self.get_is_available():
+ if comment: comment += "\n"
+ if self.get_is_idle():
+ idle_time = self.get_idle_time()
+ if idle_time:
+ comment += _("Idle since %s") % \
+ self.pretty_print_time_since(idle_time, include_today=False)
+ else:
+ comment += _("Idle")
+ else:
+ comment += _("Away")
+ else:
+ if comment: comment += "\n"
+ comment += _("Offline")
+ except (dbus.DBusException, TypeError):
+ pass
+
+ return comment
+
+ def get_name_markup(self):
+ # Reimplemented from Item to make <3 last
+ markup = saxutils.escape(self.get_name() or "")
+ online = "<span foreground='limegreen'>●</span>"
+ heart = self.get_is_pinned() and " <span foreground='red'>♥</span>" or ""
+
+ try:
+ if self.get_is_online():
+ if self.get_is_available():
+ return "%s %s%s" % (markup, online, heart)
+ else:
+ return "<i>%s</i> %s%s" % (markup, online, heart)
+ except (dbus.DBusException, TypeError):
+ pass
+
+ return "<span foreground='darkgrey'><i>%s</i></span>%s" % (markup, heart)
+
+ def matches_text(self, text):
+ '''
+ Override Item.matches_text to avoid searching the comment, since it can
+ be slow do fetch with D-BUS, and isn\'t reliable anyway.
+ '''
+ return (self.screenname.lower().find(text) > -1) or \
+ (self.alias and self.alias.lower().find(text) > -1)
+
+ def set_account(self, acct):
+ self.account = acct
+
+ def get_account(self):
+ return self.account
+
+ def get_account_name(self):
+ return self.account_name
+
+ def do_open(self):
+ try:
+ self.get_account().go_online() # Log in
+ conv = pidgin_dbus.PurpleConversationNew(1, # Create IM conversation
+ self.get_account().get_dbus_obj(),
+ self.get_screenname())
+ pidgin_dbus.PurpleConversationPresent(conv)
+ print " *** Starting new Pidgin conversation with '%s'" % self.get_screenname()
+ except (dbus.DBusException, TypeError):
+ print " *** Calling purple-remote uri: %s" % self.get_uri()
+ launcher.launch_command("purple-remote uri %s" % self.get_uri())
+
+ def get_is_online(self):
+ return pidgin_dbus.PurpleBuddyIsOnline(self.get_dbus_obj()) > 0
+
+ def get_is_available(self):
+ return pidgin_dbus.PurplePresenceIsAvailable(self.get_presence_dbus_obj())
+
+ def get_is_idle(self):
+ return pidgin_dbus.PurplePresenceIsIdle(self.get_presence_dbus_obj())
+
+ def get_idle_time(self):
+ return self.idle_time
+
+ def reset_idle_time(self):
+ self.idle_time = datetime.datetime.now()
+
+ def clear_idle_time(self):
+ self.idle_time = None
+
+ def get_presence_dbus_obj(self):
+ return pidgin_dbus.PurpleBuddyGetPresence(self.get_dbus_obj())
+
+ def get_dbus_obj(self):
+ return pidgin_dbus.PurpleFindBuddy(self.get_account().get_dbus_obj(),
+ self.get_screenname())
+
+ def get_log_path(self):
+ return os.path.join(self.get_account().get_log_path(), self.get_screenname().lower())
+
+ def get_timestamp(self):
+ try:
+ return os.path.getmtime(self.get_log_path())
+ except OSError:
+ return 0
+
+ def get_is_opened(self):
+ try:
+ return pidgin_dbus.PurpleFindConversationWithAccount(1,
+ self.get_screenname(),
+ self.get_account().get_dbus_obj()) != 0
+ except (dbus.DBusException, TypeError):
+ return False
+
+ def handle_drag_data_received(self, selection, target_type):
+ for uri in selection.get_uris():
+ if uri.lower().startswith("aim:goim?screenname="):
+ bookmarks.add_bookmark(uri, "purple/buddy")
+
+ def pin(self):
+ # PidginBuddy needs an XML node with content from the pidgin config file, so
+ # create a PidginBuddyUri which can be created from a URI and pin it
+ # instead.
+ PidginBuddyUri(self.get_uri()).pin()
+ self.emit("reload")
+
+
+class PidginBuddyUri(Item):
+ def __init__(self, uri):
+ assert uri.startswith("aim:"), "Pidgin buddy URI does not begin with aim:"
+ Item.__init__(self, uri=uri, mimetype="purple/buddy")
+
+ pidgin_reader.connect("reload", lambda r, u: self._find_buddy(u), uri)
+ self._find_buddy(uri)
+
+ def _find_buddy(self, uri):
+ for buddy in pidgin_reader.get_all_buddies():
+ if buddy.get_uri() == uri:
+ self.pidgin_buddy = buddy
+ buddy.connect("reload", lambda b: self.emit("reload"))
+ self.emit("reload")
+ break
+ else:
+ raise ValueError("Buddy for URI '%s' not found" % uri)
+
+ def __getattribute__(self, name):
+ try:
+ if hasattr(gobject.GObject, name) or name == "pin":
+ # Allow connect et al. to reach this gobject
+ return Item.__getattribute__(self, name)
+ else:
+ # Avoid infinite recursion accessing pidgin_buddy
+ pidgin_buddy = Item.__getattribute__(self, "pidgin_buddy")
+ return getattr(pidgin_buddy, name)
+ except AttributeError:
+ return Item.__getattribute__(self, name)
+
+
+class PidginGroup:
+ def __init__(self, node):
+ self.name = node.getAttribute("name")
+ self.buddy_list = []
+
+ for contact in node.getElementsByTagName("contact"):
+ for node in node.getElementsByTagName("buddy"):
+ buddy = PidginBuddy(node)
+ self.buddy_list.append(buddy)
+
+ def get_name(self):
+ return self.name
+
+ def get_buddies(self):
+ return self.buddy_list
+
+ def get_dbus_obj(self):
+ return pidgin_dbus.PurpleindGroup(self.get_name())
+
+
+class PidginAccount(ItemSource):
+ def __init__(self, node):
+ self.protocol = node.getElementsByTagName("protocol")[0].childNodes[0].data
+ if self.protocol == "prpl-irc":
+ raise NotImplementedError, "IRC accounts are not supported in Gimmie"
+
+ self.screenname = node.getElementsByTagName("name")[0].childNodes[0].data
+
+ self.alias = None
+ try:
+ self.alias = node.getElementsByTagName("alias")[0].childNodes[0].data
+ except IndexError:
+ if self.protocol == "prpl-jabber":
+ # Strip the trailing /resource from the screenname
+ try:
+ self.alias = self.screenname[:self.screenname.rindex("/")]
+ except ValueError:
+ pass
+
+ uri = "source://Purple/%s/%s" % (self.get_nice_protocol(), self.screenname)
+ comment = self.alias and self.screenname or None
+ ItemSource.__init__(self, uri=uri, comment=comment, filter_by_date=False)
+
+ self.buddyicon = None
+ buddyicon = node.getElementsByTagName("buddyicon")
+ if buddyicon:
+ self.buddyicon = buddyicon[0].childNodes[0].data
+
+ for setting in node.getElementsByTagName("setting"):
+ if setting.getAttribute("name") == "auto-login":
+ if setting.childNodes[0].data == "1":
+ self.auto_login = True
+ break
+ else:
+ self.auto_login = False
+
+ self.buddy_list = []
+ self.group_list = []
+
+ def get_name(self):
+ # Try to use the shorter alias if it's set.
+ return self.get_alias() or self.get_screenname()
+
+ def get_screenname(self):
+ return self.screenname
+
+ def get_alias(self):
+ return self.alias
+
+ def get_auto_login(self):
+ return self.auto_login
+
+ def get_protocol(self):
+ return self.protocol
+
+ def get_nice_protocol(self):
+ if self.protocol == "prpl-oscar":
+ return "aim"
+ if self.protocol.startswith("prpl-"):
+ return self.protocol[len("prpl-"):]
+ return self.protocol
+
+ def get_icon(self, icon_size):
+ icon = None
+ if self.buddyicon:
+ icon = icon_factory.load_icon(self.buddyicon, icon_size)
+ if icon:
+ icon = icon_factory.make_icon_frame(icon, icon_size, True)
+ if not icon:
+ acct_type = self.get_nice_protocol()
+ if acct_type == "aim" and self.get_name()[:1].isdigit():
+ # ICQ screennames start with a number
+ acct_type = "icq"
+ icon = icon_factory.load_icon("im-" + acct_type, icon_size)
+ if not icon:
+ icon = icon_factory.load_icon("stock_person", icon_size)
+
+ return icon
+
+ def get_items(self):
+ return self.buddy_list
+
+ def add_buddy(self, buddy):
+ self.buddy_list.append(buddy)
+
+ def is_online(self, _account = None):
+ return pidgin_dbus.PurpleAccountIsConnected(_account or self.get_dbus_obj())
+
+ def go_online(self):
+ account = self.get_dbus_obj()
+ if not self.is_online(account):
+ pidgin_dbus.PurpleAccountSetStatusVargs(account, "online", 1)
+ pidgin_dbus.PurpleAccountConnect(account)
+
+ def go_offline(self):
+ account = self.get_dbus_obj()
+ if self.is_online(account):
+ pidgin_dbus.PurpleAccountSetStatusVargs(account, "online", 0)
+ pidgin_dbus.PurpleAccountDisconnect(account)
+
+ def get_dbus_obj(self, connect = False):
+ return pidgin_dbus.PurpleAccountsFindAny(self.get_screenname(), self.get_protocol())
+
+ def get_log_path(self):
+ # Pidgin does not include Jabber's account resource in the logdir name
+ logdir = self.get_name().lower().rsplit("/", 1)[0]
+ return os.path.join(os.path.expanduser("~/.purple/logs"),
+ self.get_nice_protocol(),
+ logdir)
+
+
+class PidginAccountReader(gobject.GObject):
+ __gsignals__ = {
+ "reload" : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ())
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.accounts_path = os.path.expanduser("~/.purple/accounts.xml")
+ self.blist_path = os.path.expanduser("~/.purple/blist.xml")
+
+ # Listen for changes to user's Pidgin accounts
+ self.accounts_monitor = FileMonitor(self.accounts_path)
+ self.accounts_monitor.connect("created", lambda a, b: self.emit("reload"))
+ self.accounts_monitor.connect("changed", lambda a, b: self.emit("reload"))
+ self.accounts_monitor.open()
+
+ # Connect to dbus signals to notify us of buddy status changes
+ try:
+ # Update to a single buddy
+ pidgin_dbus.connect_to_signal("BuddyStatusChanged", self._status_changed)
+ pidgin_dbus.connect_to_signal("BuddyIdleChanged", self._idle_changed)
+ pidgin_dbus.connect_to_signal("BuddySignedOn", self._signed_on)
+ pidgin_dbus.connect_to_signal("BuddySignedOff", self._signed_off)
+ pidgin_dbus.connect_to_signal("BuddyIconChanged", self._icon_changed)
+
+ # Need to reread the blist.xml file
+ pidgin_dbus.connect_to_signal("BuddyAdded", self._added)
+ pidgin_dbus.connect_to_signal("BuddyRemoved", self._removed)
+ except dbus.DBusException:
+ # Fallback to listing for blist.xml writes
+ self.blist_monitor = FileMonitor(self.blist_path)
+ self.blist_monitor.connect("created", lambda a, b: self.emit("reload"))
+ self.blist_monitor.connect("changed", lambda a, b: self.emit("reload"))
+ self.blist_monitor.open()
+
+ self.emit("reload")
+
+ def _get_buddy_by_dbus_obj(self, dbus_obj):
+ for buddy in self.buddy_list:
+ try:
+ if buddy.get_dbus_obj() == dbus_obj:
+ return buddy
+ except dbus.DBusException:
+ pass
+ return None
+
+ def _update_buddy(self, dbus_obj):
+ buddy = self._get_buddy_by_dbus_obj(dbus_obj)
+ if buddy:
+ buddy.emit("reload")
+ else:
+ print " !!! Can't find Pidgin buddy %s to update." % dbus_obj
+
+ def _status_changed(self, buddy, old_status, new_status):
+ if old_status != new_status:
+ self._update_buddy(buddy)
+
+ def _idle_changed(self, buddy, is_idle, idle_time):
+ buddy = self._get_buddy_by_dbus_obj(buddy)
+ if buddy:
+ if is_idle:
+ buddy.reset_idle_time()
+ else:
+ buddy.clear_idle_time()
+ buddy.emit("reload")
+
+ def _signed_on(self, buddy):
+ self._update_buddy(buddy)
+
+ def _signed_off(self, buddy):
+ self._update_buddy(buddy)
+
+ def _icon_changed(self, buddy):
+ self._update_buddy(buddy)
+
+ def _added(self, buddy):
+ self.emit("reload")
+
+ def _removed(self, buddy):
+ self.emit("reload")
+
+ def do_reload(self):
+ self.account_list = {}
+ self.group_list = []
+ self.buddy_list = []
+
+ try:
+ acct_doc = parse(self.accounts_path)
+ except IOError, (errno, strerror):
+ print " !!! Error parsing ~/.purple/accounts.xml: %s" % (strerror)
+ return
+
+ for node in acct_doc.getElementsByTagName("account"):
+ try:
+ acct = PidginAccount(node)
+ self.account_list[acct.get_screenname()] = acct
+ except NotImplementedError:
+ pass
+
+ acct_doc.unlink()
+
+ try:
+ blist_doc = parse(self.blist_path)
+ except IOError, (errno, strerror):
+ print " !!! Error parsing ~/.purple/blist.xml: %s" % (strerror)
+ return
+
+ for node in blist_doc.getElementsByTagName("group"):
+ group = PidginGroup(node)
+ self.group_list.append(group)
+
+ for buddy in group.get_buddies():
+ if self.account_list.has_key(buddy.account_name):
+ # Add buddy to it's account, and set the account of the buddy
+ self.account_list[buddy.get_account_name()].add_buddy(buddy)
+ buddy.set_account(self.account_list[buddy.get_account_name()])
+ self.buddy_list.append(buddy)
+ else:
+ print " !!! Pidgin buddy '%s' has unknown account '%s'." % \
+ (buddy.get_name(), buddy.get_account_name())
+
+ blist_doc.unlink()
+
+ self.buddy_name_clashes = set()
+ names = set()
+ for name in [x.get_displayname() for x in self.buddy_list]:
+ if name in names:
+ self.buddy_name_clashes.add(name)
+ else:
+ names.add(name)
+
+ def get_accounts(self):
+ return self.account_list.values()
+
+ def get_groups(self):
+ return self.group_list
+
+ def get_all_buddies(self):
+ return self.buddy_list
+
+ def get_buddy_has_name_clash(self, buddy):
+ # FIXME: Hack so that PidginBuddy can show the screenname if there is a
+ # name clash.
+ return buddy.get_displayname() in self.buddy_name_clashes
+
+
+#
+# Globals
+#
+
+pidgin_dbus = DBusWrapper("im.pidgin.purple.PurpleService",
+ path="/im/pidgin/purple/PurpleObject",
+ interface="im.pidgin.purple.PurpleInterface",
+ program_name="Pidgin")
+
+pidgin_reader = PidginAccountReader()
diff -ruN gimmie.orig/gimmie/gimmie_pidgin.py.ok gimmie/gimmie/gimmie_pidgin.py.ok
--- gimmie.orig/gimmie/gimmie_pidgin.py.ok 1970-01-01 01:00:00.000000000 +0100
+++ gimmie/gimmie/gimmie_pidgin.py.ok 2007-05-01 19:33:03.000000000 +0100
@@ -0,0 +1,536 @@
+
+import datetime
+import re
+import os
+from gettext import gettext as _
+from xml.dom.minidom import parse
+from xml.sax import saxutils
+
+import gobject
+import dbus
+
+from gimmie_base import Item, ItemSource
+from gimmie_util import bookmarks, launcher, icon_factory, FileMonitor, DBusWrapper
+
+
+class GaimBuddy(Item):
+ def __init__(self, node):
+ self.account_name = node.getAttribute("account")
+ self.proto = node.getAttribute("proto")
+ self.screenname = node.getElementsByTagName("name")[0].childNodes[0].data
+ try:
+ self.alias = node.getElementsByTagName("alias")[0].childNodes[0].data
+ except IndexError:
+ self.alias = ""
+
+ self.buddy_icon = None
+
+ icondir = os.path.expanduser("~/.purple/icons")
+
+ for setting in node.getElementsByTagName("setting"):
+ if setting.getAttribute("name") == "buddy_icon":
+ path = setting.childNodes[0].data
+ if os.path.split(path)[0] == "":
+ path = os.path.join(icondir, path)
+ if os.path.exists(path):
+ self.buddy_icon = path
+ break
+
+ Item.__init__(self,
+ uri="aim:goim?screenname=\"%s\"" % self.screenname,
+ mimetype="purple/buddy")
+
+ self.idle_time = None
+
+ def get_name(self):
+ return self.get_displayname()
+
+ def get_displayname(self):
+ return self.get_alias() or self.get_screenname()
+
+ def get_screenname(self):
+ return self.screenname
+
+ def get_alias(self):
+ return self.alias
+
+ def get_icon(self, icon_size):
+ icon = None
+ if self.buddy_icon:
+ icon = icon_factory.load_icon(self.buddy_icon, icon_size)
+ if icon:
+ icon = icon_factory.make_icon_frame(icon, icon_size, True)
+ if not icon:
+ acct_type = self.get_account().get_nice_protocol()
+ if acct_type == "aim" and self.get_screenname()[:1].isdigit():
+ # ICQ screennames start with a number
+ acct_type = "icq"
+ icon = icon_factory.load_icon("im-" + acct_type, icon_size)
+ if not icon:
+ icon = icon_factory.load_icon("stock_person", icon_size)
+
+ try:
+ if not self.get_is_online():
+ icon = icon_factory.greyscale(icon)
+ except (dbus.DBusException, TypeError):
+ icon = icon_factory.greyscale(icon)
+
+ return icon
+
+ def get_comment(self):
+ comment = ""
+
+ if gaim_reader.get_buddy_has_name_clash(self):
+ if self.get_alias():
+ comment = self.get_screenname()
+ else:
+ comment = self.get_account_name()
+
+ try:
+ if self.get_is_online():
+ status_id = gaim_dbus.PurplePresenceGetActiveStatus(self.get_presence_dbus_obj())
+ msg = gaim_dbus.PurpleStatusGetAttrString(status_id, "message")
+ if msg:
+ if comment: comment += "\n"
+ comment += re.sub('<.*?>', '', msg)
+ elif not self.get_is_available():
+ if comment: comment += "\n"
+ if self.get_is_idle():
+ idle_time = self.get_idle_time()
+ if idle_time:
+ comment += _("Idle since %s") % \
+ self.pretty_print_time_since(idle_time, include_today=False)
+ else:
+ comment += _("Idle")
+ else:
+ comment += _("Away")
+ else:
+ if comment: comment += "\n"
+ comment += _("Offline")
+ except (dbus.DBusException, TypeError):
+ pass
+
+ return comment
+
+ def get_name_markup(self):
+ # Reimplemented from Item to make <3 last
+ markup = saxutils.escape(self.get_name() or "")
+ online = "<span foreground='limegreen'>●</span>"
+ heart = self.get_is_pinned() and " <span foreground='red'>♥</span>" or ""
+
+ try:
+ if self.get_is_online():
+ if self.get_is_available():
+ return "%s %s%s" % (markup, online, heart)
+ else:
+ return "<i>%s</i> %s%s" % (markup, online, heart)
+ except (dbus.DBusException, TypeError):
+ pass
+
+ return "<span foreground='darkgrey'><i>%s</i></span>%s" % (markup, heart)
+
+ def matches_text(self, text):
+ '''
+ Override Item.matches_text to avoid searching the comment, since it can
+ be slow do fetch with D-BUS, and isn\'t reliable anyway.
+ '''
+ return (self.screenname.lower().find(text) > -1) or \
+ (self.alias and self.alias.lower().find(text) > -1)
+
+ def set_account(self, acct):
+ self.account = acct
+
+ def get_account(self):
+ return self.account
+
+ def get_account_name(self):
+ return self.account_name
+
+ def do_open(self):
+ try:
+ self.get_account().go_online() # Log in
+ conv = gaim_dbus.PurpleConversationNew(1, # Create IM conversation
+ self.get_account().get_dbus_obj(),
+ self.get_screenname())
+ gaim_dbus.PurpleConversationPresent(conv)
+ print " *** Starting new Pidgin conversation with '%s'" % self.get_screenname()
+ except (dbus.DBusException, TypeError):
+ print " *** Calling purple-remote uri: %s" % self.get_uri()
+ launcher.launch_command("purple-remote uri %s" % self.get_uri())
+
+ def get_is_online(self):
+ return gaim_dbus.PurpleBuddyIsOnline(self.get_dbus_obj()) > 0
+
+ def get_is_available(self):
+ return gaim_dbus.PurplePresenceIsAvailable(self.get_presence_dbus_obj())
+
+ def get_is_idle(self):
+ return gaim_dbus.PurplePresenceIsIdle(self.get_presence_dbus_obj())
+
+ def get_idle_time(self):
+ return self.idle_time
+
+ def reset_idle_time(self):
+ self.idle_time = datetime.datetime.now()
+
+ def clear_idle_time(self):
+ self.idle_time = None
+
+ def get_presence_dbus_obj(self):
+ return gaim_dbus.PurpleBuddyGetPresence(self.get_dbus_obj())
+
+ def get_dbus_obj(self):
+ return gaim_dbus.PurpleFindBuddy(self.get_account().get_dbus_obj(),
+ self.get_screenname())
+
+ def get_log_path(self):
+ return os.path.join(self.get_account().get_log_path(), self.get_screenname().lower())
+
+ def get_timestamp(self):
+ try:
+ return os.path.getmtime(self.get_log_path())
+ except OSError:
+ return 0
+
+ def get_is_opened(self):
+ try:
+ return gaim_dbus.PurpleFindConversationWithAccount(1,
+ self.get_screenname(),
+ self.get_account().get_dbus_obj()) != 0
+ except (dbus.DBusException, TypeError):
+ return False
+
+ def handle_drag_data_received(self, selection, target_type):
+ for uri in selection.get_uris():
+ if uri.lower().startswith("aim:goim?screenname="):
+ bookmarks.add_bookmark(uri, "purple/buddy")
+
+ def pin(self):
+ # GaimBuddy needs an XML node with content from the gaim config file, so
+ # create a GaimBuddyUri which can be created from a URI and pin it
+ # instead.
+ GaimBuddyUri(self.get_uri()).pin()
+ self.emit("reload")
+
+
+class GaimBuddyUri(Item):
+ def __init__(self, uri):
+ assert uri.startswith("aim:"), "Gaim buddy URI does not begin with aim:"
+ Item.__init__(self, uri=uri, mimetype="purple/buddy")
+
+ gaim_reader.connect("reload", lambda r, u: self._find_buddy(u), uri)
+ self._find_buddy(uri)
+
+ def _find_buddy(self, uri):
+ for buddy in gaim_reader.get_all_buddies():
+ if buddy.get_uri() == uri:
+ self.gaim_buddy = buddy
+ buddy.connect("reload", lambda b: self.emit("reload"))
+ self.emit("reload")
+ break
+ else:
+ raise ValueError("Buddy for URI '%s' not found" % uri)
+
+ def __getattribute__(self, name):
+ try:
+ if hasattr(gobject.GObject, name) or name == "pin":
+ # Allow connect et al. to reach this gobject
+ return Item.__getattribute__(self, name)
+ else:
+ # Avoid infinite recursion accessing gaim_buddy
+ gaim_buddy = Item.__getattribute__(self, "gaim_buddy")
+ return getattr(gaim_buddy, name)
+ except AttributeError:
+ return Item.__getattribute__(self, name)
+
+
+class GaimGroup:
+ def __init__(self, node):
+ self.name = node.getAttribute("name")
+ self.buddy_list = []
+
+ for contact in node.getElementsByTagName("contact"):
+ for node in node.getElementsByTagName("buddy"):
+ buddy = GaimBuddy(node)
+ self.buddy_list.append(buddy)
+
+ def get_name(self):
+ return self.name
+
+ def get_buddies(self):
+ return self.buddy_list
+
+ def get_dbus_obj(self):
+ return gaim_dbus.PurpleindGroup(self.get_name())
+
+
+class GaimAccount(ItemSource):
+ def __init__(self, node):
+ self.protocol = node.getElementsByTagName("protocol")[0].childNodes[0].data
+ if self.protocol == "prpl-irc":
+ raise NotImplementedError, "IRC accounts are not supported in Gimmie"
+
+ self.screenname = node.getElementsByTagName("name")[0].childNodes[0].data
+
+ self.alias = None
+ try:
+ self.alias = node.getElementsByTagName("alias")[0].childNodes[0].data
+ except IndexError:
+ if self.protocol == "prpl-jabber":
+ # Strip the trailing /resource from the screenname
+ try:
+ self.alias = self.screenname[:self.screenname.rindex("/")]
+ except ValueError:
+ pass
+
+ uri = "source://Purple/%s/%s" % (self.get_nice_protocol(), self.screenname)
+ comment = self.alias and self.screenname or None
+ ItemSource.__init__(self, uri=uri, comment=comment, filter_by_date=False)
+
+ self.buddyicon = None
+ buddyicon = node.getElementsByTagName("buddyicon")
+ if buddyicon:
+ self.buddyicon = buddyicon[0].childNodes[0].data
+
+ for setting in node.getElementsByTagName("setting"):
+ if setting.getAttribute("name") == "auto-login":
+ if setting.childNodes[0].data == "1":
+ self.auto_login = True
+ break
+ else:
+ self.auto_login = False
+
+ self.buddy_list = []
+ self.group_list = []
+
+ def get_name(self):
+ # Try to use the shorter alias if it's set.
+ return self.get_alias() or self.get_screenname()
+
+ def get_screenname(self):
+ return self.screenname
+
+ def get_alias(self):
+ return self.alias
+
+ def get_auto_login(self):
+ return self.auto_login
+
+ def get_protocol(self):
+ return self.protocol
+
+ def get_nice_protocol(self):
+ if self.protocol == "prpl-oscar":
+ return "aim"
+ if self.protocol.startswith("prpl-"):
+ return self.protocol[len("prpl-"):]
+ return self.protocol
+
+ def get_icon(self, icon_size):
+ icon = None
+ if self.buddyicon:
+ icon = icon_factory.load_icon(self.buddyicon, icon_size)
+ if icon:
+ icon = icon_factory.make_icon_frame(icon, icon_size, True)
+ if not icon:
+ acct_type = self.get_nice_protocol()
+ if acct_type == "aim" and self.get_name()[:1].isdigit():
+ # ICQ screennames start with a number
+ acct_type = "icq"
+ icon = icon_factory.load_icon("im-" + acct_type, icon_size)
+ if not icon:
+ icon = icon_factory.load_icon("stock_person", icon_size)
+
+ return icon
+
+ def get_items(self):
+ return self.buddy_list
+
+ def add_buddy(self, buddy):
+ self.buddy_list.append(buddy)
+
+ def is_online(self, _account = None):
+ return gaim_dbus.PurpleAccountIsConnected(_account or self.get_dbus_obj())
+
+ def go_online(self):
+ account = self.get_dbus_obj()
+ if not self.is_online(account):
+ gaim_dbus.PurpleAccountSetStatusVargs(account, "online", 1)
+ gaim_dbus.PurpleAccountConnect(account)
+
+ def go_offline(self):
+ account = self.get_dbus_obj()
+ if self.is_online(account):
+ gaim_dbus.PurpleAccountSetStatusVargs(account, "online", 0)
+ gaim_dbus.PurpleAccountDisconnect(account)
+
+ def get_dbus_obj(self, connect = False):
+ return gaim_dbus.PurpleAccountsFindAny(self.get_screenname(), self.get_protocol())
+
+ def get_log_path(self):
+ # Gaim does not include Jabber's account resource in the logdir name
+ logdir = self.get_name().lower().rsplit("/", 1)[0]
+ return os.path.join(os.path.expanduser("~/.purple/logs"),
+ self.get_nice_protocol(),
+ logdir)
+
+
+class GaimAccountReader(gobject.GObject):
+ __gsignals__ = {
+ "reload" : (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE,
+ ())
+ }
+
+ def __init__(self):
+ gobject.GObject.__init__(self)
+
+ self.accounts_path = os.path.expanduser("~/.purple/accounts.xml")
+ self.blist_path = os.path.expanduser("~/.purple/blist.xml")
+
+ # Listen for changes to user's Gaim accounts
+ self.accounts_monitor = FileMonitor(self.accounts_path)
+ self.accounts_monitor.connect("created", lambda a, b: self.emit("reload"))
+ self.accounts_monitor.connect("changed", lambda a, b: self.emit("reload"))
+ self.accounts_monitor.open()
+
+ # Connect to dbus signals to notify us of buddy status changes
+ try:
+ # Update to a single buddy
+ gaim_dbus.connect_to_signal("BuddyStatusChanged", self._status_changed)
+ gaim_dbus.connect_to_signal("BuddyIdleChanged", self._idle_changed)
+ gaim_dbus.connect_to_signal("BuddySignedOn", self._signed_on)
+ gaim_dbus.connect_to_signal("BuddySignedOff", self._signed_off)
+ gaim_dbus.connect_to_signal("BuddyIconChanged", self._icon_changed)
+
+ # Need to reread the blist.xml file
+ gaim_dbus.connect_to_signal("BuddyAdded", self._added)
+ gaim_dbus.connect_to_signal("BuddyRemoved", self._removed)
+ except dbus.DBusException:
+ # Fallback to listing for blist.xml writes
+ self.blist_monitor = FileMonitor(self.blist_path)
+ self.blist_monitor.connect("created", lambda a, b: self.emit("reload"))
+ self.blist_monitor.connect("changed", lambda a, b: self.emit("reload"))
+ self.blist_monitor.open()
+
+ self.emit("reload")
+
+ def _get_buddy_by_dbus_obj(self, dbus_obj):
+ for buddy in self.buddy_list:
+ try:
+ if buddy.get_dbus_obj() == dbus_obj:
+ return buddy
+ except dbus.DBusException:
+ pass
+ return None
+
+ def _update_buddy(self, dbus_obj):
+ buddy = self._get_buddy_by_dbus_obj(dbus_obj)
+ if buddy:
+ buddy.emit("reload")
+ else:
+ print " !!! Can't find Gaim buddy %s to update." % dbus_obj
+
+ def _status_changed(self, buddy, old_status, new_status):
+ if old_status != new_status:
+ self._update_buddy(buddy)
+
+ def _idle_changed(self, buddy, is_idle, idle_time):
+ buddy = self._get_buddy_by_dbus_obj(buddy)
+ if buddy:
+ if is_idle:
+ buddy.reset_idle_time()
+ else:
+ buddy.clear_idle_time()
+ buddy.emit("reload")
+
+ def _signed_on(self, buddy):
+ self._update_buddy(buddy)
+
+ def _signed_off(self, buddy):
+ self._update_buddy(buddy)
+
+ def _icon_changed(self, buddy):
+ self._update_buddy(buddy)
+
+ def _added(self, buddy):
+ self.emit("reload")
+
+ def _removed(self, buddy):
+ self.emit("reload")
+
+ def do_reload(self):
+ self.account_list = {}
+ self.group_list = []
+ self.buddy_list = []
+
+ try:
+ acct_doc = parse(self.accounts_path)
+ except IOError, (errno, strerror):
+ print " !!! Error parsing ~/.purple/accounts.xml: %s" % (strerror)
+ return
+
+ for node in acct_doc.getElementsByTagName("account"):
+ try:
+ acct = GaimAccount(node)
+ self.account_list[acct.get_screenname()] = acct
+ except NotImplementedError:
+ pass
+
+ acct_doc.unlink()
+
+ try:
+ blist_doc = parse(self.blist_path)
+ except IOError, (errno, strerror):
+ print " !!! Error parsing ~/.purple/blist.xml: %s" % (strerror)
+ return
+
+ for node in blist_doc.getElementsByTagName("group"):
+ group = GaimGroup(node)
+ self.group_list.append(group)
+
+ for buddy in group.get_buddies():
+ if self.account_list.has_key(buddy.account_name):
+ # Add buddy to it's account, and set the account of the buddy
+ self.account_list[buddy.get_account_name()].add_buddy(buddy)
+ buddy.set_account(self.account_list[buddy.get_account_name()])
+ self.buddy_list.append(buddy)
+ else:
+ print " !!! Gaim buddy '%s' has unknown account '%s'." % \
+ (buddy.get_name(), buddy.get_account_name())
+
+ blist_doc.unlink()
+
+ self.buddy_name_clashes = set()
+ names = set()
+ for name in [x.get_displayname() for x in self.buddy_list]:
+ if name in names:
+ self.buddy_name_clashes.add(name)
+ else:
+ names.add(name)
+
+ def get_accounts(self):
+ return self.account_list.values()
+
+ def get_groups(self):
+ return self.group_list
+
+ def get_all_buddies(self):
+ return self.buddy_list
+
+ def get_buddy_has_name_clash(self, buddy):
+ # FIXME: Hack so that GaimBuddy can show the screenname if there is a
+ # name clash.
+ return buddy.get_displayname() in self.buddy_name_clashes
+
+
+#
+# Globals
+#
+
+gaim_dbus = DBusWrapper("im.pidgin.purple.PurpleService",
+ path="/im/pidgin/purple/PurpleObject",
+ interface="im.pidgin.purple.PurpleInterface",
+ program_name="Pidgin")
+
+gaim_reader = GaimAccountReader()
diff -ruN gimmie.orig/gimmie/Makefile.am gimmie/gimmie/Makefile.am
--- gimmie.orig/gimmie/Makefile.am 2007-05-01 06:06:41.000000000 +0100
+++ gimmie/gimmie/Makefile.am 2007-05-02 08:55:06.000000000 +0100
@@ -32,6 +32,7 @@
gimmie_gui.py \
gimmie_logout.py \
gimmie_people.py \
+ gimmie_pidgin.py \
gimmie_recent.py \
gimmie_running.py \
gimmie_tomboy.py \
_______________________________________________
gimmie-list mailing list
[email protected]
http://lists.beatniksoftware.com/listinfo.cgi/gimmie-list-beatniksoftware.com